aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-06-05 11:49:51 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2023-06-09 19:35:35 +0000
commitf7f9aa00aac0f994fa54097c0674280f99a277b8 (patch)
tree6fb9a4c63bccf3ea51d2b57c8e18f00d85cca800
parent4178c6c6c1c8b231b1e254065c5e03f731e58fe7 (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.h10
-rw-r--r--tests/auto/qml/qqmllanguage/data/retainThis.qml49
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.cpp2
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.h19
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp35
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"