File 0001-QtQml-Do-not-clear-objects-propertyCaches-on-last-GC.patch of Package qt6-declarative

From 1327fce7e500e3ae2e96ab89eaad7f1727ea77b1 Mon Sep 17 00:00:00 2001
From: Ulf Hermann <ulf.hermann@qt.io>
Date: Tue, 13 Jan 2026 11:53:27 +0100
Subject: [PATCH] QtQml: Do not clear objects' propertyCaches on last GC run

The property caches are not specific to the engine. The same object may
be exposed to other engines and still require its property cache. When
the clearing of the property caches on engine destruction was
introduced, the property caches were still engine-specific and we had no
choice but to clear them. Otherwise any further access would lead to a
dereference of a dangling pointer.

Furthermore, when clearing the JS wrapper for a QObject, check if it's
actually the wrapper being deleted. We don't want to clear some other
engine's wrapper.

Amends commit 749a7212e903d8e8c6f256edb1836b9449cc7fe1.
Amends commit c6b2dd879d02b21b18f80faab541f8f04286685a.

Fixes: QTBUG-142514
Change-Id: I40bb1aeca65225d56cb1d2ff498f5f1722216a70
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit dc2358e98b8ddab532866a403ffc09d1162ad0f9)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit abd054a491b5f443193033d451dc17e09841277a)
---
 src/qml/jsruntime/qv4qobjectwrapper.cpp       |  7 +++----
 tests/auto/qml/qjsengine/tst_qjsengine.cpp    |  2 +-
 .../qml/qmlcppcodegen/data/CMakeLists.txt     |  1 +
 .../data/multiEnginePropertyCache.qml         | 20 ++++++++++++++++++
 .../qml/qmlcppcodegen/tst_qmlcppcodegen.cpp   | 21 +++++++++++++++++++
 5 files changed, 46 insertions(+), 5 deletions(-)
 create mode 100644 tests/auto/qml/qmlcppcodegen/data/multiEnginePropertyCache.qml

diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp
index bb3d9b1c32..6887d86f4c 100644
--- a/src/qml/jsruntime/qv4qobjectwrapper.cpp
+++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp
@@ -1583,10 +1583,9 @@ void QObjectWrapper::destroyObject(bool lastCall)
                     o->deleteLater();
             } else {
                 // If the object is C++-owned, we still have to release the weak reference we have
-                // to it.
-                ddata->jsWrapper.clear();
-                if (lastCall && ddata->propertyCache)
-                    ddata->propertyCache.reset();
+                // to it. If the "main" wrapper is not ours, we should leave it alone, though.
+                if (ddata->jsWrapper.as<QObjectWrapper>() == this)
+                    ddata->jsWrapper.clear();
             }
         }
     }
diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp
index 2f855213ba..4c1e7e5a86 100644
--- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp
+++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp
@@ -1116,7 +1116,7 @@ void tst_QJSEngine::newQObjectPropertyCache()
         engine.newQObject(obj.data());
         QVERIFY(QQmlData::get(obj.data())->propertyCache);
     }
-    QVERIFY(!QQmlData::get(obj.data())->propertyCache);
+    QVERIFY(QQmlData::get(obj.data())->propertyCache);
 }
 
 void tst_QJSEngine::newQMetaObject() {
diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
index 88cc102509..8949d007e4 100644
--- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
+++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
@@ -326,6 +326,7 @@ set(qml_files
     stringLength.qml
     stringToByteArray.qml
     structuredValueType.qml
+    multiEnginePropertyCache.qml
     takenumber.qml
     testlogger.js
     text.qml
diff --git a/tests/auto/qml/qmlcppcodegen/data/multiEnginePropertyCache.qml b/tests/auto/qml/qmlcppcodegen/data/multiEnginePropertyCache.qml
new file mode 100644
index 0000000000..222b2f84de
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/multiEnginePropertyCache.qml
@@ -0,0 +1,20 @@
+pragma Strict
+import QtQml
+
+QtObject {
+    id: root
+
+    property int foo: 0
+    onFooChanged: root.close1()
+
+    property int bar: 0
+    onBarChanged: close2()
+
+    function close1() {
+        console.log("close1")
+    }
+
+    function close2() {
+        console.log("close2")
+    }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
index 46696acacf..d602f00f0b 100644
--- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
+++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
@@ -280,6 +280,7 @@ private slots:
     void stringLength();
     void stringToByteArray();
     void structuredValueType();
+    void multiEnginePropertyCache();
     void takeNumbers();
     void takeNumbers_data();
     void testIsnan();
@@ -5794,6 +5795,26 @@ void tst_QmlCppCodegen::structuredValueType()
     QCOMPARE(o->property("w2").value<WeatherModelUrl>(), w2);
 }
 
+void tst_QmlCppCodegen::multiEnginePropertyCache()
+{
+    QQmlEngine engine;
+    QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/multiEnginePropertyCache.qml"_s));
+    QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+    std::unique_ptr<QObject> o(c.create());
+    QVERIFY(o);
+
+    {
+        QJSEngine other;
+        other.newQObject(o.get());
+    }
+
+    QTest::ignoreMessage(QtDebugMsg, "close1");
+    o->setProperty("foo", 1);
+
+    QTest::ignoreMessage(QtDebugMsg, "close2");
+    o->setProperty("bar", 2);
+}
+
 void tst_QmlCppCodegen::takeNumbers()
 {
     QFETCH(QByteArray, method);
-- 
2.53.0

openSUSE Build Service is sponsored by