diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2023-06-05 11:49:51 +0200 |
|---|---|---|
| committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2023-06-09 19:35:35 +0000 |
| commit | f7f9aa00aac0f994fa54097c0674280f99a277b8 (patch) | |
| tree | 6fb9a4c63bccf3ea51d2b57c8e18f00d85cca800 | |
| parent | 4178c6c6c1c8b231b1e254065c5e03f731e58fe7 (diff) | |
QtQml: Do not retroactively detach or re-attach methods
If a method is attached to an object it means that it was stored in a
property. The property can be re-assigned, with a different method from
a different thisObject. We cannot re-use it at all when invoking the
lookup again.
There are a few cases we care about:
1. The lookup immediately calls the property.
a, The lookup retrieves a method. The method will be detached from
the get go. There is no ambiguity (unless it's overridden).
b, The lookup retrieves a var property with a method inside. The
method will be attached to its original thisObject. The method may
have been replaced with something else in the mean time.
Therefore, we re-fetch the value on each call. Since we already
have a QV4::QObjectMethod in the property, we don't need to
rebuild it. So the lookup is still not expensive.
2. The lookup stores the property in a local.
a, The lookup retrieves a method. The method will be attached,
signaling any future callers that they need to be careful here.
b, The lookup retrieves a var property with a method inside. The
method is already attached to its original thisObject and stays
that way.
3. We call a local that contains a method. This can only be a method
that contains a thisObject. We may still decide to use the thisObject
as provided by the call, depending on the NativeMethodBehavior
pragma.
Fixes: QTBUG-114086
Change-Id: Ia9a9c06355fd7d5052cdf79ac3ffddfa32f56573
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit 3c0b8076e79f64b5ba575fca7ac62c6a1b740ce4)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
| -rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper_p.h | 10 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/data/retainThis.qml | 49 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.cpp | 2 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.h | 19 | ||||
| -rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 35 |
5 files changed, 107 insertions, 8 deletions
diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index 3f94cc7798..d5d214fa0f 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -328,14 +328,8 @@ inline ReturnedValue QObjectWrapper::lookupMethodGetterImpl( } if (Heap::QObjectMethod *method = lookup->qobjectMethodLookup.method) { - if (lookup->forCall && !method->isDetached()) { - method = lookup->qobjectMethodLookup.method - = cloneMethod(engine, method, nullptr, nullptr); - } else if (!lookup->forCall && !method->isAttachedTo(qobj)) { - method = lookup->qobjectMethodLookup.method - = cloneMethod(engine, method, This, qobj); - } - return method ? method->asReturnedValue() : revertLookup(); + if (method->isDetached()) + return method->asReturnedValue(); } if (!property) // was toString() or destroy() diff --git a/tests/auto/qml/qqmllanguage/data/retainThis.qml b/tests/auto/qml/qqmllanguage/data/retainThis.qml new file mode 100644 index 0000000000..7a372ee236 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/retainThis.qml @@ -0,0 +1,49 @@ +import QmlOtherThis +import QtQml + +QtObject { + property var cppMethod: objA.greet + property var cppMethod2: objA.sum + + property Greeter a: Greeter { id: objA; objectName: "objA" } + property Greeter b: Greeter { id: objB; objectName: "objB" } + + function doCall() { + cppMethod.call(objB) + cppMethod2(5, 6) + } + + property var cppMethod3; + function doRetrieve(g) { + cppMethod3 = g.greet; + } + + function doCall2() { + cppMethod3(); + } + + property Greeter c: Greeter { + id: objC + objectName: "objC" + + property var cppMethod: objC.sum + + function doCall() { + cppMethod(7, 7) + } + } + + Component.onCompleted: { + doCall(); + doCall(); + + doRetrieve(objA); + doCall2(); + doRetrieve(objB); + doCall2(); + + objC.doCall(); + objC.cppMethod = objB.sum; + objC.doCall(); + } +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index 3fc9fd7f16..03265df71e 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -152,6 +152,8 @@ void registerTypes() QMetaType::registerConverter<UnregisteredValueDerivedType, UnregisteredValueBaseType>(); qmlRegisterTypesAndRevisions<UnregisteredValueTypeHandler>("Test", 1); + + qmlRegisterTypesAndRevisions<Greeter>("QmlOtherThis", 1); } QVariant myCustomVariantTypeConverter(const QString &data) diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 71f936160d..b031572acc 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -2460,4 +2460,23 @@ public slots: void consume(GadgetedValueBaseType) { ++gadgeted; } }; +class Greeter : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + Greeter(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE void greet() + { + qDebug().noquote() << objectName() << "says hello"; + } + + Q_INVOKABLE void sum(int a, int b) + { + qDebug().noquote() << objectName() << QString("says %1 + %2 = %3").arg(a).arg(b).arg(a + b); + } +}; + #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 44b9c2d224..bb26142d77 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -411,6 +411,7 @@ private slots: void objectMethodClone(); void unregisteredValueTypeConversion(); + void retainThis(); private: QQmlEngine engine; @@ -7924,6 +7925,40 @@ void tst_qqmllanguage::unregisteredValueTypeConversion() QCOMPARE(handler->gadgeted, 1); } +void tst_qqmllanguage::retainThis() +{ + QQmlEngine e; + const QUrl url = testFileUrl("retainThis.qml"); + QQmlComponent c(&e, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + const QString warning = u"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'"_s; + + // Both cases objA because we retain the thisObject. + for (int i = 0; i < 2; ++i) { + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":12: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objA says hello"); + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":13: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objA says 5 + 6 = 11"); + } + + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":22: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objA says hello"); + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":22: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objB says hello"); + + QTest::ignoreMessage(QtDebugMsg, "objC says 7 + 7 = 14"); + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":32: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objB says 7 + 7 = 14"); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |
