aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2024-03-20 16:04:45 +0100
committerFabian Kosmale <fabian.kosmale@qt.io>2024-03-27 00:41:52 +0100
commit332170d46a8b75ac9635d8304a47f75a501feebc (patch)
tree1e1d495b6eebd616e0ba71d0b7a40ebc209eb875
parent9dc056c5171a2ddae963cf45fa4fb5bd3978e674 (diff)
Engine: Handle grouped property bindings of non-registered types
It is possible to expose a type to QML which exposes a QObject (derived) property, which then gets used to create a grouped binding. The type of the property might not have been registered in QML, or it might be part of a base class which exposes the type already in another module. If that module is not imported (or does not exist at all), the property creator would so far bail out. Simply skipping the check would lead to crashes further down, as we also lack the property cache needed during object creation. However, in Qt 6, we can lift this restriction: We can retrieve the property's metatype, and from there we can fetch the metaobject. Once we have the metaobject, we can on-demand create a property-cache for it, and use that later in the object creator. One restriction we add is that the type must not have a dynamic meta-object, given that those don't lend themselves to usable propety caches (given their dynamic nature.) The early check in the property validator is adjusted accordingly. Add a quick test to verify that gadgets continue to work as before. As a drive-by, convert a few repeated QString::arg calls to a single call of the variadic overload. Task-number: QTBUG-122321 Pick-to: 6.7 Change-Id: I860af981250a70f541794b57db3764415ea172f0 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
-rw-r--r--src/qml/qml/qqmlpropertycachecreator.cpp13
-rw-r--r--src/qml/qml/qqmlpropertyvalidator.cpp22
-rw-r--r--tests/auto/qml/qqmllanguage/data/derivedFromUnexposedBase.qml6
-rw-r--r--tests/auto/qml/qqmllanguage/data/dynamicGroupPropertyRejected.qml5
-rw-r--r--tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt2
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.cpp18
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.h71
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp21
8 files changed, 149 insertions, 9 deletions
diff --git a/src/qml/qml/qqmlpropertycachecreator.cpp b/src/qml/qml/qqmlpropertycachecreator.cpp
index ef36668dec..780418380a 100644
--- a/src/qml/qml/qqmlpropertycachecreator.cpp
+++ b/src/qml/qml/qqmlpropertycachecreator.cpp
@@ -154,8 +154,19 @@ QQmlPropertyCache::ConstPtr QQmlBindingInstantiationContext::instantiatingProper
if (instantiatingProperty->isQObject()) {
// rawPropertyCacheForType assumes a given unspecified version means "any version".
// There is another overload that takes no version, which we shall not use here.
- return QQmlMetaType::rawPropertyCacheForType(instantiatingProperty->propType(),
+ auto result = QQmlMetaType::rawPropertyCacheForType(instantiatingProperty->propType(),
instantiatingProperty->typeVersion());
+ if (result)
+ return result;
+ /* We might end up here if there's a grouped property, and the type hasn't been registered.
+ Still try to get a property cache, as long as the type of the property is well-behaved
+ (i.e., not dynamic)*/
+ if (auto metaObject = instantiatingProperty->propType().metaObject(); metaObject) {
+ // we'll warn about dynamic meta-object later in the property validator
+ if (!(QMetaObjectPrivate::get(metaObject)->flags & DynamicMetaObject))
+ return QQmlMetaType::propertyCache(metaObject);
+ }
+ // fall through intentional
} else if (const QMetaObject *vtmo = QQmlMetaType::metaObjectForValueType(instantiatingProperty->propType())) {
return QQmlMetaType::propertyCache(vtmo, instantiatingProperty->typeVersion());
}
diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp
index a49393e425..5ae3829c27 100644
--- a/src/qml/qml/qqmlpropertyvalidator.cpp
+++ b/src/qml/qml/qqmlpropertyvalidator.cpp
@@ -302,17 +302,25 @@ QVector<QQmlError> QQmlPropertyValidator::validateObject(
return recordError(
binding->location,
tr("Invalid grouped property access: Property \"%1\" with primitive type \"%2\".")
- .arg(name)
- .arg(QString::fromUtf8(type.name()))
+ .arg(name, QString::fromUtf8(type.name()))
);
}
if (!QQmlMetaType::propertyCacheForType(type)) {
- return recordError(binding->location,
- tr("Invalid grouped property access: Property \"%1\" with type \"%2\", which is not a value type")
- .arg(name)
- .arg(QString::fromUtf8(type.name()))
- );
+ auto mo = type.metaObject();
+ if (!mo) {
+ return recordError(binding->location,
+ tr("Invalid grouped property access: Property \"%1\" with type \"%2\", which is neither a value nor an object type")
+ .arg(name, QString::fromUtf8(type.name()))
+ );
+ }
+ if (QMetaObjectPrivate::get(mo)->flags & DynamicMetaObject) {
+ return recordError(binding->location,
+ QString::fromLatin1("Unsupported grouped property access: Property \"%1\" with type \"%2\" has a dynamic meta-object.")
+ .arg(name, QString::fromUtf8(type.name()))
+ );
+ }
+ // fall through, this is okay
}
}
}
diff --git a/tests/auto/qml/qqmllanguage/data/derivedFromUnexposedBase.qml b/tests/auto/qml/qqmllanguage/data/derivedFromUnexposedBase.qml
new file mode 100644
index 0000000000..b508474a36
--- /dev/null
+++ b/tests/auto/qml/qqmllanguage/data/derivedFromUnexposedBase.qml
@@ -0,0 +1,6 @@
+import Test
+
+DerivedFromUnexposedBase {
+ group.value: 42
+ groupGadget.value: 42
+}
diff --git a/tests/auto/qml/qqmllanguage/data/dynamicGroupPropertyRejected.qml b/tests/auto/qml/qqmllanguage/data/dynamicGroupPropertyRejected.qml
new file mode 100644
index 0000000000..2fffea25c6
--- /dev/null
+++ b/tests/auto/qml/qqmllanguage/data/dynamicGroupPropertyRejected.qml
@@ -0,0 +1,5 @@
+import Test
+
+DerivedFromUnexposedBase {
+ dynamic.value: "This should fail"
+}
diff --git a/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt b/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt
index 9a0422753f..ced96fba83 100644
--- a/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt
+++ b/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt
@@ -1 +1 @@
-4:5:Invalid grouped property access: Property "customType" with type "MyCustomVariantType", which is not a value type
+4:5:Invalid grouped property access: Property "customType" with type "MyCustomVariantType", which is neither a value nor an object type
diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp
index e5bfe60a9a..ffff0a6979 100644
--- a/tests/auto/qml/qqmllanguage/testtypes.cpp
+++ b/tests/auto/qml/qqmllanguage/testtypes.cpp
@@ -56,6 +56,8 @@ void registerTypes()
qmlRegisterTypeNotAvailable("Test",1,0,"UnavailableType", "UnavailableType is unavailable for testing");
+ qmlRegisterTypesAndRevisions<DerivedFromUnexposedBase>("Test", 1);
+
qmlRegisterType<MyQmlObject>("Test.Version",1,0,"MyQmlObject");
qmlRegisterType<MyTypeObject>("Test.Version",1,0,"MyTypeObject");
qmlRegisterType<MyTypeObject>("Test.Version",2,0,"MyTypeObject");
@@ -272,3 +274,19 @@ UncreatableSingleton *UncreatableSingleton::instance()
static UncreatableSingleton instance;
return &instance;
}
+
+QT_BEGIN_NAMESPACE
+const QMetaObject *QtPrivate::MetaObjectForType<FakeDynamicObject *, void>::metaObjectFunction(const QMetaTypeInterface *)
+{
+ static auto ptr = []{
+ QMetaObjectBuilder builder(&FakeDynamicObject::staticMetaObject);
+ builder.setFlags(DynamicMetaObject);
+ auto mo = builder.toMetaObject();
+ QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [mo]() {
+ delete mo;
+ });
+ return mo;
+ }();
+ return ptr;
+}
+QT_END_NAMESPACE
diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h
index e582d28e01..bcf02c1cf9 100644
--- a/tests/auto/qml/qqmllanguage/testtypes.h
+++ b/tests/auto/qml/qqmllanguage/testtypes.h
@@ -44,6 +44,77 @@ struct MyCustomVariantType
};
Q_DECLARE_METATYPE(MyCustomVariantType);
+
+class Group : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int value MEMBER value)
+
+public:
+ Group(QObject *parent = nullptr) : QObject(parent) {}
+ int value = 0;
+};
+
+
+struct GroupGadget
+{
+ Q_GADGET
+
+ Q_PROPERTY(int value MEMBER value)
+
+public:
+ friend bool operator==(GroupGadget g1, GroupGadget g2) { return g1.value == g2.value; }
+ friend bool operator!=(GroupGadget g1, GroupGadget g2) { return !(g1 == g2); }
+ int value = 0;
+};
+
+struct FakeDynamicObject : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString value MEMBER value)
+
+public:
+ FakeDynamicObject() {}
+ QString value;
+};
+
+QT_BEGIN_NAMESPACE
+namespace QtPrivate {
+// don't do this at home – we override the meta-object which QMetaType collects for
+// FakeDynamicObject* properties
+template<>
+struct MetaObjectForType<FakeDynamicObject *, void>
+{
+ static const QMetaObject *metaObjectFunction(const QMetaTypeInterface *);
+};
+}
+QT_END_NAMESPACE
+
+class UnexposedBase : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(Group *group MEMBER group)
+ Q_PROPERTY(GroupGadget groupGadget MEMBER groupGadget)
+ Q_PROPERTY(FakeDynamicObject *dynamic MEMBER dynamic)
+public:
+ UnexposedBase(QObject *parent = nullptr) : QObject(parent)
+ {
+ group = new Group(this);
+ }
+ Group *group;
+ GroupGadget groupGadget;
+ FakeDynamicObject *dynamic = nullptr;
+};
+
+class DerivedFromUnexposedBase : public UnexposedBase
+{
+ Q_OBJECT
+ QML_ELEMENT
+};
+
+
class MyAttachedObject : public QObject
{
Q_OBJECT
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
index d4ca327530..61295ec940 100644
--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
@@ -360,6 +360,8 @@ private slots:
void hangOnWarning();
+ void groupPropertyFromNonExposedBaseClass();
+
void listEnumConversion();
void deepInlineComponentScriptBinding();
@@ -6864,6 +6866,25 @@ void tst_qqmllanguage::hangOnWarning()
QVERIFY(object != nullptr);
}
+void tst_qqmllanguage::groupPropertyFromNonExposedBaseClass()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("derivedFromUnexposedBase.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ auto root = qobject_cast<DerivedFromUnexposedBase *>(o.get());
+ QVERIFY(root);
+ QVERIFY(root->group);
+ QCOMPARE(root->group->value, 42);
+ QCOMPARE(root->groupGadget.value, 42);
+
+ c.loadUrl(testFileUrl("dynamicGroupPropertyRejected.qml"));
+ QVERIFY(c.isError());
+ QVERIFY2(c.errorString().contains("Unsupported grouped property access"), qPrintable(c.errorString()));
+}
+
void tst_qqmllanguage::listEnumConversion()
{
QQmlEngine e;