aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-06-07 09:40:00 +0200
committerUlf Hermann <ulf.hermann@qt.io>2023-06-08 05:17:58 +0200
commitcd16a5ffb9d798b08dba0cdfbef045b4b2dd0f08 (patch)
tree702f7ef9e7a037939d267acc6fee48cda8427bfd
parenta4de7389bf90ab62c5d12391f9bb0d03da9b4b82 (diff)
QML: Allow coercing variant objects to their own type
This allows passing unregistered value types through QML. Pick-to: 6.5 6.6 Task-number: QTBUG-114340 Change-Id: I6fa5adadf2d406d2d5f3a83fb922e9d547e7ead9 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/jsruntime/qv4engine.cpp82
-rw-r--r--tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml6
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.cpp2
-rw-r--r--tests/auto/qml/qqmllanguage/testtypes.h17
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp13
5 files changed, 87 insertions, 33 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp
index 93887f74c6..9a56ad9d88 100644
--- a/src/qml/jsruntime/qv4engine.cpp
+++ b/src/qml/jsruntime/qv4engine.cpp
@@ -2672,42 +2672,58 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi
return true;
const bool isPointer = (metaType.flags() & QMetaType::IsPointer);
- if (value.as<QV4::VariantObject>() && isPointer) {
- const QByteArray pointedToTypeName = QByteArray(metaType.name()).chopped(1);
- const QMetaType valueType = QMetaType::fromName(pointedToTypeName);
- QVariant &var = value.as<QV4::VariantObject>()->d()->data();
- if (valueType == var.metaType()) {
- // We have T t, T* is requested, so return &t.
- *reinterpret_cast<void* *>(data) = var.data();
+ const QV4::VariantObject *variantObject = value.as<QV4::VariantObject>();
+ if (variantObject) {
+ // Actually a reference, because we're poking it for its data() below and we want
+ // the _original_ data, not some copy.
+ const QVariant &var = variantObject->d()->data();
+
+ if (var.metaType() == metaType) {
+ metaType.destruct(data);
+ metaType.construct(data, var.data());
return true;
- } else if (Object *o = value.objectValue()) {
- // Look in the prototype chain.
- QV4::Scope scope(o->engine());
- QV4::ScopedObject proto(scope, o->getPrototypeOf());
- while (proto) {
- bool canCast = false;
- if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) {
- const QVariant &v = vo->d()->data();
- canCast = (metaType == v.metaType());
- }
- else if (proto->as<QV4::QObjectWrapper>()) {
- QV4::ScopedObject p(scope, proto.getPointer());
- if (QObject *qobject = qtObjectFromJS(p)) {
- if (const QMetaObject *metaObject = metaType.metaObject())
- canCast = metaObject->cast(qobject) != nullptr;
- else
- canCast = qobject->qt_metacast(pointedToTypeName);
+ }
+
+ if (isPointer) {
+ const QByteArray pointedToTypeName = QByteArray(metaType.name()).chopped(1);
+ const QMetaType valueType = QMetaType::fromName(pointedToTypeName);
+
+ if (valueType == var.metaType()) {
+ // ### Qt7: Remove this. Returning pointers to potentially gc'd data is crazy.
+ // We have T t, T* is requested, so return &t.
+ *reinterpret_cast<const void **>(data) = var.data();
+ return true;
+ } else if (Object *o = value.objectValue()) {
+ // Look in the prototype chain.
+ QV4::Scope scope(o->engine());
+ QV4::ScopedObject proto(scope, o->getPrototypeOf());
+ while (proto) {
+ bool canCast = false;
+ if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) {
+ const QVariant &v = vo->d()->data();
+ canCast = (metaType == v.metaType());
}
+ else if (proto->as<QV4::QObjectWrapper>()) {
+ QV4::ScopedObject p(scope, proto.getPointer());
+ if (QObject *qobject = qtObjectFromJS(p)) {
+ if (const QMetaObject *metaObject = metaType.metaObject())
+ canCast = metaObject->cast(qobject) != nullptr;
+ else
+ canCast = qobject->qt_metacast(pointedToTypeName);
+ }
+ }
+ if (canCast) {
+ const QMetaType varType = var.metaType();
+ if (varType.flags() & QMetaType::IsPointer) {
+ *reinterpret_cast<const void **>(data)
+ = *reinterpret_cast<void *const *>(var.data());
+ } else {
+ *reinterpret_cast<const void **>(data) = var.data();
+ }
+ return true;
+ }
+ proto = proto->getPrototypeOf();
}
- if (canCast) {
- const QMetaType varType = var.metaType();
- if (varType.flags() & QMetaType::IsPointer)
- *reinterpret_cast<void* *>(data) = *reinterpret_cast<void* *>(var.data());
- else
- *reinterpret_cast<void* *>(data) = var.data();
- return true;
- }
- proto = proto->getPrototypeOf();
}
}
} else if (value.isNull() && isPointer) {
diff --git a/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml b/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml
new file mode 100644
index 0000000000..ecf2130f5a
--- /dev/null
+++ b/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml
@@ -0,0 +1,6 @@
+import QtQml
+import Test
+
+UnregisteredValueTypeHandler {
+ Component.onCompleted: consume(produce())
+}
diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp
index e65cbee54b..f703eece2a 100644
--- a/tests/auto/qml/qqmllanguage/testtypes.cpp
+++ b/tests/auto/qml/qqmllanguage/testtypes.cpp
@@ -153,6 +153,8 @@ void registerTypes()
qmlRegisterNamespaceAndRevisions(&TypedEnums::staticMetaObject, "TypedEnums", 1);
qmlRegisterTypesAndRevisions<ObjectWithEnums>("TypedEnums", 1);
qmlRegisterTypesAndRevisions<GadgetWithEnums>("TypedEnums", 1);
+
+ qmlRegisterTypesAndRevisions<UnregisteredValueTypeHandler>("Test", 1);
}
QVariant myCustomVariantTypeConverter(const QString &data)
diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h
index 5f68deefaf..a16580e3f8 100644
--- a/tests/auto/qml/qqmllanguage/testtypes.h
+++ b/tests/auto/qml/qqmllanguage/testtypes.h
@@ -2567,4 +2567,21 @@ Q_SIGNALS:
void changed();
};
+struct UnregisteredValueBaseType
+{
+ int foo = 12;
+};
+
+class UnregisteredValueTypeHandler: public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+public:
+ int consumed = 0;
+
+public slots:
+ UnregisteredValueBaseType produce() { return UnregisteredValueBaseType(); }
+ void consume(UnregisteredValueBaseType) { ++consumed; }
+};
+
#endif // TESTTYPES_H
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
index 0526175178..7bef01f3eb 100644
--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
@@ -414,6 +414,7 @@ private slots:
void typedEnums();
void objectMethodClone();
+ void unregisteredValueTypeConversion();
private:
QQmlEngine engine;
@@ -7988,6 +7989,18 @@ void tst_qqmllanguage::objectMethodClone()
QTRY_COMPARE(o->property("doneClicks").toInt(), 2);
}
+void tst_qqmllanguage::unregisteredValueTypeConversion()
+{
+ QQmlEngine e;
+ QQmlComponent c(&e, testFileUrl("unregisteredValueTypeConversion.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ UnregisteredValueTypeHandler *handler = qobject_cast<UnregisteredValueTypeHandler *>(o.data());
+ Q_ASSERT(handler);
+ QCOMPARE(handler->consumed, 1);
+}
+
QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc"