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>
---
 src/qml/jsruntime/qv4lookup_p.h                    |  4 ++
 src/qml/qml/qqml.cpp                               | 13 +++++--
 tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt   |  2 +
 tests/auto/qml/qmlcppcodegen/data/propertyMap.qml  |  6 +++
 tests/auto/qml/qmlcppcodegen/data/propertymap.h    | 40 ++++++++++++++++++++
 tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 43 ++++++++++++++++++++++
 6 files changed, 104 insertions(+), 4 deletions(-)
 create mode 100644 tests/auto/qml/qmlcppcodegen/data/propertyMap.qml
 create mode 100644 tests/auto/qml/qmlcppcodegen/data/propertymap.h

diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h
index 083c3ec2df..ef36bf67c5 100644
--- a/src/qml/jsruntime/qv4lookup_p.h
+++ b/src/qml/jsruntime/qv4lookup_p.h
@@ -159,6 +159,10 @@ struct Q_QML_EXPORT Lookup {
             const QQmlPropertyData *propertyData;
         } qobjectMethodLookup;
         struct {
+            // NB: None of this is actually cache-able. The metaobject may change at any time.
+            //     We invalidate this data every time the lookup is invoked and thereby force a
+            //     re-initialization next time.
+
             quintptr isConstant; // This is a bool, encoded as 0 or 1. Both values are ignored by gc
             quintptr metaObject; // a (const QMetaObject* & 1) or nullptr
             int coreIndex;
diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp
index 4e3b4fcf1e..3f0d9e332b 100644
--- a/src/qml/qml/qqml.cpp
+++ b/src/qml/qml/qqml.cpp
@@ -1386,16 +1386,16 @@ struct FallbackPropertyQmlData
 
 static FallbackPropertyQmlData findFallbackPropertyQmlData(QV4::Lookup *lookup, QObject *object)
 {
+    // We've just initialized the lookup. So everything must be fine here.
+
     QQmlData *qmlData = QQmlData::get(object);
-    if (qmlData && qmlData->isQueuedForDeletion)
-        return {qmlData, nullptr, PropertyResult::Deleted};
 
+    Q_ASSERT(!qmlData || !qmlData->isQueuedForDeletion);
     Q_ASSERT(!QQmlData::wasDeleted(object));
 
     const QMetaObject *metaObject
             = reinterpret_cast<const QMetaObject *>(lookup->qobjectFallbackLookup.metaObject - 1);
-    if (!metaObject || metaObject != object->metaObject())
-        return {qmlData, nullptr, PropertyResult::NeedsInit};
+    Q_ASSERT(metaObject == object->metaObject());
 
     return {qmlData, metaObject, PropertyResult::OK};
 }
@@ -2585,6 +2585,7 @@ bool AOTCompiledContext::loadScopeObjectPropertyLookup(uint index, void *target)
         break;
     case QV4::Lookup::Call::ContextGetterScopeObjectPropertyFallback:
         result = loadFallbackProperty(lookup, qmlScopeObject, target, this);
+        lookup->call = QV4::Lookup::Call::ContextGetterGeneric;
         break;
     default:
         return false;
@@ -2616,6 +2617,7 @@ bool AOTCompiledContext::writeBackScopeObjectPropertyLookup(uint index, void *so
         break;
     case QV4::Lookup::Call::ContextGetterScopeObjectPropertyFallback:
         result = writeBackFallbackProperty(lookup, qmlScopeObject, source);
+        lookup->call = QV4::Lookup::Call::ContextGetterGeneric;
         break;
     default:
         return false;
@@ -2816,6 +2818,7 @@ bool AOTCompiledContext::getObjectLookup(uint index, QObject *object, void *targ
         result = lookup->asVariant
                 ? loadFallbackAsVariant(lookup, object, target, this)
                 : loadFallbackProperty(lookup, object, target, this);
+        lookup->call = QV4::Lookup::Call::GetterGeneric;
         break;
     default:
         return false;
@@ -2850,6 +2853,7 @@ bool AOTCompiledContext::writeBackObjectLookup(uint index, QObject *object, void
         result = lookup->asVariant
                 ? writeBackFallbackAsVariant(lookup, object, source)
                 : writeBackFallbackProperty(lookup, object, source);
+        lookup->call = QV4::Lookup::Call::GetterGeneric;
         break;
     default:
         return false;
@@ -3010,6 +3014,7 @@ bool AOTCompiledContext::setObjectLookup(uint index, QObject *object, void *valu
         result = lookup->asVariant
                 ? storeFallbackAsVariant(engine->handle(), lookup, object, value)
                 : storeFallbackProperty(lookup, object, value);
+        lookup->call = QV4::Lookup::Call::SetterGeneric;
         break;
     default:
         return false;
diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
index 79e908c967..67cdefa30d 100644
--- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
+++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
@@ -26,6 +26,7 @@ set(cpp_sources
     multiforeign.h
     objectwithmethod.h
     person.cpp person.h
+    propertymap.h
     qmlusing.h
     recursiveObject.h
     refuseWrite.h
@@ -282,6 +283,7 @@ set(qml_files
     popContextAfterRet.qml
     prefixedMetaType.qml
     pressAndHoldButton.qml
+    propertyMap.qml
     qmlUsing.qml
     qtbug113150.qml
     qtfont.qml
diff --git a/tests/auto/qml/qmlcppcodegen/data/propertyMap.qml b/tests/auto/qml/qmlcppcodegen/data/propertyMap.qml
new file mode 100644
index 0000000000..c00f3972e8
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/propertyMap.qml
@@ -0,0 +1,6 @@
+pragma Strict
+import TestTypes
+
+WithPropertyMap {
+    objectName: map.foo
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/propertymap.h b/tests/auto/qml/qmlcppcodegen/data/propertymap.h
new file mode 100644
index 0000000000..64d84c5c09
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/propertymap.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef PROPERTYMAP_H
+#define PROPERTYMAP_H
+
+#include <QtCore/qobject.h>
+#include <QtQml/qqml.h>
+#include <QtQml/qqmlpropertymap.h>
+
+class WithPropertyMap : public QObject
+{
+    Q_OBJECT
+    QML_ELEMENT
+    Q_PROPERTY(QQmlPropertyMap *map READ map NOTIFY mapChanged)
+public:
+    WithPropertyMap(QObject *parent = nullptr)
+        : QObject(parent)
+        , m_map(new QQmlPropertyMap(this))
+    {
+    }
+
+    QQmlPropertyMap *map() const { return m_map; }
+
+    void setProperties(const QVariantHash &properties)
+    {
+        delete m_map;
+        m_map = new QQmlPropertyMap(this);
+        m_map->insert(properties);
+        emit mapChanged();
+    }
+
+signals:
+    void mapChanged();
+
+private:
+    QQmlPropertyMap *m_map = nullptr;
+};
+
+#endif // PROPERTYMAP_H
diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
index 70c50b457a..a90e2a6050 100644
--- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
+++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
@@ -10,6 +10,7 @@
 #include <data/getOptionalLookup.h>
 #include <data/listprovider.h>
 #include <data/objectwithmethod.h>
+#include <data/propertymap.h>
 #include <data/qmlusing.h>
 #include <data/refuseWrite.h>
 #include <data/resettable.h>
@@ -237,6 +238,7 @@ private slots:
     void parentProperty();
     void popContextAfterRet();
     void prefixedType();
+    void propertyMap();
     void propertyOfParent();
     void qmlUsing();
     void qtfont();
@@ -4908,6 +4910,47 @@ void tst_QmlCppCodegen::prefixedType()
     QCOMPARE(o->property("countH").toInt(), 11);
 }
 
+void tst_QmlCppCodegen::propertyMap()
+{
+    QQmlEngine engine;
+
+    const QUrl document(u"qrc:/qt/qml/TestTypes/propertyMap.qml"_s);
+    QQmlComponent c(&engine, document);
+    QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+    QTest::ignoreMessage(
+            QtWarningMsg, qPrintable(
+                document.toString()
+                + u":5:5: QML WithPropertyMap: Unable to assign [undefined] to \"objectName\""));
+
+    QScopedPointer<QObject> o(c.create());
+    QVERIFY(o);
+
+    WithPropertyMap *w = qobject_cast<WithPropertyMap *>(o.data());
+    QVERIFY(w);
+
+    QVERIFY(w->objectName().isEmpty());
+
+    w->setProperties({
+        { u"foo"_s, u"aaa"_s },
+        { u"bar"_s, u"bbb"_s },
+    });
+
+    QCOMPARE(w->objectName(), u"aaa"_s);
+
+    w->setProperties({
+        { u"foo"_s, u"ccc"_s },
+    });
+
+    QCOMPARE(w->objectName(), u"ccc"_s);
+
+    w->setProperties({
+        { u"foo"_s, 24.25 },
+    });
+
+    QCOMPARE(w->objectName(), u"24.25"_s);
+}
+
 void tst_QmlCppCodegen::propertyOfParent()
 {
     QQmlEngine engine;
-- 
cgit v1.2.3

