From b386b6491d3486a78aee88e432f0d33f8ae39d06 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 1 Mar 2022 11:37:53 +0100 Subject: QML: Handle dynamic meta objects in AOT lookups If we are dealing with dynamic metaobjects, the QML engine may not create property caches. We cannot see this at compile time. Therefore, we need to establish a fallback infrastructure that does the same operations on plain QMetaObject. Fixes: QTBUG-101349 Change-Id: I8c936fc077b0018df71196620b6987825253cb39 Reviewed-by: Fabian Kosmale (cherry picked from commit 1e722f5e8e6510f3a1bc10436f8262c918ca9599) --- src/qml/jsruntime/qv4lookup_p.h | 6 + src/qml/jsruntime/qv4qmlcontext.cpp | 5 + src/qml/jsruntime/qv4qmlcontext_p.h | 1 + src/qml/qml/qqml.cpp | 341 ++++++++++++++++----- tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt | 2 + tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h | 78 +++++ .../qml/qmlcppcodegen/data/fallbacklookups.qml | 34 ++ tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 34 ++ 8 files changed, 419 insertions(+), 82 deletions(-) create mode 100644 tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h create mode 100644 tests/auto/qml/qmlcppcodegen/data/fallbacklookups.qml diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index ebce416172..d964fadd18 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -130,6 +130,12 @@ struct Q_QML_PRIVATE_EXPORT Lookup { QQmlPropertyCache *propertyCache; QQmlPropertyData *propertyData; } qobjectLookup; + struct { + 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; + int notifyIndex; + } qobjectFallbackLookup; struct { Heap::InternalClass *ic; quintptr metaObject; // a (const QMetaObject* & 1) or nullptr diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index c9a9ab138a..f7a5f897c8 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -640,6 +640,11 @@ ReturnedValue QQmlContextWrapper::lookupContextObjectProperty(Lookup *l, Executi return QObjectWrapper::lookupGetterImpl(l, engine, obj, /*useOriginalProperty*/ true, revertLookup); } +ReturnedValue QQmlContextWrapper::lookupScopeFallbackProperty(Lookup *l, ExecutionEngine *engine, Value *base) +{ + return resolveQmlContextPropertyLookupGetter(l, engine, base); +} + ReturnedValue QQmlContextWrapper::lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base) { Q_UNUSED(base); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index cbbd04bff0..1da64b101f 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -112,6 +112,7 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object static ReturnedValue lookupIdObject(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupIdObjectInParentContext(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupScopeObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base); + static ReturnedValue lookupScopeFallbackProperty(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupContextObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupInParentContextHierarchy(Lookup *l, ExecutionEngine *engine, Value *base); diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 4efda0f5d5..ec7f50cf04 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -379,9 +379,9 @@ static QVector availableRevisions(const QMetaObject *metaObject) return revisions; const int propertyOffset = metaObject->propertyOffset(); const int propertyCount = metaObject->propertyCount(); - for (int propertyIndex = propertyOffset, propertyEnd = propertyOffset + propertyCount; - propertyIndex < propertyEnd; ++propertyIndex) { - const QMetaProperty property = metaObject->property(propertyIndex); + for (int coreIndex = propertyOffset, propertyEnd = propertyOffset + propertyCount; + coreIndex < propertyEnd; ++coreIndex) { + const QMetaProperty property = metaObject->property(coreIndex); if (int revision = property.revision()) revisions.append(QTypeRevision::fromEncodedVersion(revision)); } @@ -761,6 +761,21 @@ void AOTCompiledContext::setReturnValueUndefined() const } } +static void captureFallbackProperty( + QObject *object, int coreIndex, int notifyIndex, bool isConstant, + QQmlContextData *qmlContext) +{ + if (!qmlContext || isConstant) + return; + + QQmlEngine *engine = qmlContext->engine(); + Q_ASSERT(engine); + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); + Q_ASSERT(ep); + if (QQmlPropertyCapture *capture = ep->propertyCapture) + capture->captureProperty(object, coreIndex, notifyIndex); +} + static void captureObjectProperty( QObject *object, const QQmlPropertyCache *propertyCache, const QQmlPropertyData *property, QQmlContextData *qmlContext) @@ -787,8 +802,8 @@ static bool inherits(const QQmlPropertyCache *descendent, const QQmlPropertyCach enum class ObjectPropertyResult { OK, NeedsInit, Deleted }; -static ObjectPropertyResult loadObjectProperty(QV4::Lookup *l, QObject *object, void *target, - QQmlContextData *qmlContext) +static ObjectPropertyResult loadObjectProperty( + QV4::Lookup *l, QObject *object, void *target, QQmlContextData *qmlContext) { QQmlData *qmlData = QQmlData::get(object); if (!qmlData) @@ -810,6 +825,33 @@ static ObjectPropertyResult loadObjectProperty(QV4::Lookup *l, QObject *object, return ObjectPropertyResult::OK; } +static ObjectPropertyResult loadFallbackProperty( + QV4::Lookup *l, QObject *object, void *target, QQmlContextData *qmlContext) +{ + QQmlData *qmlData = QQmlData::get(object); + if (qmlData && qmlData->isQueuedForDeletion) + return ObjectPropertyResult::Deleted; + + Q_ASSERT(!QQmlData::wasDeleted(object)); + + const QMetaObject *metaObject + = reinterpret_cast(l->qobjectFallbackLookup.metaObject - 1); + if (!metaObject || metaObject != object->metaObject()) + return ObjectPropertyResult::NeedsInit; + + const int coreIndex = l->qobjectFallbackLookup.coreIndex; + if (qmlData && qmlData->hasPendingBindingBit(coreIndex)) + qmlData->flushPendingBinding(coreIndex); + + captureFallbackProperty(object, coreIndex, l->qobjectFallbackLookup.notifyIndex, + l->qobjectFallbackLookup.isConstant, qmlContext); + + void *a[] = { target, nullptr }; + metaObject->metacall(object, QMetaObject::ReadProperty, coreIndex, a); + + return ObjectPropertyResult::OK; +} + static ObjectPropertyResult storeObjectProperty(QV4::Lookup *l, QObject *object, void *value) { const QQmlData *qmlData = QQmlData::get(object); @@ -826,7 +868,67 @@ static ObjectPropertyResult storeObjectProperty(QV4::Lookup *l, QObject *object, return ObjectPropertyResult::OK; } -static bool initObjectLookup( +static ObjectPropertyResult storeFallbackProperty(QV4::Lookup *l, QObject *object, void *value) +{ + const QQmlData *qmlData = QQmlData::get(object); + if (qmlData && qmlData->isQueuedForDeletion) + return ObjectPropertyResult::Deleted; + Q_ASSERT(!QQmlData::wasDeleted(object)); + + const QMetaObject *metaObject + = reinterpret_cast(l->qobjectFallbackLookup.metaObject - 1); + if (!metaObject || metaObject != object->metaObject()) + return ObjectPropertyResult::NeedsInit; + + const int coreIndex = l->qobjectFallbackLookup.coreIndex; + QQmlPropertyPrivate::removeBinding(object, QQmlPropertyIndex(coreIndex)); + + void *args[] = { value, nullptr }; + metaObject->metacall(object, QMetaObject::WriteProperty, coreIndex, args); + return ObjectPropertyResult::OK; +} + +static bool isTypeCompatible(QMetaType lookupType, QMetaType propertyType, + const AOTCompiledContext *aotContext) +{ + if (!lookupType.isValid()) { + // If type is invalid, then the calling code depends on the lookup + // to be set up in order to query the type, via lookupResultMetaType. + // We cannot verify the type in this case. + } else if ((lookupType.flags() & QMetaType::IsQmlList) + && (propertyType.flags() & QMetaType::IsQmlList)) { + // We want to check the value types here, but we cannot easily do it. + // Internally those are all QObject* lists, though. + } else if (lookupType.flags() & QMetaType::PointerToQObject) { + // We accept any base class as type, too + + const QMetaObject *typeMetaObject = lookupType.metaObject(); + const QMetaObject *foundMetaObject = propertyType.metaObject(); + if (!foundMetaObject) { + if (QQmlEngine *engine = aotContext->qmlEngine()) { + foundMetaObject = QQmlEnginePrivate::get(engine)->metaObjectForType( + propertyType).metaObject(); + } + } + + while (foundMetaObject && foundMetaObject != typeMetaObject) + foundMetaObject = foundMetaObject->superClass(); + + if (!foundMetaObject) + return false; + } else if (propertyType != lookupType) { + return false; + } + return true; +} + +enum class ObjectLookupResult { + Failure, + Object, + Fallback +}; + +static ObjectLookupResult initObjectLookup( const AOTCompiledContext *aotContext, QV4::Lookup *l, QObject *object, QMetaType type) { QV4::Scope scope(aotContext->engine->handle()); @@ -843,7 +945,7 @@ static bool initObjectLookup( QQmlData *ddata = QQmlData::get(object, true); Q_ASSERT(ddata); if (ddata->isQueuedForDeletion) - return false; + return ObjectLookupResult::Failure; QQmlPropertyData *property; if (!ddata->propertyCache) { @@ -854,45 +956,36 @@ static bool initObjectLookup( name.getPointer(), object, aotContext->qmlContext); } - if (!property) - return false; - - const QMetaType propType = property->propType(); - if (!type.isValid()) { - // If type is invalid, then the calling code depends on the lookup - // to be set up in order to query the type, via lookupResultMetaType. - // We cannot verify the type in this case. - } else if ((type.flags() & QMetaType::IsQmlList) && (propType.flags() & QMetaType::IsQmlList)) { - // We want to check the value types here, but we cannot easily do it. - // Internally those are all QObject* lists, though. - } else if (type.flags() & QMetaType::PointerToQObject) { - // We accept any base class as type, too - - const QMetaObject *typeMetaObject = type.metaObject(); - const QMetaObject *foundMetaObject = propType.metaObject(); - if (!foundMetaObject) { - if (QQmlEngine *engine = aotContext->qmlEngine()) { - foundMetaObject = QQmlEnginePrivate::get(engine)->metaObjectForType( - propType).metaObject(); - } - } - - while (foundMetaObject) { - if (foundMetaObject == typeMetaObject) - break; - foundMetaObject = foundMetaObject->superClass(); - } - - if (!foundMetaObject) - return false; - } else if (propType != type) { - return false; + if (!property) { + const QMetaObject *metaObject = object->metaObject(); + if (!metaObject) + return ObjectLookupResult::Failure; + + const int coreIndex = metaObject->indexOfProperty( + name->toQStringNoThrow().toUtf8().constData()); + if (coreIndex < 0) + return ObjectLookupResult::Failure; + + const QMetaProperty property = metaObject->property(coreIndex); + if (!isTypeCompatible(type, property.metaType(), aotContext)) + return ObjectLookupResult::Failure; + + l->releasePropertyCache(); + // & 1 to tell the gc that this is not heap allocated; see markObjects in qv4lookup_p.h + l->qobjectFallbackLookup.metaObject = quintptr(metaObject) + 1; + l->qobjectFallbackLookup.coreIndex = coreIndex; + l->qobjectFallbackLookup.notifyIndex = property.notifySignalIndex(); + l->qobjectFallbackLookup.isConstant = property.isConstant() ? 1 : 0; + return ObjectLookupResult::Fallback; } + if (!isTypeCompatible(type, property->propType(), aotContext)) + return ObjectLookupResult::Failure; + Q_ASSERT(ddata->propertyCache); QV4::setupQObjectLookup(l, ddata, property); - return true; + return ObjectLookupResult::Object; } static bool initValueLookup(QV4::Lookup *l, QV4::ExecutableCompilationUnit *compilationUnit, @@ -928,29 +1021,47 @@ bool AOTCompiledContext::captureLookup(uint index, QObject *object) const return false; QV4::Lookup *l = compilationUnit->runtimeLookups + index; - if (l->getter != QV4::QQmlTypeWrapper::lookupSingletonProperty - && l->getter != QV4::Lookup::getterQObject) { - return false; + if (l->getter == QV4::QQmlTypeWrapper::lookupSingletonProperty + || l->getter == QV4::Lookup::getterQObject) { + const QQmlPropertyData *property = l->qobjectLookup.propertyData; + QQmlData::flushPendingBinding(object, property->coreIndex()); + captureObjectProperty(object, l->qobjectLookup.propertyCache, property, qmlContext); + return true; } - const QQmlPropertyData *property = l->qobjectLookup.propertyData; - QQmlData::flushPendingBinding(object, property->coreIndex()); - captureObjectProperty(object, l->qobjectLookup.propertyCache, property, qmlContext); + if (l->getter == QV4::Lookup::getterFallback) { + const int coreIndex = l->qobjectFallbackLookup.coreIndex; + QQmlData::flushPendingBinding(object, coreIndex); + captureFallbackProperty( + object, coreIndex, l->qobjectFallbackLookup.notifyIndex, + l->qobjectFallbackLookup.isConstant, qmlContext); + return true; + } + + return true; } bool AOTCompiledContext::captureQmlContextPropertyLookup(uint index) const { QV4::Lookup *l = compilationUnit->runtimeLookups + index; - if (l->qmlContextPropertyGetter != QV4::QQmlContextWrapper::lookupScopeObjectProperty - && l->qmlContextPropertyGetter != QV4::QQmlContextWrapper::lookupContextObjectProperty) { - return false; + if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeObjectProperty + && l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupContextObjectProperty) { + const QQmlPropertyData *property = l->qobjectLookup.propertyData; + QQmlData::flushPendingBinding(qmlScopeObject, property->coreIndex()); + captureObjectProperty(qmlScopeObject, l->qobjectLookup.propertyCache, property, qmlContext); + return true; } - const QQmlPropertyData *property = l->qobjectLookup.propertyData; - QQmlData::flushPendingBinding(qmlScopeObject, property->coreIndex()); - captureObjectProperty(qmlScopeObject, l->qobjectLookup.propertyCache, property, qmlContext); - return true; + if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeFallbackProperty) { + const int coreIndex = l->qobjectFallbackLookup.coreIndex; + QQmlData::flushPendingBinding(qmlScopeObject, coreIndex); + captureFallbackProperty(qmlScopeObject, coreIndex, l->qobjectFallbackLookup.notifyIndex, + l->qobjectFallbackLookup.isConstant, qmlContext); + return true; + } + + return false; } QMetaType AOTCompiledContext::lookupResultMetaType(uint index) const @@ -971,6 +1082,14 @@ QMetaType AOTCompiledContext::lookupResultMetaType(uint index) const || l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupSingleton || l->getter == QV4::QObjectWrapper::lookupAttached) { return QMetaType::fromType(); + } else if (l->getter == QV4::Lookup::getterFallback + || l->setter == QV4::Lookup::setterFallback + || l->qmlContextPropertyGetter + == QV4::QQmlContextWrapper::lookupScopeFallbackProperty) { + const QMetaObject *metaObject + = reinterpret_cast(l->qobjectFallbackLookup.metaObject - 1); + const int coreIndex = l->qobjectFallbackLookup.coreIndex; + return metaObject->property(coreIndex).metaType(); } return QMetaType(); } @@ -984,9 +1103,9 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType QV4::Lookup l; l.clear(); l.nameIndex = nameIndex; - if (initObjectLookup(this, &l, qmlScopeObject, QMetaType())) { - - ObjectPropertyResult storeResult; + ObjectPropertyResult storeResult = ObjectPropertyResult::NeedsInit; + switch (initObjectLookup(this, &l, qmlScopeObject, QMetaType())) { + case ObjectLookupResult::Object: { const QMetaType propType = l.qobjectLookup.propertyData->propType(); if (type == propType) { storeResult = storeObjectProperty(&l, qmlScopeObject, value); @@ -996,20 +1115,38 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType storeResult = storeObjectProperty(&l, qmlScopeObject, var.data()); } - switch (storeResult) { - case ObjectPropertyResult::NeedsInit: - engine->handle()->throwTypeError(); - break; - case ObjectPropertyResult::Deleted: - engine->handle()->throwTypeError( - QStringLiteral("Value is null and could not be converted to an object")); - break; - case ObjectPropertyResult::OK: - break; - } l.qobjectLookup.propertyCache->release(); - } else { + break; + } + case ObjectLookupResult::Fallback: { + const QMetaObject *metaObject + = reinterpret_cast(l.qobjectFallbackLookup.metaObject - 1); + const QMetaType propType + = metaObject->property(l.qobjectFallbackLookup.coreIndex).metaType(); + if (type == propType) { + storeResult = storeFallbackProperty(&l, qmlScopeObject, value); + } else { + QVariant var(propType); + propType.convert(type, value, propType, var.data()); + storeResult = storeFallbackProperty(&l, qmlScopeObject, var.data()); + } + break; + } + case ObjectLookupResult::Failure: engine->handle()->throwTypeError(); + return; + } + + switch (storeResult) { + case ObjectPropertyResult::NeedsInit: + engine->handle()->throwTypeError(); + break; + case ObjectPropertyResult::Deleted: + engine->handle()->throwTypeError( + QStringLiteral("Value is null and could not be converted to an object")); + break; + case ObjectPropertyResult::OK: + break; } } @@ -1174,10 +1311,16 @@ void AOTCompiledContext::initLoadGlobalLookup(uint index) const bool AOTCompiledContext::loadScopeObjectPropertyLookup(uint index, void *target) const { QV4::Lookup *l = compilationUnit->runtimeLookups + index; - if (l->qmlContextPropertyGetter != QV4::QQmlContextWrapper::lookupScopeObjectProperty) + + ObjectPropertyResult result = ObjectPropertyResult::NeedsInit; + if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeObjectProperty) + result = loadObjectProperty(l, qmlScopeObject, target, qmlContext); + else if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeFallbackProperty) + result = loadFallbackProperty(l, qmlScopeObject, target, qmlContext); + else return false; - switch (loadObjectProperty(l, qmlScopeObject, target, qmlContext)) { + switch (result) { case ObjectPropertyResult::NeedsInit: return false; case ObjectPropertyResult::Deleted: @@ -1198,12 +1341,22 @@ void AOTCompiledContext::initLoadScopeObjectPropertyLookup(uint index, QMetaType QV4::ExecutionEngine *v4 = engine->handle(); QV4::Lookup *l = compilationUnit->runtimeLookups + index; - if (v4->hasException) + if (v4->hasException) { amendException(v4); - else if (initObjectLookup(this, l, qmlScopeObject, type)) + return; + } + + switch (initObjectLookup(this, l, qmlScopeObject, type)) { + case ObjectLookupResult::Object: l->qmlContextPropertyGetter = QV4::QQmlContextWrapper::lookupScopeObjectProperty; - else + break; + case ObjectLookupResult::Fallback: + l->qmlContextPropertyGetter = QV4::QQmlContextWrapper::lookupScopeFallbackProperty; + break; + case ObjectLookupResult::Failure: v4->throwTypeError(); + break; + } } bool AOTCompiledContext::loadSingletonLookup(uint index, void *target) const @@ -1343,10 +1496,15 @@ bool AOTCompiledContext::getObjectLookup(uint index, QObject *object, void *targ if (!object) return doThrow(); - if (l->getter != QV4::Lookup::getterQObject) + ObjectPropertyResult result = ObjectPropertyResult::NeedsInit; + if (l->getter == QV4::Lookup::getterQObject) + result = loadObjectProperty(l, object, target, qmlContext); + else if (l->getter == QV4::Lookup::getterFallback) + result = loadFallbackProperty(l, object, target, qmlContext); + else return false; - switch (loadObjectProperty(l, object, target, qmlContext)) { + switch (result) { case ObjectPropertyResult::Deleted: return doThrow(); case ObjectPropertyResult::NeedsInit: @@ -1366,10 +1524,17 @@ void AOTCompiledContext::initGetObjectLookup(uint index, QObject *object, QMetaT amendException(v4); } else { QV4::Lookup *l = compilationUnit->runtimeLookups + index; - if (initObjectLookup(this, l, object, type)) + switch (initObjectLookup(this, l, object, type)) { + case ObjectLookupResult::Object: l->getter = QV4::Lookup::getterQObject; - else + break; + case ObjectLookupResult::Fallback: + l->getter = QV4::Lookup::getterFallback; + break; + case ObjectLookupResult::Failure: engine->handle()->throwTypeError(); + break; + } } } @@ -1437,10 +1602,15 @@ bool AOTCompiledContext::setObjectLookup(uint index, QObject *object, void *valu return doThrow(); QV4::Lookup *l = compilationUnit->runtimeLookups + index; - if (l->setter != QV4::Lookup::setterQObject) + ObjectPropertyResult result = ObjectPropertyResult::NeedsInit; + if (l->setter == QV4::Lookup::setterQObject) + result = storeObjectProperty(l, object, value); + else if (l->setter == QV4::Lookup::setterFallback) + result = storeFallbackProperty(l, object, value); + else return false; - switch (storeObjectProperty(l, object, value)) { + switch (result) { case ObjectPropertyResult::Deleted: return doThrow(); case ObjectPropertyResult::NeedsInit: @@ -1460,10 +1630,17 @@ void AOTCompiledContext::initSetObjectLookup(uint index, QObject *object, QMetaT amendException(v4); } else { QV4::Lookup *l = compilationUnit->runtimeLookups + index; - if (initObjectLookup(this, l, object, type)) + switch (initObjectLookup(this, l, object, type)) { + case ObjectLookupResult::Object: l->setter = QV4::Lookup::setterQObject; - else + break; + case ObjectLookupResult::Fallback: + l->setter = QV4::Lookup::setterFallback; + break; + case ObjectLookupResult::Failure: engine->handle()->throwTypeError(); + break; + } } } diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 3d05ffcf1d..4d585b62f0 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -1,6 +1,7 @@ set(cpp_sources birthdayparty.cpp birthdayparty.h cppbaseclass.h + dynamicmeta.h objectwithmethod.h person.cpp person.h theme.cpp theme.h @@ -61,6 +62,7 @@ set(qml_files excessiveParameters.qml extendedTypes.qml failures.qml + fallbacklookups.qml fileDialog.qml functionLookup.qml funcWithParams.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h b/tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h new file mode 100644 index 0000000000..3f02e460e7 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/dynamicmeta.h @@ -0,0 +1,78 @@ +#ifndef DYNAMICMETA_H +#define DYNAMICMETA_H + +#include +#include + +struct FreeDeleter { + void operator()(QMetaObject *meta) { free(meta); } +}; + +template +class MetaObjectData : public QDynamicMetaObjectData +{ + Q_DISABLE_COPY_MOVE(MetaObjectData) +public: + MetaObjectData() = default; + ~MetaObjectData() = default; + + QMetaObject *toDynamicMetaObject(QObject *) override + { + return const_cast(&T::staticMetaObject); + } + int metaCall(QObject *o, QMetaObject::Call call, int idx, void **argv) override + { + return o->qt_metacall(call, idx, argv); + } +}; + +class DynamicMeta : public QObject +{ + Q_OBJECT + Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged FINAL) + QML_ELEMENT +public: + + DynamicMeta(QObject *parent = nullptr) + : QObject(parent) + { + // deletes itself + QObjectPrivate::get(this)->metaObject = new MetaObjectData; + } + + int foo() const { return m_foo; } + void setFoo(int newFoo) + { + if (m_foo != newFoo) { + m_foo = newFoo; + emit fooChanged(); + } + } + + Q_INVOKABLE int bar(int baz) { return baz + 12; } + +Q_SIGNALS: + void fooChanged(); + +private: + int m_foo = 0; +}; + +class DynamicMetaSingleton : public DynamicMeta +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + Q_PROPERTY(DynamicMetaSingleton *itself READ itself CONSTANT FINAL) +public: + DynamicMetaSingleton(QObject *parent = nullptr) : DynamicMeta(parent) + { + QObjectPrivate *d = QObjectPrivate::get(this); + delete d->metaObject; + d->metaObject = new MetaObjectData; + } + + DynamicMetaSingleton *itself() { return this; } +}; + +#endif // DYNAMICMETA_H diff --git a/tests/auto/qml/qmlcppcodegen/data/fallbacklookups.qml b/tests/auto/qml/qmlcppcodegen/data/fallbacklookups.qml new file mode 100644 index 0000000000..4b58cd344d --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/fallbacklookups.qml @@ -0,0 +1,34 @@ +import TestTypes +import QtQml + +DynamicMeta { + id: self + + function getSingleton(): QtObject { + return DynamicMetaSingleton.itself + } + + function withContext(): int { + foo = 93; + objectName = "aa" + foo; + return bar(4); + } + + function withId(): int { + self.foo = 94; + self.objectName = "bb" + foo; + return self.bar(5); + } + + function withSingleton(): int { + DynamicMetaSingleton.foo = 95; + DynamicMetaSingleton.objectName = "cc" + DynamicMetaSingleton.foo; + return DynamicMetaSingleton.bar(6); + } + + function withProperty(): int { + DynamicMetaSingleton.itself.foo = 96; + DynamicMetaSingleton.itself.objectName = "dd" + DynamicMetaSingleton.itself.foo; + return DynamicMetaSingleton.itself.bar(7); + } +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index d1b8565afc..acc117d967 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -124,6 +124,7 @@ private slots: void functionLookup(); void objectInVar(); void testIsnan(); + void fallbackLookups(); }; void tst_QmlCppCodegen::simpleBinding() @@ -1846,6 +1847,39 @@ void tst_QmlCppCodegen::testIsnan() QVERIFY(b.toBool()); } +void tst_QmlCppCodegen::fallbackLookups() +{ + QQmlEngine engine; + const QUrl document(u"qrc:/TestTypes/fallbacklookups.qml"_qs); + QQmlComponent c(&engine, document); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + QVERIFY(o); + + QCOMPARE(o->objectName(), QString()); + int result = 0; + + QMetaObject::invokeMethod(o.data(), "withContext", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 16); + QCOMPARE(o->objectName(), QStringLiteral("aa93")); + + QMetaObject::invokeMethod(o.data(), "withId", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 17); + QCOMPARE(o->objectName(), QStringLiteral("bb94")); + + QObject *singleton = nullptr; + QMetaObject::invokeMethod(o.data(), "getSingleton", Q_RETURN_ARG(QObject*, singleton)); + QVERIFY(singleton); + + QMetaObject::invokeMethod(o.data(), "withSingleton", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 18); + QCOMPARE(singleton->objectName(), QStringLiteral("cc95")); + + QMetaObject::invokeMethod(o.data(), "withProperty", Q_RETURN_ARG(int, result)); + QCOMPARE(result, 19); + QCOMPARE(singleton->objectName(), QStringLiteral("dd96")); +} + void tst_QmlCppCodegen::runInterpreted() { if (qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER")) -- cgit v1.2.3