[neon/qt6/qt6-declarative/Neon/release_noble] debian/patches: backport as per nicofee's post to kde-distro's
Carlos De Maine
null at kde.org
Wed Sep 30 03:07:26 BST 2026
Git commit 282ce0cf85384a7ec29442bce444ee33fca5e64b by Carlos De Maine.
Committed on 30/09/2026 at 02:07.
Pushed by carlosdem into branch 'Neon/release_noble'.
backport as per nicofee's post to kde-distro's
https://bugs.kde.org/show_bug.cgi?id=508377
A +330 -0 debian/patches/2efb7c6.diff
M +1 -0 debian/patches/series
https://invent.kde.org/neon/qt6/qt6-declarative/-/commit/282ce0cf85384a7ec29442bce444ee33fca5e64b
diff --git a/debian/patches/2efb7c6.diff b/debian/patches/2efb7c6.diff
new file mode 100644
index 0000000..d78545a
--- /dev/null
+++ b/debian/patches/2efb7c6.diff
@@ -0,0 +1,330 @@
+From 2efb7c60ef45952cc8e04b9c9a07965d14c30446 Mon Sep 17 00:00:00 2001
+From: Fabian Kosmale <fabian.kosmale at qt.io>
+Date: Mon, 31 Aug 2026 16:54:58 +0200
+Subject: [PATCH] QML engine: Correctly compare composites when multiple engines are used
+
+Since 6.11, we allow multiple types to be active for the same URL.
+While we converted multiple places to handle this, we missed adjusting
+the property validator. There, we just picked the last inserted property
+cache, which can lead to validation failures.
+
+Fix this by adding a fallback path which checks all potential matches
+when the initial lookup would yield an error.
+
+The same kind of check is also needed in qmlobject_can_qml_cast.
+
+Amends 9cc23ca91c404f24fff3c36e8c9425ae674f341d.
+
+Change compared to 6.12:
+Dropped the isComposite check, which doesn't exist there. That causes
+one potential extra unneeded lock acquisition, but only in a case
+where'd we get an error anyway.
+
+Fixes: QTBUG-149607
+Change-Id: Ibd0df2325807e75b23056ebc0bacdfe877b002f3
+Reviewed-by: Ulf Hermann <ulf.hermann at qt.io>
+(cherry picked from commit 94a76ed58629932f6d689c48ecf8d3132188923b)
+Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot at qt-project.org>
+(cherry picked from commit 8c23f65ec4889f9ff7f609002a129698796de2b8)
+---
+
+diff --git a/src/qml/qml/qqmlglobal.cpp b/src/qml/qml/qqmlglobal.cpp
+index 3e11b86..e4d94e4 100644
+--- a/src/qml/qml/qqmlglobal.cpp
++++ b/src/qml/qml/qqmlglobal.cpp
+@@ -1112,9 +1112,9 @@
+
+ // A non-composite type will always have a metaobject.
+ const QMetaObject *typeMetaObject = type.metaObject();
+- const QQmlPropertyCache::ConstPtr typePropertyCache = typeMetaObject
+- ? QQmlPropertyCache::ConstPtr()
+- : QQmlMetaType::findPropertyCacheInCompositeTypes(type.typeId());
++ QVarLengthArray<QQmlPropertyCache::ConstPtr, 4> allPropertyCacheCandidates =
++ typeMetaObject ? QVarLengthArray<QQmlPropertyCache::ConstPtr, 4>{}
++ : QQmlMetaType::rawCompositePropertyCachesForType(type.typeId());
+
+ if (const QQmlData *ddata = ddata_for_cast(object)) {
+ for (const QQmlPropertyCache *propertyCache = ddata->propertyCache.data(); propertyCache;
+@@ -1134,17 +1134,17 @@
+ // property caches to be unrelated but the types still convertible.
+ // Multiple property caches can hold the same metaobject, for example for
+ // versions of non-composite types.
+- if (propertyCache == typePropertyCache.data())
++ if (allPropertyCacheCandidates.contains(propertyCache))
+ return true;
+ }
+ }
+ }
+
+- // If nothing else works, we have to create the metaobjects.
++ // If nothing else works, we have to create the metaobjects (if we can).
++ if (!typeMetaObject && !allPropertyCacheCandidates.isEmpty())
++ typeMetaObject = allPropertyCacheCandidates.first()->createMetaObject();
+
+- return object->metaObject()->inherits(typeMetaObject
+- ? typeMetaObject
+- : (typePropertyCache ? typePropertyCache->createMetaObject() : nullptr));
++ return object->metaObject()->inherits(typeMetaObject);
+ }
+
+ QT_END_NAMESPACE
+diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp
+index 173c21d..1703c99 100644
+--- a/src/qml/qml/qqmlmetatype.cpp
++++ b/src/qml/qml/qqmlmetatype.cpp
+@@ -1490,6 +1490,20 @@
+ /*!
+ * \internal
+ *
++ * Returns all candidate property caches for a composite
++ * metatype instead of only the last inserted one.
++ * compare rawPropertyCacheForType (which handles however also non-composites)
++ */
++QVarLengthArray<QQmlPropertyCache::ConstPtr, 4>
++QQmlMetaType::rawCompositePropertyCachesForType(QMetaType metaType)
++{
++ const QQmlMetaTypeDataPtr data;
++ return data->findPropertyCachesInCompositeTypes(metaType);
++}
++
++/*!
++ * \internal
++ *
+ * Look up by QQmlType and version. We only fall back to lookup by metaobject if the type
+ * has no revisiononed attributes here. Unspecified versions are interpreted as "any".
+ */
+diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h
+index 62b6cba..040a42d 100644
+--- a/src/qml/qml/qqmlmetatype_p.h
++++ b/src/qml/qml/qqmlmetatype_p.h
+@@ -23,6 +23,7 @@
+ #include <private/qtqmlglobal_p.h>
+
+ #include <QtCore/qtyperevision.h>
++#include <QtCore/qvarlengtharray.h>
+
+ QT_BEGIN_NAMESPACE
+
+@@ -171,6 +172,10 @@
+ static QQmlPropertyCache::ConstPtr rawPropertyCacheForType(
+ QMetaType metaType, QTypeRevision version);
+
++ // All property caches for a composite metatype, which may map to more than one of them.
++ static QVarLengthArray<QQmlPropertyCache::ConstPtr, 4> rawCompositePropertyCachesForType(
++ QMetaType metaType);
++
+ static bool canConvert(QObject *o, QMetaType metaType);
+ static bool canConvert(const QQmlPropertyCache::ConstPtr &from, QMetaType metaType);
+
+diff --git a/src/qml/qml/qqmlmetatypedata.cpp b/src/qml/qml/qqmlmetatypedata.cpp
+index e72a3a9..41d7d8c 100644
+--- a/src/qml/qml/qqmlmetatypedata.cpp
++++ b/src/qml/qml/qqmlmetatypedata.cpp
+@@ -257,6 +257,18 @@
+ : propertyCacheForPotentialInlineComponentType(t, iter);
+ }
+
++QVarLengthArray<QQmlPropertyCache::ConstPtr, 4>
++QQmlMetaTypeData::findPropertyCachesInCompositeTypes(QMetaType t) const
++{
++ QVarLengthArray<QQmlPropertyCache::ConstPtr, 4> result;
++ const auto [begin, end] = compositeTypes.equal_range(t.iface());
++ for (auto iter = begin; iter != end; ++iter) {
++ if (auto cache = propertyCacheForPotentialInlineComponentType(t, iter))
++ result.append(std::move(cache));
++ }
++ return result;
++}
++
+ void QQmlMetaTypeData::clearCompositeTypes()
+ {
+ // Unregister all remaining composite types.
+diff --git a/src/qml/qml/qqmlmetatypedata_p.h b/src/qml/qml/qqmlmetatypedata_p.h
+index ce13c40..95c60a1 100644
+--- a/src/qml/qml/qqmlmetatypedata_p.h
++++ b/src/qml/qml/qqmlmetatypedata_p.h
+@@ -23,6 +23,7 @@
+ #include <private/qqmlvaluetype_p.h>
+
+ #include <QtCore/qset.h>
++#include <QtCore/qvarlengtharray.h>
+ #include <QtCore/qvector.h>
+
+ QT_BEGIN_NAMESPACE
+@@ -119,6 +120,10 @@
+ QQmlPropertyCache::ConstPtr propertyCache(const QQmlType &type, QTypeRevision version);
+ QQmlPropertyCache::ConstPtr findPropertyCacheInCompositeTypes(QMetaType t) const;
+
++ // Same, but returns all matches rather than only the last inserted one.
++ QVarLengthArray<QQmlPropertyCache::ConstPtr, 4> findPropertyCachesInCompositeTypes(
++ QMetaType t) const;
++
+ static QQmlPropertyCache::ConstPtr propertyCacheForPotentialInlineComponentType(
+ QMetaType t, const QQmlMetaTypeData::CompositeTypes::const_iterator &iter);
+
+diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp
+index c252f47..1acbb22 100644
+--- a/src/qml/qml/qqmlpropertyvalidator.cpp
++++ b/src/qml/qml/qqmlpropertyvalidator.cpp
+@@ -2,6 +2,7 @@
+ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+ // Qt-Security score:significant
+
++#include "qqmlmetatype_p.h"
+ #include "qqmlpropertyvalidator_p.h"
+
+ #include <private/qqmlcustomparser_p.h>
+@@ -781,10 +782,29 @@
+ // Will be true if the assigned type inherits propertyMetaObject
+ // Determine isAssignable value
+ bool isAssignable = false;
+- QQmlPropertyCache::ConstPtr c = propertyCaches.at(binding->value.objectIndex);
+- while (c && !isAssignable) {
+- isAssignable |= c == propertyMetaObject;
+- c = c->parent();
++ QQmlPropertyCache::ConstPtr source = propertyCaches.at(binding->value.objectIndex);
++
++ const auto inheritsFrom = [&](const QQmlPropertyCache::ConstPtr &target) {
++ for (QQmlPropertyCache::ConstPtr c = source; c; c = c->parent()) {
++ if (c == target)
++ return true;
++ }
++ return false;
++ };
++
++ isAssignable = inheritsFrom(propertyMetaObject);
++
++ if (!isAssignable) {
++ // A single (composite) metatype can map to multiple property caches when
++ // there are multiple engines; rawPropertyCacheForType only returns one of
++ // them. For non-composite types this yields an empty list and is a no-op.
++ const auto candidates = QQmlMetaType::rawCompositePropertyCachesForType(propType);
++ for (const auto &candidate : candidates) {
++ if (inheritsFrom(candidate)) {
++ isAssignable = true;
++ break;
++ }
++ }
+ }
+
+ if (!isAssignable) {
+diff --git a/tests/auto/qml/qqmllanguage/data/canQmlCastMultiEngine/Foo.qml b/tests/auto/qml/qqmllanguage/data/canQmlCastMultiEngine/Foo.qml
+new file mode 100644
+index 0000000..8fc36a4
+--- /dev/null
++++ b/tests/auto/qml/qqmllanguage/data/canQmlCastMultiEngine/Foo.qml
+@@ -0,0 +1,3 @@
++import QtQml
++
++QtObject {}
+diff --git a/tests/auto/qml/qqmllanguage/data/canQmlCastMultiEngine/Main.qml b/tests/auto/qml/qqmllanguage/data/canQmlCastMultiEngine/Main.qml
+new file mode 100644
+index 0000000..1470151
+--- /dev/null
++++ b/tests/auto/qml/qqmllanguage/data/canQmlCastMultiEngine/Main.qml
+@@ -0,0 +1,9 @@
++import QtQml
++
++QtObject {
++ property Foo theFoo: Foo {}
++
++ function check(foo: Foo) : bool {
++ return foo !== null;
++ }
++}
+diff --git a/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Consumer1.qml b/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Consumer1.qml
+new file mode 100644
+index 0000000..80de08e
+--- /dev/null
++++ b/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Consumer1.qml
+@@ -0,0 +1,5 @@
++import QtQml
++
++QtObject {
++ property Foo f: Foo {}
++}
+diff --git a/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Consumer2.qml b/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Consumer2.qml
+new file mode 100644
+index 0000000..80de08e
+--- /dev/null
++++ b/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Consumer2.qml
+@@ -0,0 +1,5 @@
++import QtQml
++
++QtObject {
++ property Foo f: Foo {}
++}
+diff --git a/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Foo.qml b/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Foo.qml
+new file mode 100644
+index 0000000..8fc36a4
+--- /dev/null
++++ b/tests/auto/qml/qqmllanguage/data/propertyValidatorMultiEngine/Foo.qml
+@@ -0,0 +1,3 @@
++import QtQml
++
++QtObject {}
+diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+index c683aa1..0af0f34 100644
+--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
++++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+@@ -298,6 +298,8 @@
+ void instanceof();
+ void instanceofMultiEngine();
+ void instanceofMultiEngineInlineComponent();
++ void propertyValidatorMultiEngine();
++ void canQmlCastMultiEngine();
+
+ void concurrentLoadQmlDir();
+
+@@ -6013,6 +6015,51 @@
+ QVERIFY(result2.toBool());
+ }
+
++// verify that the property validator does not get confused when
++// multiple engines end up registering the same type, leading to
++// multiple entries in compositeTypes
++void tst_qqmllanguage::propertyValidatorMultiEngine()
++{
++ QQmlEngine engine1;
++ QQmlComponent c1(&engine1, testFileUrl("propertyValidatorMultiEngine/Consumer1.qml"));
++ QVERIFY2(c1.isReady(), qPrintable(c1.errorString()));
++
++ QQmlEngine engine2;
++ QQmlComponent c2(&engine2, testFileUrl("propertyValidatorMultiEngine/Consumer1.qml"));
++ QVERIFY2(c2.isReady(), qPrintable(c2.errorString()));
++
++ QQmlComponent c3(&engine1, testFileUrl("propertyValidatorMultiEngine/Consumer2.qml"));
++ QVERIFY2(c3.isReady(), qPrintable(c3.errorString()));
++}
++
++// Verify that qmlobject_can_qml_cast() still recognizes a composite type when
++// multiple engines have loaded the same QML file
++void tst_qqmllanguage::canQmlCastMultiEngine()
++{
++ const QUrl url = testFileUrl("canQmlCastMultiEngine/Main.qml");
++
++ QQmlEngine engine1;
++ QQmlComponent c1(&engine1, url);
++ QVERIFY2(c1.isReady(), qPrintable(c1.errorString()));
++ QScopedPointer<QObject> obj1(c1.create());
++ QVERIFY(obj1);
++
++ // A second engine loads the same file and registers another compilation unit
++ // (and thus another property cache) for Foo.
++ QQmlEngine engine2;
++ QQmlComponent c2(&engine2, url);
++ QVERIFY2(c2.isReady(), qPrintable(c2.errorString()));
++ QScopedPointer<QObject> obj2(c2.create());
++ QVERIFY(obj2);
++
++ // Call the typed function on engine1's object with engine1's Foo instance.
++ QQmlExpression expr(engine1.contextForObject(obj1.data()), obj1.data(),
++ QStringLiteral("check(theFoo)"));
++ const QVariant result = expr.evaluate();
++ QVERIFY2(!expr.hasError(), qPrintable(expr.error().description()));
++ QVERIFY(result.toBool());
++}
++
+ void tst_qqmllanguage::concurrentLoadQmlDir()
+ {
+ ThreadedTestHTTPServer server(dataDirectory());
diff --git a/debian/patches/series b/debian/patches/series
index 190739c..6a35965 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1,2 @@
8a2c82be6ad90e3f2a0760d8bab1e3a8cdb2473a.diff
+2efb7c6.diff
More information about the Neon-commits
mailing list