diff options
author | Fabian Kosmale <fabian.kosmale@qt.io> | 2024-03-20 16:04:45 +0100 |
---|---|---|
committer | Fabian Kosmale <fabian.kosmale@qt.io> | 2024-03-27 00:41:52 +0100 |
commit | 332170d46a8b75ac9635d8304a47f75a501feebc (patch) | |
tree | 1e1d495b6eebd616e0ba71d0b7a40ebc209eb875 | |
parent | 9dc056c5171a2ddae963cf45fa4fb5bd3978e674 (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.cpp | 13 | ||||
-rw-r--r-- | src/qml/qml/qqmlpropertyvalidator.cpp | 22 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/derivedFromUnexposedBase.qml | 6 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/dynamicGroupPropertyRejected.qml | 5 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.cpp | 18 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/testtypes.h | 71 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 21 |
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; |