diff options
-rw-r--r-- | src/qml/common/qv4compileddata_p.h | 1 | ||||
-rw-r--r-- | src/qml/compiler/qqmlirbuilder.cpp | 25 | ||||
-rw-r--r-- | src/qml/compiler/qqmlirbuilder_p.h | 8 | ||||
-rw-r--r-- | src/qml/doc/src/cppintegration/exposecppattributes.qdoc | 48 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4executablecompilationunit_p.h | 5 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper.cpp | 172 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper_p.h | 28 | ||||
-rw-r--r-- | src/qml/qml/qqmlirloader.cpp | 9 | ||||
-rw-r--r-- | src/qml/qml/qqmlobjectorgadget_p.h | 1 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsAcceptThisObject.qml | 33 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsRejectThisObject.qml | 32 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.h | 1 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 86 |
13 files changed, 399 insertions, 50 deletions
diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index 8636fa4fbe..58a7009083 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -1183,6 +1183,7 @@ struct Unit = ListPropertyAssignReplaceIfDefault | ListPropertyAssignReplaceIfNotDefault, ComponentsBound = 0x200, FunctionSignaturesEnforced = 0x400, + NativeMethodsAcceptThisObject = 0x800, }; quint32_le flags; quint32_le stringTableSize; diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 47671bddfb..ef27567eea 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -790,6 +790,8 @@ private: return Pragma::ListPropertyAssignBehavior; } else if constexpr (std::is_same_v<Argument, Pragma::FunctionSignatureBehaviorValue>) { return Pragma::FunctionSignatureBehavior; + } else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) { + return Pragma::NativeMethodBehavior; } Q_UNREACHABLE_RETURN(Pragma::PragmaType(-1)); @@ -831,6 +833,15 @@ private: pragma->functionSignatureBehavior = Pragma::Enforced; return true; } + } else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) { + if (value == "AcceptThisObject"_L1) { + pragma->nativeMethodBehavior = Pragma::AcceptThisObject; + return true; + } + if (value == "RejectThisObject"_L1) { + pragma->nativeMethodBehavior = Pragma::RejectThisObject; + return true; + } } return false; @@ -854,6 +865,8 @@ private: return "component behavior"_L1; case Pragma::FunctionSignatureBehavior: return "function signature behavior"_L1; + case Pragma::NativeMethodBehavior: + return "native method behavior"_L1; default: break; } @@ -879,6 +892,9 @@ bool IRBuilder::visit(QQmlJS::AST::UiPragma *node) } else if (node->name == QStringLiteral("FunctionSignatureBehavior")) { if (!PragmaParser<Pragma::FunctionSignatureBehaviorValue>::run(this, node, pragma)) return false; + } else if (node->name == QStringLiteral("NativeMethodBehavior")) { + if (!PragmaParser<Pragma::NativeMethodBehaviorValue>::run(this, node, pragma)) + return false; } else { recordError(node->pragmaToken, QCoreApplication::translate( "QQmlParser", "Unknown pragma '%1'").arg(node->name)); @@ -1650,6 +1666,15 @@ void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::Depen break; } break; + case Pragma::NativeMethodBehavior: + switch (p->nativeMethodBehavior) { + case Pragma::AcceptThisObject: + createdUnit->flags |= Unit::NativeMethodsAcceptThisObject; + break; + case Pragma::RejectThisObject: + // this is the default; + break; + } } } diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 85578cdd6d..852c2d2244 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -406,6 +406,7 @@ struct Q_QML_COMPILER_PRIVATE_EXPORT Pragma ListPropertyAssignBehavior, ComponentBehavior, FunctionSignatureBehavior, + NativeMethodBehavior, }; enum ListPropertyAssignBehaviorValue @@ -427,12 +428,19 @@ struct Q_QML_COMPILER_PRIVATE_EXPORT Pragma Enforced }; + enum NativeMethodBehaviorValue + { + AcceptThisObject, + RejectThisObject + }; + PragmaType type; union { ListPropertyAssignBehaviorValue listPropertyAssignBehavior; ComponentBehaviorValue componentBehavior; FunctionSignatureBehaviorValue functionSignatureBehavior; + NativeMethodBehaviorValue nativeMethodBehavior; }; QV4::CompiledData::Location location; diff --git a/src/qml/doc/src/cppintegration/exposecppattributes.qdoc b/src/qml/doc/src/cppintegration/exposecppattributes.qdoc index 477f17584c..e3b3288a98 100644 --- a/src/qml/doc/src/cppintegration/exposecppattributes.qdoc +++ b/src/qml/doc/src/cppintegration/exposecppattributes.qdoc @@ -448,6 +448,54 @@ be called according to the number and the types of arguments that are provided. Values returned from C++ methods are converted to JavaScript values when accessed from JavaScript expressions in QML. +\section2 C++ methods and the 'this' object + +You may want to retrieve a C++ method from one object and call it on a different +object. Consider the following example, within a QML module called \c{Example}: + +\table +\row +\li C++ +\li +\code +class Invokable : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + Invokable(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE void invoke() { qDebug() << "invoked on " << objectName(); } +}; +\endcode +\row +\li QML +\li +\qml +import QtQml +import Example + +Invokable { + objectName: "parent" + property Invokable child: Invokable {} + Component.onCompleted: child.invoke.call(this) +} +\endqml +\endtable + +If you load the QML code from a suitable main.cpp, it should print +"invoked on parent". However, due to a long standing bug, it doesn't. +Historically, the 'this' object of C++-based methods is inseparably bound to +the method. Changing this behavior for existing code would cause subtle errors +since the 'this' object is implicit in many places. Since Qt 6.5 you can +explicitly opt into the correct behavior and allow C++ methods to accept a +'this' object. To do so, add the following pragma to your QML documents: + +\qml +pragma NativeMethodBehavior: AcceptThisObject +\endqml + +With this line added, the example above will work as expected. \section1 Exposing Signals diff --git a/src/qml/jsruntime/qv4executablecompilationunit_p.h b/src/qml/jsruntime/qv4executablecompilationunit_p.h index bb99040b9a..0b27e68a20 100644 --- a/src/qml/jsruntime/qv4executablecompilationunit_p.h +++ b/src/qml/jsruntime/qv4executablecompilationunit_p.h @@ -171,6 +171,11 @@ public: return data->flags & CompiledData::Unit::FunctionSignaturesEnforced; } + bool nativeMethodsAcceptThisObjects() const + { + return data->flags & CompiledData::Unit::NativeMethodsAcceptThisObject; + } + int objectCount() const { return qmlData->nObjects; } const CompiledObject *objectAt(int index) const { diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index dd0abf818e..873262ea16 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -54,6 +54,7 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcBindingRemoval, "qt.qml.binding.removal", QtWarningMsg) Q_LOGGING_CATEGORY(lcObjectConnect, "qt.qml.object.connect", QtWarningMsg) Q_LOGGING_CATEGORY(lcOverloadResolution, "qt.qml.overloadresolution", QtWarningMsg) +Q_LOGGING_CATEGORY(lcMethodBehavior, "qt.qml.method.behavior") // The code in this file does not violate strict aliasing, but GCC thinks it does // so turn off the warnings for us to have a clean build @@ -2326,7 +2327,7 @@ void Heap::QObjectMethod::init(QV4::ExecutionContext *scope) Heap::FunctionObject::init(scope); } -const QMetaObject *Heap::QObjectMethod::metaObject() +const QMetaObject *Heap::QObjectMethod::metaObject() const { if (valueTypeWrapper) return valueTypeWrapper->metaObject(); @@ -2367,17 +2368,88 @@ bool Heap::QObjectMethod::isAttachedTo(QObject *o) const return true; } -void Heap::QObjectMethod::ensureMethodsCache(QObject *o) +Heap::QObjectMethod::ThisObjectMode Heap::QObjectMethod::checkThisObject( + const QMetaObject *thisMeta) const +{ + // Check that the metaobject matches. + + if (!thisMeta) { + // You can only get a detached method via a lookup, and then you have a thisObject. + Q_ASSERT(valueTypeWrapper || qObj); + return Included; + } + + const auto check = [&](const QMetaObject *included) { + const auto stackFrame = internalClass->engine->currentStackFrame; + if (stackFrame && !stackFrame->v4Function->executableCompilationUnit() + ->nativeMethodsAcceptThisObjects()) { + qCWarning(lcMethodBehavior, + "%s:%d: Calling C++ methods with 'this' objects different from the one " + "they were retrieved from is broken, due to historical reasons. The " + "original object is used as 'this' object. You can allow the given " + "'this' object to be used by setting " + "'pragma NativeMethodBehavior: AcceptThisObject'", + qPrintable(stackFrame->source()), stackFrame->lineNumber()); + return Included; + } + + // Find the base type the method belongs to. + int methodOffset = included->methodOffset(); + while (true) { + if (included == thisMeta) + return Explicit; + + if (methodOffset <= index) + return thisMeta->inherits(included) ? Explicit : Invalid; + + methodOffset -= QMetaObjectPrivate::get(included)->methodCount; + included = included->superClass(); + Q_ASSERT(included); + }; + + Q_UNREACHABLE_RETURN(Invalid); + }; + + if (QObject *o = object()) + return check(o->metaObject()); + + if (valueTypeWrapper) + return check(valueTypeWrapper->metaObject()); + + // If the QObjectMethod is detached, we can only have gotten here via a lookup. + // The lookup checks that the QQmlPropertyCache matches. + return Explicit; +} + +QString Heap::QObjectMethod::name() const +{ + if (index == QV4::QObjectMethod::DestroyMethod) + return QStringLiteral("destroy"); + else if (index == QV4::QObjectMethod::ToStringMethod) + return QStringLiteral("toString"); + + const QMetaObject *mo = metaObject(); + if (!mo) + return QString(); + + int methodOffset = mo->methodOffset(); + while (methodOffset > index) { + mo = mo->superClass(); + methodOffset -= QMetaObjectPrivate::get(mo)->methodCount; + } + + return QLatin1String("%1::%2").arg(mo->className(), mo->method(index).name()); +} + +void Heap::QObjectMethod::ensureMethodsCache(const QMetaObject *thisMeta) { if (methods) return; const QMetaObject *mo = metaObject(); - if (!mo) { - Q_ASSERT(o); - mo = o->metaObject(); - } + if (!mo) + mo = thisMeta; Q_ASSERT(mo); @@ -2420,29 +2492,20 @@ static QObject *qObject(const Value *v) return nullptr; } -ReturnedValue QObjectMethod::method_toString( - ExecutionEngine *engine, const QV4::Value *thisObject) const +ReturnedValue QObjectMethod::method_toString(ExecutionEngine *engine, QObject *o) const { - QObject *o = qObject(thisObject); - - d()->assertIntegrity(o); - - const QMetaObject *metaObject = d()->metaObject(); - if (!metaObject && o) - metaObject = o->metaObject(); - - return engine->newString(QObjectWrapper::objectToString( - engine, metaObject, o ? o : d()->object()))->asReturnedValue(); + return engine->newString( + QObjectWrapper::objectToString( + engine, o ? o->metaObject() : d()->metaObject(), o))->asReturnedValue(); } -ReturnedValue QObjectMethod::method_destroy(ExecutionEngine *engine, const QV4::Value *thisObject, const Value *args, int argc) const +ReturnedValue QObjectMethod::method_destroy( + ExecutionEngine *engine, QObject *o, const Value *args, int argc) const { - QObject *o = qObject(thisObject); - QObject *object = o ? o : d()->object(); - if (!object) + if (!o) return Encode::undefined(); - if (QQmlData::keepAliveDuringGarbageCollection(object)) + if (QQmlData::keepAliveDuringGarbageCollection(o)) return engine->throwError(QStringLiteral("Invalid attempt to destroy() an indestructible object")); int delay = 0; @@ -2450,9 +2513,9 @@ ReturnedValue QObjectMethod::method_destroy(ExecutionEngine *engine, const QV4:: delay = args[0].toUInt32(); if (delay > 0) - QTimer::singleShot(delay, object, SLOT(deleteLater())); + QTimer::singleShot(delay, o, SLOT(deleteLater())); else - object->deleteLater(); + o->deleteLater(); return Encode::undefined(); } @@ -2467,23 +2530,62 @@ ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value * { ExecutionEngine *v4 = engine(); - if (d()->index == DestroyMethod) - return method_destroy(v4, thisObject, argv, argc); - else if (d()->index == ToStringMethod) - return method_toString(v4, thisObject); + const QMetaObject *thisMeta = nullptr; QObject *o = qObject(thisObject); + Heap::QQmlValueTypeWrapper *wrapper = nullptr; + if (o) { + thisMeta = o->metaObject(); + } else if (const QQmlValueTypeWrapper *value = thisObject->as<QQmlValueTypeWrapper>()) { + wrapper = value->d(); + thisMeta = wrapper->metaObject(); + } + + Heap::QObjectMethod::ThisObjectMode mode = Heap::QObjectMethod::Invalid; + if (o && o == d()->object()) { + mode = Heap::QObjectMethod::Explicit; + // Nothing to do; objects are the same. This should be common + } else if (wrapper && wrapper == d()->valueTypeWrapper) { + mode = Heap::QObjectMethod::Explicit; + // Nothing to do; gadgets are the same. This should be somewhat common + } else { + mode = d()->checkThisObject(thisMeta); + if (mode == Heap::QObjectMethod::Invalid) { + v4->throwError(QLatin1String("Cannot call method %1 on %2").arg( + d()->name(), thisObject->toQStringNoThrow())); + return Encode::undefined(); + } + } + + QQmlObjectOrGadget object = [&](){ + if (mode == Heap::QObjectMethod::Included) { + if (QObject *qObject = d()->qObj) + return QQmlObjectOrGadget(qObject); - d()->assertIntegrity(o); - d()->ensureMethodsCache(o); + wrapper = d()->valueTypeWrapper; + Q_ASSERT(wrapper); + return QQmlObjectOrGadget(wrapper->metaObject(), wrapper->gadgetPtr()); + } else { + if (o) + return QQmlObjectOrGadget(o); - QQmlObjectOrGadget object = d()->valueTypeWrapper - ? QQmlObjectOrGadget(d()->metaObject(), d()->valueTypeWrapper->gadgetPtr()) - : QQmlObjectOrGadget(o ? o : d()->object()); + Q_ASSERT(wrapper); + if (!wrapper->enforcesLocation()) + QV4::ReferenceObject::readReference(wrapper); + return QQmlObjectOrGadget(thisMeta, wrapper->gadgetPtr()); + } + }(); if (object.isNull()) return Encode::undefined(); + if (d()->index == DestroyMethod) + return method_destroy(v4, object.qObject(), argv, argc); + else if (d()->index == ToStringMethod) + return method_toString(v4, object.qObject()); + + d()->ensureMethodsCache(thisMeta); + Scope scope(v4); JSCallData cData(thisObject, argv, argc); CallData *callData = cData.callData(scope); @@ -2494,7 +2596,7 @@ ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value * // The method might change the value. const auto doCall = [&](const auto &call) { if (!method->isConstant()) { - if (d()->valueTypeWrapper && d()->valueTypeWrapper->isReference()) { + if (wrapper && wrapper->isReference()) { ScopedValue rv(scope, call()); d()->valueTypeWrapper->writeBack(); return rv->asReturnedValue(); diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index d17b12ea4a..3f94cc7798 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -80,26 +80,23 @@ DECLARE_HEAP_OBJECT(QObjectMethod, FunctionObject) { FunctionObject::destroy(); } - void ensureMethodsCache(QObject *o); + void ensureMethodsCache(const QMetaObject *thisMeta); + QString name() const; - const QMetaObject *metaObject(); + const QMetaObject *metaObject() const; QObject *object() const { return qObj.data(); } void setObject(QObject *o) { qObj = o; } bool isDetached() const; bool isAttachedTo(QObject *o) const; - void assertIntegrity(QObject *thisObject) const - { - // This implies that the metaobject matches. - // If the QObjectMethod is detached, we can only have gotten here via a lookup. - // The lookup checks that the QQmlPropertyCache matches. - // We can also call methods on standalone value type wrappers, without an object. - if (thisObject) - Q_ASSERT(isDetached() || isAttachedTo(thisObject)); - else - Q_ASSERT(valueTypeWrapper || !isDetached()); - } + enum ThisObjectMode { + Invalid, + Included, + Explicit, + }; + + QV4::Heap::QObjectMethod::ThisObjectMode checkThisObject(const QMetaObject *thisMeta) const; }; struct QMetaObjectWrapper : FunctionObject { @@ -373,8 +370,9 @@ struct Q_QML_EXPORT QObjectMethod : public QV4::FunctionObject int methodIndex() const { return d()->index; } QObject *object() const { return d()->object(); } - QV4::ReturnedValue method_toString(QV4::ExecutionEngine *engine, const QV4::Value *thisObject) const; - QV4::ReturnedValue method_destroy(QV4::ExecutionEngine *ctx, const QV4::Value *thisObject, const Value *args, int argc) const; + QV4::ReturnedValue method_toString(QV4::ExecutionEngine *engine, QObject *o) const; + QV4::ReturnedValue method_destroy( + QV4::ExecutionEngine *ctx, QObject *o, const Value *args, int argc) const; static ReturnedValue virtualCall(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); diff --git a/src/qml/qml/qqmlirloader.cpp b/src/qml/qml/qqmlirloader.cpp index 59853a85e5..c80f9b4bd1 100644 --- a/src/qml/qml/qqmlirloader.cpp +++ b/src/qml/qml/qqmlirloader.cpp @@ -49,6 +49,12 @@ void QQmlIRLoader::load() createPragma(type)->functionSignatureBehavior = value; }; + const auto createNativeMethodPragma = [&]( + Pragma::PragmaType type, + Pragma::NativeMethodBehaviorValue value) { + createPragma(type)->nativeMethodBehavior = value; + }; + if (unit->flags & QV4::CompiledData::Unit::IsSingleton) createPragma(Pragma::Singleton); if (unit->flags & QV4::CompiledData::Unit::IsStrict) @@ -65,6 +71,9 @@ void QQmlIRLoader::load() if (unit->flags & QV4::CompiledData::Unit::FunctionSignaturesEnforced) createFunctionSignaturePragma(Pragma::FunctionSignatureBehavior, Pragma::Enforced); + if (unit->flags & QV4::CompiledData::Unit::NativeMethodsAcceptThisObject) + createNativeMethodPragma(Pragma::NativeMethodBehavior, Pragma::AcceptThisObject); + for (uint i = 0; i < qmlUnit->nObjects; ++i) { const QV4::CompiledData::Object *serializedObject = qmlUnit->objectAt(i); QmlIR::Object *object = loadObject(serializedObject); diff --git a/src/qml/qml/qqmlobjectorgadget_p.h b/src/qml/qml/qqmlobjectorgadget_p.h index ee7c8de65f..ddce295c9d 100644 --- a/src/qml/qml/qqmlobjectorgadget_p.h +++ b/src/qml/qml/qqmlobjectorgadget_p.h @@ -38,6 +38,7 @@ public: void metacall(QMetaObject::Call type, int index, void **argv) const; bool isNull() const { return ptr.isNull(); } + QObject *qObject() const { return ptr.isT1() ? ptr.asT1() : nullptr; } private: QBiPointer<QObject, void> ptr; diff --git a/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsAcceptThisObject.qml b/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsAcceptThisObject.qml new file mode 100644 index 0000000000..2af2e04352 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsAcceptThisObject.qml @@ -0,0 +1,33 @@ +pragma NativeMethodBehavior: AcceptThisObject +import ValueTypes +import StaticTest +import QtQml + +QtObject { + id: self + + property base v1 + property derived v2 + + property var valueTypeMethod: v1.report + + property var qtMethod: Qt.rect + + property rect badRect: self.qtMethod(1, 2, 3, 4) + property rect goodRect1: qtMethod.call(undefined, 1, 2, 3, 4) + property rect goodRect2: qtMethod.call(Qt, 1, 2, 3, 4) + + property string badString: self.valueTypeMethod() + property string goodString1: valueTypeMethod.call(undefined) + property string goodString2: valueTypeMethod.call(v1) + property string goodString3: valueTypeMethod.call(v2) + + property string goodString4: toString.call(Qt) + property string badString2: toString.call(goodRect1) + + property var mm: OriginalSingleton.mm + property int badInt: self.mm() + property int goodInt1: mm.call(undefined) + property int goodInt2: mm.call(OriginalSingleton) + property int goodInt3: mm.call(DerivedSingleton) +} diff --git a/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsRejectThisObject.qml b/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsRejectThisObject.qml new file mode 100644 index 0000000000..aab4bf8451 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/objectAndGadgetMethodCallsRejectThisObject.qml @@ -0,0 +1,32 @@ +import ValueTypes +import StaticTest +import QtQml + +QtObject { + id: self + + property base v1 + property derived v2 + + property var valueTypeMethod: v1.report + + property var qtMethod: Qt.rect + + property rect badRect: self.qtMethod(1, 2, 3, 4) + property rect goodRect1: qtMethod.call(undefined, 1, 2, 3, 4) + property rect goodRect2: qtMethod.call(Qt, 1, 2, 3, 4) + + property string badString: self.valueTypeMethod() + property string goodString1: valueTypeMethod.call(undefined) + property string goodString2: valueTypeMethod.call(v1) + property string goodString3: valueTypeMethod.call(v2) + + property string goodString4: toString.call(Qt) + property string badString2: toString.call(goodRect1) + + property var mm: OriginalSingleton.mm + property int badInt: self.mm() + property int goodInt1: mm.call(undefined) + property int goodInt2: mm.call(OriginalSingleton) + property int goodInt3: mm.call(DerivedSingleton) +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 9ba5132bb4..0777ed103d 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -2305,6 +2305,7 @@ signals: void abcChanged(const QString &); public: + Q_INVOKABLE int mm() { return 5; } QString abc() const { return m_abc; } void setAbc(const QString &abc) { diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index efe89f61ac..fee65fcb17 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -403,6 +403,9 @@ private slots: void nullIsNull(); void multiRequired(); + void objectAndGadgetMethodCallsRejectThisObject(); + void objectAndGadgetMethodCallsAcceptThisObject(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -7763,6 +7766,89 @@ void tst_qqmllanguage::multiRequired() qPrintable(url.toString() + ":5 Required property description was not initialized\n")); } +void tst_qqmllanguage::objectAndGadgetMethodCallsRejectThisObject() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("objectAndGadgetMethodCallsRejectThisObject.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + const QList<int> lines = { 15, 19, 21, 22, 24, 25, 28, 31 }; + for (int line : lines) { + const QString message + = ".*:%1: Calling C.. methods with 'this' objects different from the one " + "they were retrieved from is broken, due to historical reasons. The " + "original object is used as 'this' object. You can allow the given " + "'this' object to be used by setting " + "'pragma NativeMethodBehavior: AcceptThisObject'"_L1.arg(QString::number(line)); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(message)); + } + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("badRect"), QRectF(1, 2, 3, 4)); + QCOMPARE(o->property("goodRect1"), QRectF(1, 2, 3, 4)); + QCOMPARE(o->property("goodRect2"), QRectF(1, 2, 3, 4)); + + QCOMPARE(o->property("badString"), QStringLiteral("27")); + QCOMPARE(o->property("goodString1"), QStringLiteral("27")); + QCOMPARE(o->property("goodString2"), QStringLiteral("27")); + QCOMPARE(o->property("goodString3"), QStringLiteral("27")); + + QVERIFY(o->property("goodString4").value<QString>().startsWith("QObject_QML_"_L1)); + QVERIFY(o->property("badString2").value<QString>().startsWith("QObject_QML_"_L1)); + + QCOMPARE(o->property("badInt"), 5); + QCOMPARE(o->property("goodInt1"), 5); + QCOMPARE(o->property("goodInt2"), 5); + QCOMPARE(o->property("goodInt3"), 5); +} + +void tst_qqmllanguage::objectAndGadgetMethodCallsAcceptThisObject() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("objectAndGadgetMethodCallsAcceptThisObject.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression( + "objectAndGadgetMethodCallsAcceptThisObject.qml:16: Error: " + "Cannot call method QtObject::rect on QObject_QML_")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression( + "objectAndGadgetMethodCallsAcceptThisObject.qml:20: Error: " + "Cannot call method BaseValueType::report on QObject_QML_")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression( + "objectAndGadgetMethodCallsAcceptThisObject.qml:26: Error: " + "Cannot call method toString on QRectF")); + QTest::ignoreMessage( + QtWarningMsg, QRegularExpression( + "objectAndGadgetMethodCallsAcceptThisObject.qml:29: Error: " + "Cannot call method OriginalSingleton::mm on QObject_QML_")); + + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("badRect"), QRectF()); + QCOMPARE(o->property("goodRect1"), QRectF(1, 2, 3, 4)); + QCOMPARE(o->property("goodRect2"), QRectF(1, 2, 3, 4)); + + QCOMPARE(o->property("badString"), QString()); + QCOMPARE(o->property("goodString1"), QStringLiteral("27")); + QCOMPARE(o->property("goodString2"), QStringLiteral("27")); + QCOMPARE(o->property("goodString3"), QStringLiteral("28")); + + QVERIFY(o->property("goodString4").value<QString>().startsWith("QtObject"_L1)); + QCOMPARE(o->property("badString2"), QString()); + + QCOMPARE(o->property("badInt"), 0); + QCOMPARE(o->property("goodInt1"), 5); + QCOMPARE(o->property("goodInt2"), 5); + QCOMPARE(o->property("goodInt3"), 5); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |