diff --git a/src/qml/jsruntime/qv4lookup.cpp b/src/qml/jsruntime/qv4lookup.cpp index abf88f4283..ff6b349ad9 100644 --- a/src/qml/jsruntime/qv4lookup.cpp +++ b/src/qml/jsruntime/qv4lookup.cpp @@ -201,6 +201,21 @@ ReturnedValue Lookup::getterFallback(Lookup *l, ExecutionEngine *engine, const V return o->get(name); } +ReturnedValue Lookup::getterFallbackAsVariant( + Lookup *l, ExecutionEngine *engine, const Value &object) +{ + if (&Lookup::getterFallback == &Lookup::getterFallbackAsVariant) { + // Certain compilers, e.g. MSVC, will "helpfully" deduplicate methods that are completely + // equal. As a result, the pointers are the same, which wreaks havoc on the logic that + // decides how to retrieve the property. + qFatal("Your C++ compiler is broken."); + } + + // This getter just marks the presence of a fallback lookup with variant conversion. + // It only does anything with it when running AOT-compiled code. + return getterFallback(l, engine, object); +} + ReturnedValue Lookup::getter0MemberData(Lookup *l, ExecutionEngine *engine, const Value &object) { // we can safely cast to a QV4::Object here. If object is actually a string, @@ -401,6 +416,21 @@ ReturnedValue Lookup::getterQObject(Lookup *lookup, ExecutionEngine *engine, con return QObjectWrapper::lookupPropertyGetterImpl(lookup, engine, object, flags, revertLookup); } +ReturnedValue Lookup::getterQObjectAsVariant( + Lookup *lookup, ExecutionEngine *engine, const Value &object) +{ + if (&Lookup::getterQObject == &Lookup::getterQObjectAsVariant) { + // Certain compilers, e.g. MSVC, will "helpfully" deduplicate methods that are completely + // equal. As a result, the pointers are the same, which wreaks havoc on the logic that + // decides how to retrieve the property. + qFatal("Your C++ compiler is broken."); + } + + // This getter marks the presence of a qobjectlookup with variant conversion. + // It only does anything with it when running AOT-compiled code. + return getterQObject(lookup, engine, object); +} + ReturnedValue Lookup::getterQObjectMethod(Lookup *lookup, ExecutionEngine *engine, const Value &object) { const auto revertLookup = [lookup, engine, &object]() { @@ -559,6 +589,14 @@ bool Lookup::setterFallback(Lookup *l, ExecutionEngine *engine, Value &object, c return o->put(name, value); } +bool Lookup::setterFallbackAsVariant( + Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +{ + // This setter just marks the presence of a fallback lookup with QVariant conversion. + // It only does anything with it when running AOT-compiled code. + return setterFallback(l, engine, object, value); +} + bool Lookup::setter0MemberData(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { Heap::Object *o = static_cast(object.heapObject()); @@ -622,6 +660,15 @@ bool Lookup::setterQObject(Lookup *l, ExecutionEngine *engine, Value &object, co return QV4::Lookup::setterFallback(l, engine, object, v); } +bool Lookup::setterQObjectAsVariant( + Lookup *l, ExecutionEngine *engine, Value &object, const Value &v) +{ + // This setter marks the presence of a qobjectlookup with QVariant conversion. + // It only does anything with it when running AOT-compiled code. + return QV4::Lookup::setterFallback(l, engine, object, v); +} + + bool Lookup::arrayLengthSetter(Lookup *, ExecutionEngine *engine, Value &object, const Value &value) { Q_ASSERT(object.isObject() && static_cast(object).isArrayObject()); diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index 88a91aacf3..1637d4d82e 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -165,6 +165,7 @@ struct Q_QML_PRIVATE_EXPORT Lookup { static ReturnedValue getterGeneric(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getterTwoClasses(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getterFallback(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue getterFallbackAsVariant(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getter0MemberData(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getter0Inline(Lookup *l, ExecutionEngine *engine, const Value &object); @@ -178,6 +179,7 @@ struct Q_QML_PRIVATE_EXPORT Lookup { static ReturnedValue getterProtoAccessorTwoClasses(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getterIndexed(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getterQObject(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue getterQObjectAsVariant(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getterQObjectMethod(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue primitiveGetterProto(Lookup *l, ExecutionEngine *engine, const Value &object); @@ -192,11 +194,13 @@ struct Q_QML_PRIVATE_EXPORT Lookup { static bool setterGeneric(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); Q_NEVER_INLINE static bool setterTwoClasses(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setterFallback(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool setterFallbackAsVariant(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setter0MemberData(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setter0Inline(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setter0setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setterInsert(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setterQObject(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool setterQObjectAsVariant(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool arrayLengthSetter(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); void markObjects(MarkStack *stack) { @@ -216,7 +220,9 @@ struct Q_QML_PRIVATE_EXPORT Lookup { || getter == QQmlTypeWrapper::lookupSingletonProperty || setter == setterQObject || qmlContextPropertyGetter == QQmlContextWrapper::lookupScopeObjectProperty - || qmlContextPropertyGetter == QQmlContextWrapper::lookupContextObjectProperty) { + || qmlContextPropertyGetter == QQmlContextWrapper::lookupContextObjectProperty + || getter == getterQObjectAsVariant + || setter == setterQObjectAsVariant) { if (const QQmlPropertyCache *pc = qobjectLookup.propertyCache) pc->release(); } else if (getter == getterQObjectMethod diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index ee7a1ce68e..5e4e93fbd9 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -416,8 +416,11 @@ void VME::exec(MetaTypesStackFrame *frame, ExecutionEngine *engine) Q_ALLOCA_DECLARE(void *, transformedArguments); for (qsizetype i = 0; i < numFunctionArguments; ++i) { + const bool isValid = frame->argc() > i; + const QMetaType frameType = isValid ? frame->argTypes()[i] : QMetaType(); + const QMetaType argumentType = function->typedFunction->argumentTypes[i]; - if (frame->argc() > i && argumentType == frame->argTypes()[i]) + if (isValid && argumentType == frameType) continue; if (transformedArguments == nullptr) { @@ -430,22 +433,41 @@ void VME::exec(MetaTypesStackFrame *frame, ExecutionEngine *engine) continue; } + void *frameVal = isValid ? frame->argv()[i] : nullptr; + if (isValid && frameType == QMetaType::fromType()) { + QVariant *variant = static_cast(frameVal); + + const QMetaType variantType = variant->metaType(); + if (variantType == argumentType) { + // Slightly nasty, but we're allowed to do this. + // We don't want to destruct() the QVariant's data() below. + transformedArguments[i] = frame->argv()[i] = variant->data(); + } else { + Q_ALLOCA_VAR(void, arg, argumentType.sizeOf()); + argumentType.construct(arg); + QMetaType::convert(variantType, variant->data(), argumentType, arg); + transformedArguments[i] = arg; + } + continue; + } + Q_ALLOCA_VAR(void, arg, argumentType.sizeOf()); if (argumentType == QMetaType::fromType()) { - if (frame->argc() > i) - new (arg) QVariant(frame->argTypes()[i], frame->argv()[i]); + if (isValid) + new (arg) QVariant(frameType, frameVal); else new (arg) QVariant(); } else if (argumentType == QMetaType::fromType()) { - if (frame->argc() > i) - new (arg) QJSPrimitiveValue(frame->argTypes()[i], frame->argv()[i]); + if (isValid) + new (arg) QJSPrimitiveValue(frameType, frameVal); else new (arg) QJSPrimitiveValue(); } else { + argumentType.construct(arg); - if (frame->argc() > i) - QMetaType::convert(frame->argTypes()[i], frame->argv()[i], argumentType, arg); + if (isValid) + QMetaType::convert(frameType, frameVal, argumentType, arg); } transformedArguments[i] = arg; @@ -453,12 +475,19 @@ void VME::exec(MetaTypesStackFrame *frame, ExecutionEngine *engine) const QMetaType returnType = function->typedFunction->returnType; const QMetaType frameReturn = frame->returnType(); + bool returnsQVariantWrapper = false; Q_ALLOCA_DECLARE(void, transformedResult); if (frame->returnValue() && returnType != frameReturn) { - if (returnType.sizeOf() > 0) + if (frameReturn == QMetaType::fromType()) { + void *returnValue = frame->returnValue(); + new (returnValue) QVariant(returnType); + transformedResult = static_cast(returnValue)->data(); + returnsQVariantWrapper = true; + } else if (returnType.sizeOf() > 0) { Q_ALLOCA_ASSIGN(void, transformedResult, returnType.sizeOf()); - else + } else { transformedResult = frame; // Some non-null marker value + } } QQmlPrivate::AOTCompiledContext aotContext; @@ -475,7 +504,7 @@ void VME::exec(MetaTypesStackFrame *frame, ExecutionEngine *engine) &aotContext, transformedResult ? transformedResult : frame->returnValue(), transformedArguments ? transformedArguments : frame->argv()); - if (transformedResult) { + if (transformedResult && !returnsQVariantWrapper) { // Shortcut the common case of the AOT function returning a more generic QObject pointer // that we need to QObject-cast. No need to construct or destruct anything in that case. if ((frameReturn.flags() & QMetaType::PointerToQObject) diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index bdebba53d0..4304934019 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -880,7 +880,8 @@ static bool inherits(const QQmlPropertyCache *descendent, const QQmlPropertyCach enum class ObjectPropertyResult { OK, NeedsInit, Deleted }; -static ObjectPropertyResult loadObjectProperty( +template +ObjectPropertyResult loadObjectProperty( QV4::Lookup *l, QObject *object, void *target, QQmlContextData *qmlContext) { QQmlData *qmlData = QQmlData::get(object); @@ -890,8 +891,12 @@ static ObjectPropertyResult loadObjectProperty( return ObjectPropertyResult::Deleted; Q_ASSERT(!QQmlData::wasDeleted(object)); const QQmlPropertyCache *propertyCache = l->qobjectLookup.propertyCache; - if (!inherits(qmlData->propertyCache.data(), propertyCache)) + if (StrictType) { + if (qmlData->propertyCache.data() != propertyCache) + return ObjectPropertyResult::NeedsInit; + } else if (!inherits(qmlData->propertyCache.data(), propertyCache)) { return ObjectPropertyResult::NeedsInit; + } const QQmlPropertyData *property = l->qobjectLookup.propertyData; const int coreIndex = property->coreIndex(); @@ -930,7 +935,35 @@ static ObjectPropertyResult loadFallbackProperty( return ObjectPropertyResult::OK; } -template +ObjectPropertyResult loadObjectAsVariant( + QV4::Lookup *l, QObject *object, void *target, QQmlContextData *qmlContext) +{ + QVariant *variant = static_cast(target); + const QMetaType propType = l->qobjectLookup.propertyData->propType(); + if (propType == QMetaType::fromType()) + return loadObjectProperty(l, object, variant, qmlContext); + + *variant = QVariant(propType); + return loadObjectProperty(l, object, variant->data(), qmlContext); +} + +ObjectPropertyResult loadFallbackAsVariant( + QV4::Lookup *l, QObject *object, void *target, QQmlContextData *qmlContext) +{ + const QMetaObject *metaObject + = reinterpret_cast(l->qobjectFallbackLookup.metaObject - 1); + Q_ASSERT(metaObject); + + QVariant *variant = static_cast(target); + const QMetaType propType = metaObject->property(l->qobjectFallbackLookup.coreIndex).metaType(); + if (propType == QMetaType::fromType()) + return loadFallbackProperty(l, object, variant, qmlContext); + + *variant = QVariant(propType); + return loadFallbackProperty(l, object, variant->data(), qmlContext); +} + +template static ObjectPropertyResult changeObjectProperty(QV4::Lookup *l, QObject *object, Op op) { const QQmlData *qmlData = QQmlData::get(object); @@ -939,24 +972,30 @@ static ObjectPropertyResult changeObjectProperty(QV4::Lookup *l, QObject *object if (qmlData->isQueuedForDeletion) return ObjectPropertyResult::Deleted; Q_ASSERT(!QQmlData::wasDeleted(object)); - if (!inherits(qmlData->propertyCache.data(), l->qobjectLookup.propertyCache)) + if (StrictType) { + if (qmlData->propertyCache.data() != l->qobjectLookup.propertyCache) + return ObjectPropertyResult::NeedsInit; + } else if (!inherits(qmlData->propertyCache.data(), l->qobjectLookup.propertyCache)) { return ObjectPropertyResult::NeedsInit; + } const QQmlPropertyData *property = l->qobjectLookup.propertyData; QQmlPropertyPrivate::removeBinding(object, QQmlPropertyIndex(property->coreIndex())); op(property); return ObjectPropertyResult::OK; } +template static ObjectPropertyResult resetObjectProperty(QV4::Lookup *l, QObject *object) { - return changeObjectProperty(l, object, [&](const QQmlPropertyData *property) { + return changeObjectProperty(l, object, [&](const QQmlPropertyData *property) { property->resetProperty(object, {}); }); } +template static ObjectPropertyResult storeObjectProperty(QV4::Lookup *l, QObject *object, void *value) { - return changeObjectProperty(l, object, [&](const QQmlPropertyData *property) { + return changeObjectProperty(l, object, [&](const QQmlPropertyData *property) { property->writeProperty(object, value, {}); }); } @@ -1061,10 +1100,55 @@ static bool isTypeCompatible(QMetaType lookupType, QMetaType propertyType) return true; } +static ObjectPropertyResult storeObjectAsVariant( + QV4::ExecutionEngine *v4, QV4::Lookup *l, QObject *object, void *value) +{ + QVariant *variant = static_cast(value); + const QMetaType propType = l->qobjectLookup.propertyData->propType(); + if (propType == QMetaType::fromType()) + return storeObjectProperty(l, object, variant); + + if (!variant->isValid()) + return resetObjectProperty(l, object); + + if (isTypeCompatible(variant->metaType(), propType)) + return storeObjectProperty(l, object, variant->data()); + + QVariant converted(propType); + v4->metaTypeFromJS(v4->fromVariant(*variant), propType, converted.data()); + return storeObjectProperty(l, object, converted.data()); +} + +static ObjectPropertyResult storeFallbackAsVariant( + QV4::ExecutionEngine *v4, QV4::Lookup *l, QObject *object, void *value) +{ + QVariant *variant = static_cast(value); + + const QMetaObject *metaObject + = reinterpret_cast(l->qobjectFallbackLookup.metaObject - 1); + Q_ASSERT(metaObject); + + const QMetaType propType = metaObject->property(l->qobjectFallbackLookup.coreIndex).metaType(); + if (propType == QMetaType::fromType()) + return storeFallbackProperty(l, object, variant); + + if (!propType.isValid()) + return resetFallbackProperty(l, object); + + if (isTypeCompatible(variant->metaType(), propType)) + return storeFallbackProperty(l, object, variant->data()); + + QVariant converted(propType); + v4->metaTypeFromJS(v4->fromVariant(*variant), propType, converted.data()); + return storeFallbackProperty(l, object, converted.data()); +} + enum class ObjectLookupResult { Failure, Object, - Fallback + Fallback, + ObjectAsVariant, + FallbackAsVariant, }; static ObjectLookupResult initObjectLookup( @@ -1094,6 +1178,7 @@ static ObjectLookupResult initObjectLookup( name.getPointer(), object, aotContext->qmlContext); } + const bool doVariantLookup = type == QMetaType::fromType(); if (!property) { const QMetaObject *metaObject = object->metaObject(); if (!metaObject) @@ -1105,7 +1190,7 @@ static ObjectLookupResult initObjectLookup( return ObjectLookupResult::Failure; const QMetaProperty property = metaObject->property(coreIndex); - if (!isTypeCompatible(type, property.metaType())) + if (!doVariantLookup && !isTypeCompatible(type, property.metaType())) return ObjectLookupResult::Failure; l->releasePropertyCache(); @@ -1115,16 +1200,21 @@ static ObjectLookupResult initObjectLookup( l->qobjectFallbackLookup.notifyIndex = QMetaObjectPrivate::signalIndex(property.notifySignal()); l->qobjectFallbackLookup.isConstant = property.isConstant() ? 1 : 0; - return ObjectLookupResult::Fallback; + return doVariantLookup + ? ObjectLookupResult::FallbackAsVariant + : ObjectLookupResult::Fallback; } - if (!isTypeCompatible(type, property->propType())) + if (!doVariantLookup && !isTypeCompatible(type, property->propType())) return ObjectLookupResult::Failure; Q_ASSERT(ddata->propertyCache); QV4::setupQObjectLookup(l, ddata, property); - return ObjectLookupResult::Object; + + return doVariantLookup + ? ObjectLookupResult::ObjectAsVariant + : ObjectLookupResult::Object; } static bool initValueLookup(QV4::Lookup *l, QV4::ExecutableCompilationUnit *compilationUnit, @@ -1161,14 +1251,16 @@ bool AOTCompiledContext::captureLookup(uint index, QObject *object) const QV4::Lookup *l = compilationUnit->runtimeLookups + index; if (l->getter == QV4::QQmlTypeWrapper::lookupSingletonProperty - || l->getter == QV4::Lookup::getterQObject) { + || l->getter == QV4::Lookup::getterQObject + || l->getter == QV4::Lookup::getterQObjectAsVariant) { const QQmlPropertyData *property = l->qobjectLookup.propertyData; QQmlData::flushPendingBinding(object, property->coreIndex()); captureObjectProperty(object, l->qobjectLookup.propertyCache, property, qmlContext); return true; } - if (l->getter == QV4::Lookup::getterFallback) { + if (l->getter == QV4::Lookup::getterFallback + || l->getter == QV4::Lookup::getterFallbackAsVariant) { const int coreIndex = l->qobjectFallbackLookup.coreIndex; QQmlData::flushPendingBinding(object, coreIndex); captureFallbackProperty( @@ -1224,7 +1316,9 @@ QMetaType AOTCompiledContext::lookupResultMetaType(uint index) const || l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupContextObjectProperty || l->getter == QV4::QQmlTypeWrapper::lookupSingletonProperty || l->getter == QV4::Lookup::getterQObject - || l->setter == QV4::Lookup::setterQObject) { + || l->setter == QV4::Lookup::setterQObject + || l->getter == QV4::Lookup::getterQObjectAsVariant + || l->setter == QV4::Lookup::setterQObjectAsVariant) { return l->qobjectLookup.propertyData->propType(); } else if (l->getter == QV4::QQmlValueTypeWrapper::lookupGetter) { return QMetaType(l->qgadgetLookup.metaType); @@ -1237,6 +1331,8 @@ QMetaType AOTCompiledContext::lookupResultMetaType(uint index) const return QMetaType::fromType(); } else if (l->getter == QV4::Lookup::getterFallback || l->setter == QV4::Lookup::setterFallback + || l->getter == QV4::Lookup::getterFallbackAsVariant + || l->setter == QV4::Lookup::setterFallbackAsVariant || l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeFallbackProperty) { const QMetaObject *metaObject @@ -1272,33 +1368,37 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType l.forCall = false; ObjectPropertyResult storeResult = ObjectPropertyResult::NeedsInit; switch (initObjectLookup(this, &l, qmlScopeObject, QMetaType())) { + case ObjectLookupResult::ObjectAsVariant: case ObjectLookupResult::Object: { const QMetaType propType = l.qobjectLookup.propertyData->propType(); - if (type == propType) { + if (isTypeCompatible(type, propType)) { storeResult = storeObjectProperty(&l, qmlScopeObject, value); } else if (isUndefined(value, type)) { storeResult = resetObjectProperty(&l, qmlScopeObject); } else { QVariant var(propType); - propType.convert(type, value, propType, var.data()); + QV4::ExecutionEngine *v4 = engine->handle(); + v4->metaTypeFromJS(v4->metaTypeToJS(type, value), propType, var.data()); storeResult = storeObjectProperty(&l, qmlScopeObject, var.data()); } l.qobjectLookup.propertyCache->release(); break; } + case ObjectLookupResult::FallbackAsVariant: case ObjectLookupResult::Fallback: { const QMetaObject *metaObject = reinterpret_cast(l.qobjectFallbackLookup.metaObject - 1); const QMetaType propType = metaObject->property(l.qobjectFallbackLookup.coreIndex).metaType(); - if (type == propType) { + if (isTypeCompatible(type, propType)) { storeResult = storeFallbackProperty(&l, qmlScopeObject, value); } else if (isUndefined(value, type)) { storeResult = resetFallbackProperty(&l, qmlScopeObject); } else { QVariant var(propType); - propType.convert(type, value, propType, var.data()); + QV4::ExecutionEngine *v4 = engine->handle(); + v4->metaTypeFromJS(v4->metaTypeToJS(type, value), propType, var.data()); storeResult = storeFallbackProperty(&l, qmlScopeObject, var.data()); } break; @@ -1574,9 +1674,11 @@ void AOTCompiledContext::initLoadScopeObjectPropertyLookup(uint index, QMetaType } switch (initObjectLookup(this, l, qmlScopeObject, type)) { + case ObjectLookupResult::ObjectAsVariant: case ObjectLookupResult::Object: l->qmlContextPropertyGetter = QV4::QQmlContextWrapper::lookupScopeObjectProperty; break; + case ObjectLookupResult::FallbackAsVariant: case ObjectLookupResult::Fallback: l->qmlContextPropertyGetter = QV4::QQmlContextWrapper::lookupScopeFallbackProperty; break; @@ -1742,6 +1844,10 @@ bool AOTCompiledContext::getObjectLookup(uint index, QObject *object, void *targ result = loadObjectProperty(l, object, target, qmlContext); else if (l->getter == QV4::Lookup::getterFallback) result = loadFallbackProperty(l, object, target, qmlContext); + else if (l->getter == QV4::Lookup::getterQObjectAsVariant) + result = loadObjectAsVariant(l, object, target, qmlContext); + else if (l->getter == QV4::Lookup::getterFallbackAsVariant) + result = loadFallbackAsVariant(l, object, target, qmlContext); else return false; @@ -1768,9 +1874,15 @@ void AOTCompiledContext::initGetObjectLookup(uint index, QObject *object, QMetaT case ObjectLookupResult::Object: l->getter = QV4::Lookup::getterQObject; break; + case ObjectLookupResult::ObjectAsVariant: + l->getter = QV4::Lookup::getterQObjectAsVariant; + break; case ObjectLookupResult::Fallback: l->getter = QV4::Lookup::getterFallback; break; + case ObjectLookupResult::FallbackAsVariant: + l->getter = QV4::Lookup::getterFallbackAsVariant; + break; case ObjectLookupResult::Failure: engine->handle()->throwTypeError(); break; @@ -1884,6 +1996,10 @@ bool AOTCompiledContext::setObjectLookup(uint index, QObject *object, void *valu result = storeObjectProperty(l, object, value); else if (l->setter == QV4::Lookup::setterFallback) result = storeFallbackProperty(l, object, value); + else if (l->setter == QV4::Lookup::setterQObjectAsVariant) + result = storeObjectAsVariant(engine->handle(), l, object, value); + else if (l->setter == QV4::Lookup::setterFallbackAsVariant) + result = storeFallbackAsVariant(engine->handle(), l, object, value); else return false; @@ -1910,9 +2026,15 @@ void AOTCompiledContext::initSetObjectLookup(uint index, QObject *object, QMetaT case ObjectLookupResult::Object: l->setter = QV4::Lookup::setterQObject; break; + case ObjectLookupResult::ObjectAsVariant: + l->setter = QV4::Lookup::setterQObjectAsVariant; + break; case ObjectLookupResult::Fallback: l->setter = QV4::Lookup::setterFallback; break; + case ObjectLookupResult::FallbackAsVariant: + l->setter = QV4::Lookup::setterFallbackAsVariant; + break; case ObjectLookupResult::Failure: engine->handle()->throwTypeError(); break; diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index 724ac50ca8..cbec62114b 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -1047,6 +1047,13 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index) // that would be expensive. reject(u"attached object for non-QObject type"_s); } + + if (!m_state.accumulatorIn().storedType()->isReferenceType()) { + // This can happen if we retroactively determine that the property might not be + // what we think it is (ie, it can be shadowed). + reject(u"attached object of potentially non-QObject base"_s); + } + rejectIfNonQObjectOut(u"non-QObject attached type"_s); const QString lookup = u"aotContext->loadAttachedLookup("_s + indexString diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp index e946e622c0..63de0358ae 100644 --- a/src/qmlcompiler/qqmljscompiler.cpp +++ b/src/qmlcompiler/qqmljscompiler.cpp @@ -779,13 +779,13 @@ QQmlJSAotFunction QQmlJSAotCompiler::doCompile( if (error->isValid()) return compileError(); - QQmlJSBasicBlocks basicBlocks(m_unitGenerator, &m_typeResolver, m_logger); - typePropagationResult = basicBlocks.run(function, typePropagationResult, error); + QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger); + shadowCheck.run(&typePropagationResult, function, error); if (error->isValid()) return compileError(); - QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger); - shadowCheck.run(&typePropagationResult, function, error); + QQmlJSBasicBlocks basicBlocks(m_unitGenerator, &m_typeResolver, m_logger); + typePropagationResult = basicBlocks.run(function, typePropagationResult, error); if (error->isValid()) return compileError(); diff --git a/src/qmlcompiler/qqmljsshadowcheck.cpp b/src/qmlcompiler/qqmljsshadowcheck.cpp index 7ccef5e7bb..cce320f27d 100644 --- a/src/qmlcompiler/qqmljsshadowcheck.cpp +++ b/src/qmlcompiler/qqmljsshadowcheck.cpp @@ -26,10 +26,16 @@ using namespace Qt::StringLiterals; * 3. The property is declared final. * 4. The object we are retrieving the property from is a value type. Value * types cannot be used polymorphically. + * + * If the property is potentially shadowed, we can still retrieve it, but we + * don't know its type. We should assume "var" then. + * + * All of the above also holds for methods. There we have to transform the + * arguments and return types into "var". */ void QQmlJSShadowCheck::run( - const InstructionAnnotations *annotations, const Function *function, + InstructionAnnotations *annotations, const Function *function, QQmlJS::DiagnosticMessage *error) { m_annotations = annotations; @@ -45,8 +51,11 @@ void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex) return; // enum lookup cannot be shadowed. auto accumulatorIn = m_state.registers.find(Accumulator); - if (accumulatorIn != m_state.registers.end()) - checkShadowing(accumulatorIn.value().content, m_jsUnitGenerator->stringForIndex(nameIndex)); + if (accumulatorIn != m_state.registers.end()) { + checkShadowing( + accumulatorIn.value().content, m_jsUnitGenerator->stringForIndex(nameIndex), + Accumulator); + } } void QQmlJSShadowCheck::generate_GetLookup(int index) @@ -55,18 +64,35 @@ void QQmlJSShadowCheck::generate_GetLookup(int index) return; // enum lookup cannot be shadowed. auto accumulatorIn = m_state.registers.find(Accumulator); - if (accumulatorIn != m_state.registers.end()) - checkShadowing(accumulatorIn.value().content, m_jsUnitGenerator->lookupName(index)); + if (accumulatorIn != m_state.registers.end()) { + checkShadowing( + accumulatorIn.value().content, m_jsUnitGenerator->lookupName(index), Accumulator); + } } void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base) { - checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->stringForIndex(nameIndex)); + checkShadowing( + m_state.registers[base].content, m_jsUnitGenerator->stringForIndex(nameIndex), base); } void QQmlJSShadowCheck::generate_SetLookup(int index, int base) { - checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(index)); + checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(index), base); +} + +void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv) +{ + Q_UNUSED(argc); + Q_UNUSED(argv); + checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(nameIndex), base); +} + +void QQmlJSShadowCheck::generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv) +{ + Q_UNUSED(argc); + Q_UNUSED(argv); + checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(nameIndex), base); } QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type) @@ -82,7 +108,7 @@ void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type) } void QQmlJSShadowCheck::checkShadowing( - const QQmlJSRegisterContent &baseType, const QString &memberName) + const QQmlJSRegisterContent &baseType, const QString &memberName, int baseRegister) { if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) return; @@ -110,8 +136,30 @@ void QQmlJSShadowCheck::checkShadowing( return; // Only properties and methods can be shadowed } - setError(u"Member %1 of %2 can be shadowed"_s - .arg(memberName, m_state.accumulatorIn().descriptiveName())); + m_logger->log( + u"Member %1 of %2 can be shadowed"_s.arg( + memberName, m_state.accumulatorIn().descriptiveName()), + qmlCompiler, currentSourceLocation()); + + // Make it "var". We don't know what it is. + const QQmlJSScope::ConstPtr varType = m_typeResolver->varType(); + const QQmlJSRegisterContent varContent = m_typeResolver->globalType(varType); + InstructionAnnotation ¤tAnnotation = (*m_annotations)[currentInstructionOffset()]; + + if (currentAnnotation.changedRegisterIndex != InvalidRegister) { + m_typeResolver->adjustOriginalType( + currentAnnotation.changedRegister.storedType(), varType); + m_typeResolver->adjustOriginalType( + m_typeResolver->containedType(currentAnnotation.changedRegister), varType); + } + + for (auto it = currentAnnotation.readRegisters.begin(), + end = currentAnnotation.readRegisters.end(); + it != end; ++it) { + if (it.key() != baseRegister) + it->second.content = m_typeResolver->convert(it->second.content, varContent); + } + return; } default: diff --git a/src/qmlcompiler/qqmljsshadowcheck_p.h b/src/qmlcompiler/qqmljsshadowcheck_p.h index 92990a9eeb..f583c5f072 100644 --- a/src/qmlcompiler/qqmljsshadowcheck_p.h +++ b/src/qmlcompiler/qqmljsshadowcheck_p.h @@ -28,7 +28,7 @@ public: ~QQmlJSShadowCheck() = default; - void run(const InstructionAnnotations *annotations, const Function *function, + void run(InstructionAnnotations *annotations, const Function *function, QQmlJS::DiagnosticMessage *error); private: @@ -36,13 +36,16 @@ private: void generate_GetLookup(int index) override; void generate_StoreProperty(int nameIndex, int base) override; void generate_SetLookup(int index, int base) override; + void generate_CallProperty(int nameIndex, int base, int argc, int argv) override; + void generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv) override; QV4::Moth::ByteCodeHandler::Verdict startInstruction(QV4::Moth::Instr::Type) override; void endInstruction(QV4::Moth::Instr::Type) override; - void checkShadowing(const QQmlJSRegisterContent &baseType, const QString &propertyName); + void checkShadowing( + const QQmlJSRegisterContent &baseType, const QString &propertyName, int baseRegister); - const InstructionAnnotations *m_annotations = nullptr; + InstructionAnnotations *m_annotations = nullptr; State m_state; }; diff --git a/src/qmlcompiler/qqmljstyperesolver.cpp b/src/qmlcompiler/qqmljstyperesolver.cpp index 984df8cdaf..d739b24d41 100644 --- a/src/qmlcompiler/qqmljstyperesolver.cpp +++ b/src/qmlcompiler/qqmljstyperesolver.cpp @@ -515,6 +515,19 @@ bool QQmlJSTypeResolver::adjustTrackedType( return true; } +void QQmlJSTypeResolver::adjustOriginalType( + const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const +{ + if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) + return; + + const auto it = m_trackedTypes->find(tracked); + Q_ASSERT(it != m_trackedTypes->end()); + + it->original = conversion; + *it->clone = std::move(*QQmlJSScope::clone(conversion)); +} + void QQmlJSTypeResolver::generalizeType(const QQmlJSScope::ConstPtr &type) const { if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) diff --git a/src/qmlcompiler/qqmljstyperesolver_p.h b/src/qmlcompiler/qqmljstyperesolver_p.h index 1879f8db25..71f140c089 100644 --- a/src/qmlcompiler/qqmljstyperesolver_p.h +++ b/src/qmlcompiler/qqmljstyperesolver_p.h @@ -140,6 +140,8 @@ public: [[nodiscard]] bool adjustTrackedType( const QQmlJSScope::ConstPtr &tracked, const QList &conversions) const; + void adjustOriginalType( + const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const; void generalizeType(const QQmlJSScope::ConstPtr &type) const; void setParentMode(ParentMode mode) { m_parentMode = mode; } diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 85ac7e6e2e..48fcf3bec0 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -181,6 +181,7 @@ set(qml_files scopeVsObject.qml script.js script.mjs + shadowedMethod.qml shared/Slider.qml shifts.qml signal.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/shadowedMethod.qml b/tests/auto/qml/qmlcppcodegen/data/shadowedMethod.qml new file mode 100644 index 0000000000..590fb40b17 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/shadowedMethod.qml @@ -0,0 +1,35 @@ +pragma Strict +import QtQuick + +Item { + component B: Item { + function contains(point: point) : string { + return "b" + } + } + + + component C: Item { + function contains(point: point) : string { + return "c" + } + } + + property Item a: Item {} + property B b: B {} + property C c: C {} + + function doThing() : var { return a.contains(Qt.point(0, 0)) } + + property var athing; + property var bthing; + property var cthing; + + Component.onCompleted: { + athing = doThing(); + a = b; + bthing = doThing(); + a = c; + cthing = doThing(); + } +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 25002acd3f..e3292b52f3 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -183,6 +183,7 @@ private slots: void jsArrayMethods(); void jsArrayMethodsWithParams_data(); void jsArrayMethodsWithParams(); + void shadowedMethod(); }; void tst_QmlCppCodegen::initTestCase() @@ -3754,6 +3755,18 @@ void tst_QmlCppCodegen::jsArrayMethodsWithParams() QCOMPARE(object->property("listPropertyLastIndexOf"), check->property("jsArrayLastIndexOf")); } +void tst_QmlCppCodegen::shadowedMethod() +{ + QQmlEngine e; + QQmlComponent c(&e, QUrl(u"qrc:/qt/qml/TestTypes/shadowedMethod.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("athing"), QVariant::fromValue(false)); + QCOMPARE(o->property("bthing"), QVariant::fromValue(u"b"_s)); + QCOMPARE(o->property("cthing"), QVariant::fromValue(u"c"_s)); +} + QTEST_MAIN(tst_QmlCppCodegen) #include "tst_qmlcppcodegen.moc"