From 9c6b2b78e9076f1c2676aa0c41573db9ca480654 Mon Sep 17 00:00:00 2001
From: Ulf Hermann <ulf.hermann@qt.io>
Date: Tue, 2 Dec 2025 17:42:30 +0100
Subject: QtQml: Invalidate fallback lookups after each call from AOT code
Fallback property lookups are created for completely dynamic
metaobjects. Anything about them may change between any two calls.
Pick-to: 6.8 6.5
Fixes: QTBUG-142331
Change-Id: Ib732c37a6f27ab8105bea0eeae000af7eb9c36d7
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
(cherry picked from commit 9af6d2d6d0046b3c8369e15eb4791957cdc7ab7b)
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
---
qtdeclarative/src/qml/jsruntime/qv4lookup_p.h | 4 ++
qtdeclarative/src/qml/qml/qqml.cpp | 13 +++++--
qtdeclarative/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt | 2 +
qtdeclarative/tests/auto/qml/qmlcppcodegen/data/propertyMap.qml | 6 +++
qtdeclarative/tests/auto/qml/qmlcppcodegen/data/propertymap.h | 40 ++++++++++++++++++++
qtdeclarative/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 43 ++++++++++++++++++++++
6 files changed, 104 insertions(+), 4 deletions(-)
create mode 100644 qtdeclarative/tests/auto/qml/qmlcppcodegen/data/propertyMap.qml
create mode 100644 qtdeclarative/tests/auto/qml/qmlcppcodegen/data/propertymap.h
diff --git a/qtdeclarative/src/qml/jsruntime/qv4lookup_p.h b/qtdeclarative/src/qml/jsruntime/qv4lookup_p.h
index 083c3ec2df..ef36bf67c5 100644
|
a
|
b
|
struct Q_QML_EXPORT Lookup {
|
| 159 | 159 | const QQmlPropertyData *propertyData; |
| 160 | 160 | } qobjectMethodLookup; |
| 161 | 161 | struct { |
| | 162 | // NB: None of this is actually cache-able. The metaobject may change at any time. |
| | 163 | // We invalidate this data every time the lookup is invoked and thereby force a |
| | 164 | // re-initialization next time. |
| | 165 | |
| 162 | 166 | quintptr isConstant; // This is a bool, encoded as 0 or 1. Both values are ignored by gc |
| 163 | 167 | quintptr metaObject; // a (const QMetaObject* & 1) or nullptr |
| 164 | 168 | int coreIndex; |
diff --git a/qtdeclarative/src/qml/qml/qqml.cpp b/qtdeclarative/src/qml/qml/qqml.cpp
index 4e3b4fcf1e..3f0d9e332b 100644
|
a
|
b
|
struct FallbackPropertyQmlData
|
| 1386 | 1386 | |
| 1387 | 1387 | static FallbackPropertyQmlData findFallbackPropertyQmlData(QV4::Lookup *lookup, QObject *object) |
| 1388 | 1388 | { |
| | 1389 | // We've just initialized the lookup. So everything must be fine here. |
| | 1390 | |
| 1389 | 1391 | QQmlData *qmlData = QQmlData::get(object); |
| 1390 | | if (qmlData && qmlData->isQueuedForDeletion) |
| 1391 | | return {qmlData, nullptr, PropertyResult::Deleted}; |
| 1392 | 1392 | |
| | 1393 | Q_ASSERT(!qmlData || !qmlData->isQueuedForDeletion); |
| 1393 | 1394 | Q_ASSERT(!QQmlData::wasDeleted(object)); |
| 1394 | 1395 | |
| 1395 | 1396 | const QMetaObject *metaObject |
| 1396 | 1397 | = reinterpret_cast<const QMetaObject *>(lookup->qobjectFallbackLookup.metaObject - 1); |
| 1397 | | if (!metaObject || metaObject != object->metaObject()) |
| 1398 | | return {qmlData, nullptr, PropertyResult::NeedsInit}; |
| | 1398 | Q_ASSERT(metaObject == object->metaObject()); |
| 1399 | 1399 | |
| 1400 | 1400 | return {qmlData, metaObject, PropertyResult::OK}; |
| 1401 | 1401 | } |
| … |
… |
bool AOTCompiledContext::loadScopeObjectPropertyLookup(uint index, void *target)
|
| 2585 | 2585 | break; |
| 2586 | 2586 | case QV4::Lookup::Call::ContextGetterScopeObjectPropertyFallback: |
| 2587 | 2587 | result = loadFallbackProperty(lookup, qmlScopeObject, target, this); |
| | 2588 | lookup->call = QV4::Lookup::Call::ContextGetterGeneric; |
| 2588 | 2589 | break; |
| 2589 | 2590 | default: |
| 2590 | 2591 | return false; |
| … |
… |
bool AOTCompiledContext::writeBackScopeObjectPropertyLookup(uint index, void *so
|
| 2616 | 2617 | break; |
| 2617 | 2618 | case QV4::Lookup::Call::ContextGetterScopeObjectPropertyFallback: |
| 2618 | 2619 | result = writeBackFallbackProperty(lookup, qmlScopeObject, source); |
| | 2620 | lookup->call = QV4::Lookup::Call::ContextGetterGeneric; |
| 2619 | 2621 | break; |
| 2620 | 2622 | default: |
| 2621 | 2623 | return false; |
| … |
… |
bool AOTCompiledContext::getObjectLookup(uint index, QObject *object, void *targ
|
| 2816 | 2818 | result = lookup->asVariant |
| 2817 | 2819 | ? loadFallbackAsVariant(lookup, object, target, this) |
| 2818 | 2820 | : loadFallbackProperty(lookup, object, target, this); |
| | 2821 | lookup->call = QV4::Lookup::Call::GetterGeneric; |
| 2819 | 2822 | break; |
| 2820 | 2823 | default: |
| 2821 | 2824 | return false; |
| … |
… |
bool AOTCompiledContext::writeBackObjectLookup(uint index, QObject *object, void
|
| 2850 | 2853 | result = lookup->asVariant |
| 2851 | 2854 | ? writeBackFallbackAsVariant(lookup, object, source) |
| 2852 | 2855 | : writeBackFallbackProperty(lookup, object, source); |
| | 2856 | lookup->call = QV4::Lookup::Call::GetterGeneric; |
| 2853 | 2857 | break; |
| 2854 | 2858 | default: |
| 2855 | 2859 | return false; |
| … |
… |
bool AOTCompiledContext::setObjectLookup(uint index, QObject *object, void *valu
|
| 3010 | 3014 | result = lookup->asVariant |
| 3011 | 3015 | ? storeFallbackAsVariant(engine->handle(), lookup, object, value) |
| 3012 | 3016 | : storeFallbackProperty(lookup, object, value); |
| | 3017 | lookup->call = QV4::Lookup::Call::SetterGeneric; |
| 3013 | 3018 | break; |
| 3014 | 3019 | default: |
| 3015 | 3020 | return false; |
diff --git a/qtdeclarative/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/qtdeclarative/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
index 79e908c967..67cdefa30d 100644
|
a
|
b
|
set(cpp_sources
|
| 26 | 26 | multiforeign.h |
| 27 | 27 | objectwithmethod.h |
| 28 | 28 | person.cpp person.h |
| | 29 | propertymap.h |
| 29 | 30 | qmlusing.h |
| 30 | 31 | recursiveObject.h |
| 31 | 32 | refuseWrite.h |
| … |
… |
set(qml_files
|
| 282 | 283 | popContextAfterRet.qml |
| 283 | 284 | prefixedMetaType.qml |
| 284 | 285 | pressAndHoldButton.qml |
| | 286 | propertyMap.qml |
| 285 | 287 | qmlUsing.qml |
| 286 | 288 | qtbug113150.qml |
| 287 | 289 | qtfont.qml |
diff --git a/qtdeclarative/tests/auto/qml/qmlcppcodegen/data/propertyMap.qml b/qtdeclarative/tests/auto/qml/qmlcppcodegen/data/propertyMap.qml
new file mode 100644
index 0000000000..c00f3972e8
|
-
|
+
|
|
| | 1 | pragma Strict |
| | 2 | import TestTypes |
| | 3 | |
| | 4 | WithPropertyMap { |
| | 5 | objectName: map.foo |
| | 6 | } |
diff --git a/qtdeclarative/tests/auto/qml/qmlcppcodegen/data/propertymap.h b/qtdeclarative/tests/auto/qml/qmlcppcodegen/data/propertymap.h
new file mode 100644
index 0000000000..64d84c5c09
|
-
|
+
|
|
| | 1 | // Copyright (C) 2025 The Qt Company Ltd. |
| | 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| | 3 | |
| | 4 | #ifndef PROPERTYMAP_H |
| | 5 | #define PROPERTYMAP_H |
| | 6 | |
| | 7 | #include <QtCore/qobject.h> |
| | 8 | #include <QtQml/qqml.h> |
| | 9 | #include <QtQml/qqmlpropertymap.h> |
| | 10 | |
| | 11 | class WithPropertyMap : public QObject |
| | 12 | { |
| | 13 | Q_OBJECT |
| | 14 | QML_ELEMENT |
| | 15 | Q_PROPERTY(QQmlPropertyMap *map READ map NOTIFY mapChanged) |
| | 16 | public: |
| | 17 | WithPropertyMap(QObject *parent = nullptr) |
| | 18 | : QObject(parent) |
| | 19 | , m_map(new QQmlPropertyMap(this)) |
| | 20 | { |
| | 21 | } |
| | 22 | |
| | 23 | QQmlPropertyMap *map() const { return m_map; } |
| | 24 | |
| | 25 | void setProperties(const QVariantHash &properties) |
| | 26 | { |
| | 27 | delete m_map; |
| | 28 | m_map = new QQmlPropertyMap(this); |
| | 29 | m_map->insert(properties); |
| | 30 | emit mapChanged(); |
| | 31 | } |
| | 32 | |
| | 33 | signals: |
| | 34 | void mapChanged(); |
| | 35 | |
| | 36 | private: |
| | 37 | QQmlPropertyMap *m_map = nullptr; |
| | 38 | }; |
| | 39 | |
| | 40 | #endif // PROPERTYMAP_H |
diff --git a/qtdeclarative/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/qtdeclarative/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
index 70c50b457a..a90e2a6050 100644
|
a
|
b
|
|
| 10 | 10 | #include <data/getOptionalLookup.h> |
| 11 | 11 | #include <data/listprovider.h> |
| 12 | 12 | #include <data/objectwithmethod.h> |
| | 13 | #include <data/propertymap.h> |
| 13 | 14 | #include <data/qmlusing.h> |
| 14 | 15 | #include <data/refuseWrite.h> |
| 15 | 16 | #include <data/resettable.h> |
| … |
… |
private slots:
|
| 237 | 238 | void parentProperty(); |
| 238 | 239 | void popContextAfterRet(); |
| 239 | 240 | void prefixedType(); |
| | 241 | void propertyMap(); |
| 240 | 242 | void propertyOfParent(); |
| 241 | 243 | void qmlUsing(); |
| 242 | 244 | void qtfont(); |
| … |
… |
void tst_QmlCppCodegen::prefixedType()
|
| 4908 | 4910 | QCOMPARE(o->property("countH").toInt(), 11); |
| 4909 | 4911 | } |
| 4910 | 4912 | |
| | 4913 | void tst_QmlCppCodegen::propertyMap() |
| | 4914 | { |
| | 4915 | QQmlEngine engine; |
| | 4916 | |
| | 4917 | const QUrl document(u"qrc:/qt/qml/TestTypes/propertyMap.qml"_s); |
| | 4918 | QQmlComponent c(&engine, document); |
| | 4919 | QVERIFY2(c.isReady(), qPrintable(c.errorString())); |
| | 4920 | |
| | 4921 | QTest::ignoreMessage( |
| | 4922 | QtWarningMsg, qPrintable( |
| | 4923 | document.toString() |
| | 4924 | + u":5:5: QML WithPropertyMap: Unable to assign [undefined] to \"objectName\"")); |
| | 4925 | |
| | 4926 | QScopedPointer<QObject> o(c.create()); |
| | 4927 | QVERIFY(o); |
| | 4928 | |
| | 4929 | WithPropertyMap *w = qobject_cast<WithPropertyMap *>(o.data()); |
| | 4930 | QVERIFY(w); |
| | 4931 | |
| | 4932 | QVERIFY(w->objectName().isEmpty()); |
| | 4933 | |
| | 4934 | w->setProperties({ |
| | 4935 | { u"foo"_s, u"aaa"_s }, |
| | 4936 | { u"bar"_s, u"bbb"_s }, |
| | 4937 | }); |
| | 4938 | |
| | 4939 | QCOMPARE(w->objectName(), u"aaa"_s); |
| | 4940 | |
| | 4941 | w->setProperties({ |
| | 4942 | { u"foo"_s, u"ccc"_s }, |
| | 4943 | }); |
| | 4944 | |
| | 4945 | QCOMPARE(w->objectName(), u"ccc"_s); |
| | 4946 | |
| | 4947 | w->setProperties({ |
| | 4948 | { u"foo"_s, 24.25 }, |
| | 4949 | }); |
| | 4950 | |
| | 4951 | QCOMPARE(w->objectName(), u"24.25"_s); |
| | 4952 | } |
| | 4953 | |
| 4911 | 4954 | void tst_QmlCppCodegen::propertyOfParent() |
| 4912 | 4955 | { |
| 4913 | 4956 | QQmlEngine engine; |