diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2024-04-16 17:03:01 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2024-04-26 12:18:15 +0000 |
commit | 8bf5aae19b77b618f3f7a55a59e87c8a319475a8 (patch) | |
tree | d331328f478ac13593524eaaeb3a874691ccadd2 | |
parent | 23fc22e16022e355f2a1aff8705c09b807fbe024 (diff) |
QtQml: Properly enforce signatures of AOT-compiled functions
Pass the metatypes of the contained types rather than the stored types.
[ChangeLog][QtQml][Important Behavior Changes] The AOT compiled code for
type-annotated JavaScript functions does not let you pass or return
values of the wrong type anymore.
Fixes: QTBUG-119885
Change-Id: I685d398c0745d32a999a3abd76c622a2c0d6651f
Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
24 files changed, 321 insertions, 141 deletions
diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index caedd8928e..79df230872 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE // Also change the comment behind the number to describe the latest change. This has the added // benefit that if another patch changes the version too, it will result in a merge conflict, and // not get removed silently. -#define QV4_DATA_STRUCTURE_VERSION 0x41 // Change signature of AOT compiled functions +#define QV4_DATA_STRUCTURE_VERSION 0x42 // Change metatype computation of AOT-compiled functions class QIODevice; class QQmlTypeNameCache; diff --git a/src/qml/jsruntime/qv4executablecompilationunit.cpp b/src/qml/jsruntime/qv4executablecompilationunit.cpp index 9e10e437a8..34d737cdae 100644 --- a/src/qml/jsruntime/qv4executablecompilationunit.cpp +++ b/src/qml/jsruntime/qv4executablecompilationunit.cpp @@ -157,7 +157,7 @@ void ExecutableCompilationUnit::populate() auto advanceAotFunction = [&](int i) -> const QQmlPrivate::AOTCompiledFunction * { if (aotFunction) { if (aotFunction->functionPtr) { - if (aotFunction->extraData == i) + if (aotFunction->functionIndex == i) return aotFunction++; } else { aotFunction = nullptr; diff --git a/src/qml/jsruntime/qv4function.cpp b/src/qml/jsruntime/qv4function.cpp index caba2b1a9a..ae36b563e0 100644 --- a/src/qml/jsruntime/qv4function.cpp +++ b/src/qml/jsruntime/qv4function.cpp @@ -61,14 +61,14 @@ ReturnedValue Function::call( switch (kind) { case AotCompiled: return QV4::convertAndCall( - context->engine(), aotCompiledFunction, thisObject, argv, argc, + context->engine(), &aotCompiledFunction, thisObject, argv, argc, [this, context]( QObject *thisObject, void **a, const QMetaType *types, int argc) { call(thisObject, a, types, argc, context); }); case JsTyped: return QV4::coerceAndCall( - context->engine(), jsTypedFunction, compiledFunction, argv, argc, + context->engine(), &jsTypedFunction, compiledFunction, argv, argc, [this, context, thisObject](const Value *argv, int argc) { return doCall(this, thisObject, argv, argc, context); }); @@ -109,10 +109,6 @@ Function::Function(ExecutionEngine *engine, ExecutableCompilationUnit *unit, : FunctionData(engine, unit) , compiledFunction(function) , codeData(function->code()) - , jittedCode(nullptr) - , codeRef(nullptr) - , aotCompiledFunction(aotFunction) - , kind(aotFunction ? AotCompiled : JsUntyped) { Scope scope(engine); Scoped<InternalClass> ic(scope, engine->internalClasses(EngineBase::Class_CallContext)); @@ -123,7 +119,7 @@ Function::Function(ExecutionEngine *engine, ExecutableCompilationUnit *unit, ic = ic->addMember(engine->identifierTable->asPropertyKey(compilationUnit->runtimeStrings[localsIndices[i]]), Attr_NotConfigurable); const CompiledData::Parameter *formalsIndices = compiledFunction->formalsTable(); - bool enforceJsTypes = !aotFunction && !unit->ignoresFunctionSignature(); + bool enforceJsTypes = !unit->ignoresFunctionSignature(); for (quint32 i = 0; i < compiledFunction->nFormals; ++i) { ic = ic->addMember(engine->identifierTable->asPropertyKey(compilationUnit->runtimeStrings[formalsIndices[i].nameIndex]), Attr_NotConfigurable); @@ -134,14 +130,24 @@ Function::Function(ExecutionEngine *engine, ExecutableCompilationUnit *unit, nFormals = compiledFunction->nFormals; + if (!enforceJsTypes) + return; + + if (aotFunction) { + aotCompiledCode = aotFunction->functionPtr; + new (&aotCompiledFunction) AOTCompiledFunction; + kind = AotCompiled; + aotCompiledFunction.types.resize(aotFunction->numArguments + 1); + aotFunction->signature(unit, aotCompiledFunction.types.data()); + return; + } + // If a function has any typed arguments, but an untyped return value, the return value is void. // If it doesn't have any arguments at all and the return value is untyped, the function is // untyped. Users can specifically set the return type to "void" to have it enforced. - if (!enforceJsTypes || (nFormals == 0 && !isSpecificType(compiledFunction->returnType))) + if (nFormals == 0 && !isSpecificType(compiledFunction->returnType)) return; - JSTypedFunction *synthesized = new JSTypedFunction; - QQmlTypeLoader *typeLoader = engine->typeLoader(); auto findQmlType = [&](const CompiledData::ParameterType ¶m) { @@ -151,37 +157,21 @@ Function::Function(ExecutionEngine *engine, ExecutableCompilationUnit *unit, QV4::CompiledData::CommonType(type))); } - if (type == 0) + if (type == 0 || !typeLoader) return QQmlType(); - const auto base = unit->baseCompilationUnit(); - const QQmlType qmltype = typeLoader - ? base->typeNameCache->query<QQmlImport::AllowRecursion>( - base->stringAt(type), typeLoader).type - : QQmlType(); - - if (!qmltype.isValid() || qmltype.isComposite()) - return qmltype; - - if (qmltype.isInlineComponentType()) { - Q_ASSERT(qmltype.typeId().isValid()); - - // If it seems to be an IC type, make sure there is an actual - // compilation unit for it. We create inline component types speculatively. - return QQmlMetaType::obtainCompilationUnit(qmltype.typeId()) - ? qmltype - : QQmlType(); - } - + const auto &base = unit->baseCompilationUnit(); + const QQmlType qmltype = QQmlTypePrivate::compositeQmlType( + base, typeLoader, base->stringAt(type)); return qmltype.typeId().isValid() ? qmltype : QQmlType(); }; - for (quint16 i = 0; i < nFormals; ++i) - synthesized->argumentTypes.append(findQmlType(formalsIndices[i].type)); - - synthesized->returnType = findQmlType(compiledFunction->returnType); - jsTypedFunction = synthesized; + new (&jsTypedFunction) JSTypedFunction; kind = JsTyped; + jsTypedFunction.types.reserve(nFormals + 1); + jsTypedFunction.types.append(findQmlType(compiledFunction->returnType)); + for (quint16 i = 0; i < nFormals; ++i) + jsTypedFunction.types.append(findQmlType(formalsIndices[i].type)); } Function::~Function() @@ -190,8 +180,18 @@ Function::~Function() destroyFunctionTable(this, codeRef); delete codeRef; } - if (kind == JsTyped) - delete jsTypedFunction; + + switch (kind) { + case JsTyped: + jsTypedFunction.~JSTypedFunction(); + break; + case AotCompiled: + aotCompiledFunction.~AOTCompiledFunction(); + break; + case JsUntyped: + case Eval: + break; + } } void Function::updateInternalClass(ExecutionEngine *engine, const QList<QByteArray> ¶meters) diff --git a/src/qml/jsruntime/qv4function_p.h b/src/qml/jsruntime/qv4function_p.h index 3c9617f359..7543dd3c4b 100644 --- a/src/qml/jsruntime/qv4function_p.h +++ b/src/qml/jsruntime/qv4function_p.h @@ -51,11 +51,12 @@ protected: public: struct JSTypedFunction { - QList<QQmlType> argumentTypes; - QQmlType returnType; + QVarLengthArray<QQmlType, 4> types; }; - const CompiledData::Function *compiledFunction; + struct AOTCompiledFunction { + QVarLengthArray<QMetaType, 4> types; + }; QV4::ExecutableCompilationUnit *executableCompilationUnit() const { @@ -73,20 +74,28 @@ public: ReturnedValue call(const Value *thisObject, const Value *argv, int argc, ExecutionContext *context); - const char *codeData; + const CompiledData::Function *compiledFunction = nullptr; + const char *codeData = nullptr; + JSC::MacroAssemblerCodeRef *codeRef = nullptr; typedef ReturnedValue (*JittedCode)(CppStackFrame *, ExecutionEngine *); - JittedCode jittedCode; - JSC::MacroAssemblerCodeRef *codeRef; + typedef void (*AotCompiledCode)(const QQmlPrivate::AOTCompiledContext *context, void **argv); + + union { + void *noFunction = nullptr; + JSTypedFunction jsTypedFunction; + AOTCompiledFunction aotCompiledFunction; + }; + union { - const QQmlPrivate::AOTCompiledFunction *aotCompiledFunction = nullptr; - const JSTypedFunction *jsTypedFunction; + JittedCode jittedCode = nullptr; + AotCompiledCode aotCompiledCode; }; // first nArguments names in internalClass are the actual arguments QV4::WriteBarrier::Pointer<Heap::InternalClass> internalClass; int interpreterCallCount = 0; - quint16 nFormals; + quint16 nFormals = 0; enum Kind : quint8 { JsUntyped, JsTyped, AotCompiled, Eval }; Kind kind = JsUntyped; bool detectedInjectedParameters = false; diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index b5a3934528..ab6a34435f 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -539,13 +539,13 @@ ReturnedValue ArrowFunction::virtualCall(const QV4::FunctionObject *fo, const Va switch (function->kind) { case Function::AotCompiled: return QV4::convertAndCall( - fo->engine(), function->aotCompiledFunction, thisObject, argv, argc, + fo->engine(), &function->aotCompiledFunction, thisObject, argv, argc, [fo](QObject *thisObject, void **a, const QMetaType *types, int argc) { ArrowFunction::virtualCallWithMetaTypes(fo, thisObject, a, types, argc); }); case Function::JsTyped: return QV4::coerceAndCall( - fo->engine(), function->jsTypedFunction, function->compiledFunction, argv, argc, + fo->engine(), &function->jsTypedFunction, function->compiledFunction, argv, argc, [fo, thisObject](const Value *argv, int argc) { return qfoDoCall(fo, thisObject, argv, argc); }); diff --git a/src/qml/jsruntime/qv4jscall_p.h b/src/qml/jsruntime/qv4jscall_p.h index 59f594c939..ed1ca983ad 100644 --- a/src/qml/jsruntime/qv4jscall_p.h +++ b/src/qml/jsruntime/qv4jscall_p.h @@ -113,15 +113,15 @@ void populateJSCallArguments(ExecutionEngine *v4, JSCallArguments &jsCall, int a template<typename Callable> ReturnedValue convertAndCall( - ExecutionEngine *engine, const QQmlPrivate::AOTCompiledFunction *aotFunction, + ExecutionEngine *engine, const Function::AOTCompiledFunction *aotFunction, const Value *thisObject, const Value *argv, int argc, Callable call) { - const qsizetype numFunctionArguments = aotFunction->argumentTypes.size(); + const qsizetype numFunctionArguments = aotFunction->types.length() - 1; Q_ALLOCA_VAR(void *, values, (numFunctionArguments + 1) * sizeof(void *)); Q_ALLOCA_VAR(QMetaType, types, (numFunctionArguments + 1) * sizeof(QMetaType)); for (qsizetype i = 0; i < numFunctionArguments; ++i) { - const QMetaType argumentType = aotFunction->argumentTypes[i]; + const QMetaType argumentType = aotFunction->types[i + 1]; types[i + 1] = argumentType; if (const qsizetype argumentSize = argumentType.sizeOf()) { Q_ALLOCA_VAR(void, argument, argumentSize); @@ -144,7 +144,7 @@ ReturnedValue convertAndCall( } Q_ALLOCA_DECLARE(void, returnValue); - types[0] = aotFunction->returnType; + types[0] = aotFunction->types[0]; if (const qsizetype returnSize = types[0].sizeOf()) { Q_ALLOCA_ASSIGN(void, returnValue, returnSize); values[0] = returnValue; @@ -412,16 +412,16 @@ ReturnedValue coerceAndCall( { Scope scope(engine); - QV4::JSCallArguments jsCallData(scope, typedFunction->argumentTypes.size()); + QV4::JSCallArguments jsCallData(scope, typedFunction->types.size() - 1); const CompiledData::Parameter *formals = compiledFunction->formalsTable(); for (qsizetype i = 0; i < jsCallData.argc; ++i) { jsCallData.args[i] = coerce( engine, i < argc ? argv[i] : Encode::undefined(), - typedFunction->argumentTypes[i], formals[i].type.isList()); + typedFunction->types[i + 1], formals[i].type.isList()); } ScopedValue result(scope, call(jsCallData.args, jsCallData.argc)); - return coerce(engine, result, typedFunction->returnType, compiledFunction->returnType.isList()); + return coerce(engine, result, typedFunction->types[0], compiledFunction->returnType.isList()); } // Note: \a to is unininitialized here! This is in contrast to most other related functions. diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index db17a7141c..096b9a6299 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -398,16 +398,16 @@ static bool compareEqualInt(QV4::Value &accumulator, QV4::Value lhs, int rhs) struct AOTCompiledMetaMethod { public: - AOTCompiledMetaMethod(const QQmlPrivate::AOTCompiledFunction *aotCompiledFunction) + AOTCompiledMetaMethod(const Function::AOTCompiledFunction *aotCompiledFunction) : aotCompiledFunction(aotCompiledFunction) {} - int parameterCount() const { return aotCompiledFunction->argumentTypes.size(); } - QMetaType returnMetaType() const { return aotCompiledFunction->returnType; } - QMetaType parameterMetaType(int i) const { return aotCompiledFunction->argumentTypes[i]; } + int parameterCount() const { return aotCompiledFunction->types.size() - 1; } + QMetaType returnMetaType() const { return aotCompiledFunction->types[0]; } + QMetaType parameterMetaType(int i) const { return aotCompiledFunction->types[i + 1]; } private: - const QQmlPrivate::AOTCompiledFunction *aotCompiledFunction = nullptr; + const Function::AOTCompiledFunction *aotCompiledFunction = nullptr; }; void VME::exec(MetaTypesStackFrame *frame, ExecutionEngine *engine) @@ -420,14 +420,14 @@ void VME::exec(MetaTypesStackFrame *frame, ExecutionEngine *engine) ExecutionEngineCallDepthRecorder executionEngineCallDepthRecorder(engine); Function *function = frame->v4Function; - Q_ASSERT(function->aotCompiledFunction); + Q_ASSERT(function->aotCompiledCode); Q_TRACE_SCOPE(QQmlV4_function_call, engine, function->name()->toQString(), function->executableCompilationUnit()->fileName(), function->compiledFunction->location.line(), function->compiledFunction->location.column()); Profiling::FunctionCallProfiler profiler(engine, function); // start execution profiling - const AOTCompiledMetaMethod method(function->aotCompiledFunction); + const AOTCompiledMetaMethod method(&function->aotCompiledFunction); QV4::coerceAndCall( engine, &method, frame->returnAndArgValues(), frame->returnAndArgTypes(), frame->argc(), @@ -443,7 +443,7 @@ void VME::exec(MetaTypesStackFrame *frame, ExecutionEngine *engine) aotContext.engine = engine->jsEngine(); aotContext.compilationUnit = function->executableCompilationUnit(); - function->aotCompiledFunction->functionPtr(&aotContext, argv); + function->aotCompiledCode(&aotContext, argv); }); } diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index ff419b0b34..eb716671b1 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -176,6 +176,22 @@ void QQmlPrivate::qmlRegistrationWarning( } } +QMetaType QQmlPrivate::compositeMetaType( + QV4::ExecutableCompilationUnit *unit, const QString &elementName) +{ + return QQmlTypePrivate::compositeQmlType( + unit->baseCompilationUnit(), unit->engine->typeLoader(), elementName) + .typeId(); +} + +QMetaType QQmlPrivate::compositeListMetaType( + QV4::ExecutableCompilationUnit *unit, const QString &elementName) +{ + return QQmlTypePrivate::compositeQmlType( + unit->baseCompilationUnit(), unit->engine->typeLoader(), elementName) + .qListTypeId(); +} + int qmlRegisterUncreatableMetaObject(const QMetaObject &staticMetaObject, const char *uri, int versionMajor, int versionMinor, const char *qmlName, diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index f1bedaf65f..47f8e5c429 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -675,7 +675,7 @@ void QQmlBinding::doUpdate(const DeleteWatcher &watcher, QQmlPropertyData::Write auto canWrite = [&]() { return !watcher.wasDeleted() && isAddedToObject() && !hasError(); }; const QV4::Function *v4Function = function(); if (v4Function && v4Function->kind == QV4::Function::AotCompiled && !hasBoundFunction()) { - const auto returnType = v4Function->aotCompiledFunction->returnType; + const auto returnType = v4Function->aotCompiledFunction.types[0]; if (returnType == QMetaType::fromType<QVariant>()) { QVariant result; const bool isUndefined = !evaluate(&result, returnType); diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index 25dc36d33d..92c9765509 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -740,9 +740,9 @@ namespace QQmlPrivate }; struct AOTCompiledFunction { - qintptr extraData; - QMetaType returnType; - QList<QMetaType> argumentTypes; + int functionIndex; + int numArguments; + void (*signature)(QV4::ExecutableCompilationUnit *unit, QMetaType *argTypes); void (*functionPtr)(const AOTCompiledContext *context, void **argv); }; @@ -1152,6 +1152,11 @@ namespace QQmlPrivate Q_QML_EXPORT void qmlRegistrationWarning(QmlRegistrationWarning warning, QMetaType type); + Q_QML_EXPORT QMetaType compositeMetaType( + QV4::ExecutableCompilationUnit *unit, const QString &elementName); + Q_QML_EXPORT QMetaType compositeListMetaType( + QV4::ExecutableCompilationUnit *unit, const QString &elementName); + } // namespace QQmlPrivate QT_END_NAMESPACE diff --git a/src/qml/qml/qqmltype_p_p.h b/src/qml/qml/qqmltype_p_p.h index 8f614eeac2..2bf83ddb8b 100644 --- a/src/qml/qml/qqmltype_p_p.h +++ b/src/qml/qml/qqmltype_p_p.h @@ -21,6 +21,9 @@ #include <private/qqmlrefcount_p.h> #include <private/qqmlpropertycache_p.h> #include <private/qqmlmetatype_p.h> +#include <private/qqmltypeloader_p.h> +#include <private/qv4executablecompilationunit_p.h> +#include <private/qv4engine_p.h> #include <QAtomicInteger> @@ -231,6 +234,27 @@ public: return nullptr; } + static QQmlType compositeQmlType( + const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit, + QQmlTypeLoader *typeLoader, const QString &type) + { + Q_ASSERT(typeLoader); + + const QQmlType qmltype + = unit->typeNameCache->query<QQmlImport::AllowRecursion>(type, typeLoader).type; + if (!qmltype.isValid()) + return qmltype; + + if (qmltype.isInlineComponentType() + && !QQmlMetaType::obtainCompilationUnit(qmltype.typeId())) { + // If it seems to be an IC type, make sure there is an actual + // compilation unit for it. We create inline component types speculatively. + return QQmlType(); + } + + return qmltype; + } + private: mutable QAtomicPointer<const ProxyMetaObjects> proxyMetaObjects; mutable QAtomicPointer<const Enums> enums; diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index 7b0664f918..e6529a07d9 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -73,22 +73,54 @@ QString QQmlJSCodeGenerator::metaTypeFromName(const QQmlJSScope::ConstPtr &type) + u"\"); return t; }()"_s; } +QString QQmlJSCodeGenerator::compositeListMetaType(const QString &elementName) const +{ + return u"[](auto *aotContext) { static const auto t = QQmlPrivate::compositeListMetaType(" + "aotContext->compilationUnit, \""_s + + elementName + + u"\"); return t; }(aotContext)"_s; +} + +QString QQmlJSCodeGenerator::compositeMetaType(const QString &elementName) const +{ + return u"[](auto *aotContext) { static const auto t = QQmlPrivate::compositeMetaType(" + "aotContext->compilationUnit, \""_s + + elementName + + u"\"); return t; }(aotContext)"_s; +} + QString QQmlJSCodeGenerator::metaObject(const QQmlJSScope::ConstPtr &objectType) { - if (!objectType->isComposite()) { - if (objectType->internalName() == u"QObject"_s - || objectType->internalName() == u"QQmlComponent"_s) { - return u'&' + objectType->internalName() + u"::staticMetaObject"_s; + if (objectType->isComposite()) { + const QString name = m_typeResolver->nameForType(objectType); + if (name.isEmpty()) { + reject(u"retrieving the metaObject of a composite type without an element name."_s); + return QString(); } - return metaTypeFromName(objectType) + u".metaObject()"_s; + return compositeMetaType(name) + u".metaObject()"_s; } - reject(u"retrieving the metaObject of a composite type without using an instance."_s); - return QString(); + if (objectType->internalName() == u"QObject"_s + || objectType->internalName() == u"QQmlComponent"_s) { + return u'&' + objectType->internalName() + u"::staticMetaObject"_s; + } + return metaTypeFromName(objectType) + u".metaObject()"_s; } QString QQmlJSCodeGenerator::metaType(const QQmlJSScope::ConstPtr &type) { + if (type->isComposite()) { + const QString name = m_typeResolver->nameForType(type); + if (!name.isEmpty()) + return compositeMetaType(name); + } + + if (type->isListProperty() && type->valueType()->isComposite()) { + const QString name = m_typeResolver->nameForType(type->valueType()); + if (!name.isEmpty()) + return compositeListMetaType(name); + } + return m_typeResolver->equals(m_typeResolver->genericType(type), type) ? metaTypeFromType(type) : metaTypeFromName(type); @@ -223,24 +255,26 @@ QT_WARNING_POP result.code += m_body; - for (const QQmlJSRegisterContent &argType : std::as_const(function->argumentTypes)) { - if (argType.isValid()) { - result.argumentTypes.append( - m_typeResolver->originalType(argType.storedType()) - ->augmentedInternalName()); - } else { - result.argumentTypes.append(u"void"_s); - } - } - if (function->returnType) { - result.returnType = function->returnType->internalName(); - if (function->returnType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) - result.returnType += u'*'; + QString signature + = u" struct { QV4::ExecutableCompilationUnit *compilationUnit; } c { unit };\n" + " const auto *aotContext = &c;\n" + " Q_UNUSED(aotContext);\n"_s; + + if (function->returnType.isValid()) { + signature += u" argTypes[0] = %1;\n"_s.arg( + metaType(m_typeResolver->containedType(function->returnType))); } else { - result.returnType = u"void"_s; + signature += u" argTypes[0] = QMetaType();\n"_s; + } + result.numArguments = function->argumentTypes.length(); + for (qsizetype i = 0; i != result.numArguments; ++i) { + signature += u" argTypes[%1] = %2;\n"_s.arg( + QString::number(i + 1), + metaType(m_typeResolver->originalContainedType(function->argumentTypes[i]))); } + result.signature = signature; return result; } @@ -250,16 +284,27 @@ void QQmlJSCodeGenerator::generateReturnError() m_body += u"aotContext->setReturnValueUndefined();\n"_s; const auto ret = m_function->returnType; - if (!ret || m_typeResolver->equals(m_function->returnType, m_typeResolver->voidType())) + if (!ret.isValid() || m_typeResolver->registerContains(ret, m_typeResolver->voidType())) return; - const QString value = ret->accessSemantics() == QQmlJSScope::AccessSemantics::Reference - ? convertStored(m_typeResolver->nullType(), ret, QString()) - : ret->internalName() + u"()"_s; - m_body += u"if (argv[0]) {\n"_s; - m_body += u" *static_cast<"_s + m_function->returnType->augmentedInternalName() - + u" *>(argv[0]) = "_s + value + u";\n"_s; + + const auto contained = m_typeResolver->containedType(ret); + const auto stored = ret.storedType(); + if (contained->isReferenceType() && stored->isReferenceType()) { + m_body += u" *static_cast<"_s + + stored->augmentedInternalName() + + u" *>(argv[0]) = nullptr;\n"_s; + } else if (m_typeResolver->equals(contained, stored)) { + m_body += u" *static_cast<"_s + stored->internalName() + u" *>(argv[0]) = "_s + + stored->internalName() + u"();\n"_s; + } else { + m_body += u" const QMetaType returnType = "_s + + metaType(m_typeResolver->containedType(ret)) + u";\n"_s; + m_body += u" returnType.destruct(argv[0]);\n"_s; + m_body += u" returnType.construct(argv[0]);\n "_s; + } + m_body += u"}\n"_s; } @@ -273,7 +318,7 @@ void QQmlJSCodeGenerator::generate_Ret() resetState(); }); - if (!m_function->returnType) + if (!m_function->returnType.isValid()) return; m_body += u"if (argv[0]) {\n"_s; @@ -301,11 +346,36 @@ void QQmlJSCodeGenerator::generate_Ret() m_body += u" "_s + signalUndefined; } - if (!m_typeResolver->equals(m_function->returnType, m_typeResolver->voidType())) { - m_body += u" *static_cast<"_s + m_function->returnType->augmentedInternalName() - +u" *>(argv[0]) = "_s + convertStored( - m_state.accumulatorIn().storedType(), m_function->returnType, in) + if (m_typeResolver->registerContains( + m_function->returnType, m_typeResolver->voidType())) { + m_body += u"}\n"_s; + return; + } + + const auto contained = m_typeResolver->containedType(m_function->returnType); + const auto stored = m_function->returnType.storedType(); + if (m_typeResolver->equals(contained, stored) + || (contained->isReferenceType() && stored->isReferenceType())) { + m_body += u" *static_cast<"_s + + stored->augmentedInternalName() + + u" *>(argv[0]) = "_s + + conversion(m_state.accumulatorIn(), m_function->returnType, in) + + u";\n"_s; + } else if (m_typeResolver->registerContains(m_state.accumulatorIn(), contained)) { + m_body += u" const QMetaType returnType = "_s + contentType(m_state.accumulatorIn(), in) + u";\n"_s; + m_body += u" returnType.destruct(argv[0]);\n"_s; + m_body += u" returnType.construct(argv[0], "_s + + contentPointer(m_state.accumulatorIn(), in) + u");\n"_s; + } else { + m_body += u" const auto converted = "_s + + conversion(m_state.accumulatorIn(), m_function->returnType, in) + u";\n"_s; + m_body += u" const QMetaType returnType = "_s + + contentType(m_function->returnType, u"converted"_s) + + u";\n"_s; + m_body += u" returnType.destruct(argv[0]);\n"_s; + m_body += u" returnType.construct(argv[0], "_s + + contentPointer(m_function->returnType, u"converted"_s) + u");\n"_s; } m_body += u"}\n"_s; @@ -2871,8 +2941,7 @@ QString QQmlJSCodeGenerator::contentPointer(const QQmlJSRegisterContent &content QString QQmlJSCodeGenerator::contentType(const QQmlJSRegisterContent &content, const QString &var) { const QQmlJSScope::ConstPtr stored = content.storedType(); - const QQmlJSScope::ConstPtr contained = QQmlJSScope::nonCompositeBaseType( - m_typeResolver->containedType(content)); + const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(content); if (m_typeResolver->equals(contained, stored)) return metaTypeFromType(stored); @@ -2882,13 +2951,14 @@ QString QQmlJSCodeGenerator::contentType(const QQmlJSRegisterContent &content, c } if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) - return metaTypeFromName(contained); + return metaType(contained); - if (m_typeResolver->isNumeric(stored) && contained->scopeType() == QQmlSA::ScopeType::EnumScope) - return metaTypeFromType(contained->baseType()); + const QQmlJSScope::ConstPtr nonComposite = QQmlJSScope::nonCompositeBaseType(contained); + if (m_typeResolver->isNumeric(stored) && nonComposite->scopeType() == QQmlSA::ScopeType::EnumScope) + return metaTypeFromType(nonComposite->baseType()); - if (stored->isListProperty() && m_typeResolver->containedType(content)->isListProperty()) - return metaTypeFromType(m_typeResolver->listPropertyType()); + if (stored->isListProperty() && contained->isListProperty()) + return metaType(contained); reject(u"content type of unsupported wrapper type "_s + content.descriptiveName()); return QString(); diff --git a/src/qmlcompiler/qqmljscodegenerator_p.h b/src/qmlcompiler/qqmljscodegenerator_p.h index 79f6352551..2edccf31ae 100644 --- a/src/qmlcompiler/qqmljscodegenerator_p.h +++ b/src/qmlcompiler/qqmljscodegenerator_p.h @@ -247,6 +247,9 @@ protected: QString metaTypeFromType(const QQmlJSScope::ConstPtr &type) const; QString metaTypeFromName(const QQmlJSScope::ConstPtr &type) const; + QString compositeMetaType(const QString &elementName) const; + QString compositeListMetaType(const QString &elementName) const; + QString contentPointer(const QQmlJSRegisterContent &content, const QString &var); QString contentType(const QQmlJSRegisterContent &content, const QString &var); diff --git a/src/qmlcompiler/qqmljscompilepass_p.h b/src/qmlcompiler/qqmljscompilepass_p.h index 7a56494744..a18b906d8d 100644 --- a/src/qmlcompiler/qqmljscompilepass_p.h +++ b/src/qmlcompiler/qqmljscompilepass_p.h @@ -94,7 +94,7 @@ public: QQmlJSScopesById addressableScopes; QList<QQmlJSRegisterContent> argumentTypes; QList<QQmlJSRegisterContent> registerTypes; - QQmlJSScope::ConstPtr returnType; + QQmlJSRegisterContent returnType; QQmlJSScope::ConstPtr qmlScope; QByteArray code; const SourceLocationTable *sourceLocations = nullptr; diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp index cca05380c9..8ecc69d1c9 100644 --- a/src/qmlcompiler/qqmljscompiler.cpp +++ b/src/qmlcompiler/qqmljscompiler.cpp @@ -560,7 +560,7 @@ bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFile if (aotFunctions.size() <= 1) { // FileScopeCodeIndex is always there, but it may be the only one. writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n" - "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = { { 0, QMetaType::fromType<void>(), {}, nullptr } };\n"); + "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = { { 0, 0, nullptr, nullptr } };\n"); } else { writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n" "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = {\n"); @@ -574,25 +574,18 @@ bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFile if (func.key() == FileScopeCodeIndex) continue; - QString function = QString::fromUtf8(funcHeaderCode) + func.value().code + footer; + const QString function = QString::fromUtf8(funcHeaderCode) + func.value().code + footer; - QString argumentTypes = func.value().argumentTypes.join( - QStringLiteral(">(), QMetaType::fromType<")); - if (!argumentTypes.isEmpty()) { - argumentTypes = QStringLiteral("QMetaType::fromType<") - + argumentTypes + QStringLiteral(">()"); - } - - writeStr(QStringLiteral("{ %1, QMetaType::fromType<%2>(), { %3 }, %4 },") + writeStr(QStringLiteral("{ %1, %2, [](QV4::ExecutableCompilationUnit *unit, " + "QMetaType *argTypes) {\n%3}, %4 },") .arg(func.key()) - .arg(func.value().returnType) - .arg(argumentTypes) - .arg(function) + .arg(func->numArguments) + .arg(func->signature, function) .toUtf8().constData()); } // Conclude the list with a nullptr - writeStr("{ 0, QMetaType::fromType<void>(), {}, nullptr }"); + writeStr("{ 0, 0, nullptr, nullptr }"); writeStr("};\n"); } diff --git a/src/qmlcompiler/qqmljscompiler_p.h b/src/qmlcompiler/qqmljscompiler_p.h index aace476cf1..e358f76fef 100644 --- a/src/qmlcompiler/qqmljscompiler_p.h +++ b/src/qmlcompiler/qqmljscompiler_p.h @@ -48,9 +48,9 @@ struct Q_QMLCOMPILER_EXPORT QQmlJSCompileError struct Q_QMLCOMPILER_EXPORT QQmlJSAotFunction { QStringList includes; - QStringList argumentTypes; QString code; - QString returnType; + QString signature; + int numArguments = 0; }; class Q_QMLCOMPILER_EXPORT QQmlJSAotCompiler diff --git a/src/qmlcompiler/qqmljscontextualtypes_p.h b/src/qmlcompiler/qqmljscontextualtypes_p.h index 10bbeef3b7..cccd7fd1b0 100644 --- a/src/qmlcompiler/qqmljscontextualtypes_p.h +++ b/src/qmlcompiler/qqmljscontextualtypes_p.h @@ -42,14 +42,23 @@ struct ContextualTypes QQmlJSScope::ConstPtr arrayType() const { return m_arrayType; } bool hasType(const QString &name) const { return m_types.contains(name); } + ImportedScope<QQmlJSScope::ConstPtr> type(const QString &name) const { return m_types[name]; } + QString name(const QQmlJSScope::ConstPtr &type) const { return m_names[type]; } + void setType(const QString &name, const ImportedScope<QQmlJSScope::ConstPtr> &type) { + if (!name.startsWith(u'$')) + m_names.insert(type.scope, name); m_types.insert(name, type); } void clearType(const QString &name) { - m_types[name].scope = QQmlJSScope::ConstPtr(); + auto &scope = m_types[name].scope; + auto it = m_names.constFind(scope); + while (it != m_names.constEnd() && it.key() == scope) + it = m_names.erase(it); + scope = QQmlJSScope::ConstPtr(); } bool isNullType(const QString &name) const @@ -61,21 +70,37 @@ struct ContextualTypes void addTypes(ContextualTypes &&types) { Q_ASSERT(types.m_context == m_context); + insertNames(types); m_types.insert(std::move(types.m_types)); } void addTypes(const ContextualTypes &types) { Q_ASSERT(types.m_context == m_context); + insertNames(types); m_types.insert(types.m_types); } const QHash<QString, ImportedScope<QQmlJSScope::ConstPtr>> &types() const { return m_types; } - void clearTypes() { m_types.clear(); } + void clearTypes() + { + m_names.clear(); + m_types.clear(); + } private: + void insertNames(const ContextualTypes &types) { + for (auto it = types.m_types.constBegin(), end = types.m_types.constEnd(); + it != end; ++it) { + const QString &name = it.key(); + if (!name.startsWith(u'$')) + m_names.insert(it->scope, name); + } + } + QHash<QString, ImportedScope<QQmlJSScope::ConstPtr>> m_types; + QMultiHash<QQmlJSScope::ConstPtr, QString> m_names; CompileContext m_context; // For resolving sequence types diff --git a/src/qmlcompiler/qqmljsfunctioninitializer.cpp b/src/qmlcompiler/qqmljsfunctioninitializer.cpp index 217b00c52c..09928364b1 100644 --- a/src/qmlcompiler/qqmljsfunctioninitializer.cpp +++ b/src/qmlcompiler/qqmljsfunctioninitializer.cpp @@ -116,10 +116,11 @@ void QQmlJSFunctionInitializer::populateSignature( } } - if (!function->returnType) { + if (!function->returnType.isValid()) { if (ast->typeAnnotation) { - function->returnType = m_typeResolver->typeFromAST(ast->typeAnnotation->type); - if (!function->returnType) + function->returnType = m_typeResolver->globalType( + m_typeResolver->typeFromAST(ast->typeAnnotation->type)); + if (!function->returnType.isValid()) signatureError(u"Cannot resolve return type %1"_s.arg( QmlIR::IRBuilder::asString(ast->typeAnnotation->type->typeId))); } @@ -218,9 +219,9 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run( const auto property = m_objectType->property(propertyName); if (const QQmlJSScope::ConstPtr propertyType = property.type()) { - function.returnType = propertyType->isListProperty() - ? m_typeResolver->qObjectListType() - : propertyType; + function.returnType = m_typeResolver->globalType(propertyType->isListProperty() + ? m_typeResolver->qObjectListType() + : QQmlJSScope::ConstPtr(property.type())); } else { QString message = u"Cannot resolve property type %1 for binding on %2."_s .arg(property.typeName(), propertyName); diff --git a/src/qmlcompiler/qqmljsstoragegeneralizer.cpp b/src/qmlcompiler/qqmljsstoragegeneralizer.cpp index 077c1dcc01..937c35ddcd 100644 --- a/src/qmlcompiler/qqmljsstoragegeneralizer.cpp +++ b/src/qmlcompiler/qqmljsstoragegeneralizer.cpp @@ -24,13 +24,13 @@ QQmlJSStorageGeneralizer::run(Function *function, QQmlJS::DiagnosticMessage *err { m_error = error; - if (QQmlJSScope::ConstPtr &returnType = function->returnType) { + if (QQmlJSRegisterContent &returnType = function->returnType; returnType.isValid()) { if (QQmlJSScope::ConstPtr stored = m_typeResolver->genericType( - returnType, QQmlJSTypeResolver::ComponentIsGeneric::Yes)) { - returnType = stored; + returnType.storedType(), QQmlJSTypeResolver::ComponentIsGeneric::Yes)) { + returnType = returnType.storedIn(stored); } else { setError(QStringLiteral("Cannot store the return type %1.") - .arg(returnType->internalName(), 0)); + .arg(returnType.storedType()->internalName())); return {}; } } diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index baa6eab694..d7a7d68d9f 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -41,7 +41,7 @@ QQmlJSCompilePass::BlocksAndAnnotations QQmlJSTypePropagator::run( { m_function = function; m_error = error; - m_returnType = m_typeResolver->globalType(m_function->returnType); + m_returnType = m_function->returnType; do { // Reset the error if we need to do another pass diff --git a/src/qmlcompiler/qqmljstyperesolver_p.h b/src/qmlcompiler/qqmljstyperesolver_p.h index 6a5ea8cf9b..9961c24842 100644 --- a/src/qmlcompiler/qqmljstyperesolver_p.h +++ b/src/qmlcompiler/qqmljstyperesolver_p.h @@ -96,6 +96,11 @@ public: { return m_imports.type(name).scope; } + QString nameForType(const QQmlJSScope::ConstPtr &type) const + { + return m_imports.name(originalType(type)); + } + QQmlJSScope::ConstPtr typeFromAST(QQmlJS::AST::Type *type) const; QQmlJSScope::ConstPtr typeForConst(QV4::ReturnedValue rv) const; QQmlJSRegisterContent typeForBinaryOperation(QSOperator::Op oper, diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 4d9b6aea41..8c5449d192 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -125,6 +125,7 @@ set(qml_files dialog.qml dialogButtonBox.qml dynamicscene.qml + enforceSignature.qml enumConversion.qml enumFromBadSingleton.qml enumInvalid.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml b/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml new file mode 100644 index 0000000000..571a000199 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml @@ -0,0 +1,11 @@ +import QtQml + +QtObject { + id: mainItem + + function arg(item: Binding) : QtObject { return item } + function ret(item: QtObject) : Binding { return item } + + property QtObject a: arg(mainItem); + property QtObject b: ret(mainItem); +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index ae8ef49b22..9b66143f62 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -89,6 +89,7 @@ private slots: void enumProblems(); void enumScope(); void enums(); + void enforceSignature(); void enumsInOtherObject(); void equalityQObjects(); void equalityQUrl(); @@ -1652,6 +1653,23 @@ void tst_QmlCppCodegen::enums() } +void tst_QmlCppCodegen::enforceSignature() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enforceSignature.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + const QVariant a = object->property("a"); + QCOMPARE(a.metaType(), QMetaType::fromType<QObject *>()); + QCOMPARE(a.value<QObject *>(), nullptr); + + const QVariant b = object->property("b"); + QCOMPARE(b.metaType(), QMetaType::fromType<QObject *>()); + QCOMPARE(b.value<QObject *>(), nullptr); +} + void tst_QmlCppCodegen::enumsInOtherObject() { QQmlEngine engine; @@ -1865,9 +1883,8 @@ void tst_QmlCppCodegen::failures() { const auto &aotFailure = QmlCacheGeneratedCode::_qt_qml_TestTypes_failures_qml::aotBuiltFunctions[0]; - QVERIFY(aotFailure.argumentTypes.isEmpty()); QVERIFY(!aotFailure.functionPtr); - QCOMPARE(aotFailure.extraData, 0); + QCOMPARE(aotFailure.functionIndex, 0); } void tst_QmlCppCodegen::fallbackLookups() |