diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2023-09-19 16:43:11 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2023-09-28 16:23:04 +0200 |
commit | 4f1b9156a48e44cf1f127a4563d0ac69ab436f12 (patch) | |
tree | e4092d3574468a0af89efc39145e6fd0348230ae /src/qml/jsruntime/qv4jscall_p.h | |
parent | f9271771cf6179a8b51ca90a2f863d6983c8fe0c (diff) |
QML: Implement QObjectMethod::virtualCallWithMetaTypes
We can use the same mechanism we have in place when calling typed
JavaScript functions. The type coercion is generalized and moved to
qv4jscall_p.h. We also use the correct JavaScript coercion in the rare
fallback case where the types are actually different.
Fixes: QTBUG-113258
Change-Id: I30404ee0122433b47227b2fc0dc4b0e3862a99c7
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qml/jsruntime/qv4jscall_p.h')
-rw-r--r-- | src/qml/jsruntime/qv4jscall_p.h | 159 |
1 files changed, 157 insertions, 2 deletions
diff --git a/src/qml/jsruntime/qv4jscall_p.h b/src/qml/jsruntime/qv4jscall_p.h index c11a0be6f3..f49b5a4a47 100644 --- a/src/qml/jsruntime/qv4jscall_p.h +++ b/src/qml/jsruntime/qv4jscall_p.h @@ -390,7 +390,7 @@ template<typename Callable> ReturnedValue coerceAndCall( ExecutionEngine *engine, const Function::JSTypedFunction *typedFunction, const CompiledData::Function *compiledFunction, - const Value *thisObject, const Value *argv, int argc, Callable call) + const Value *argv, int argc, Callable call) { Scope scope(engine); @@ -402,10 +402,165 @@ ReturnedValue coerceAndCall( typedFunction->argumentTypes[i], formals[i].type.isList()); } - ScopedValue result(scope, call(thisObject, jsCallData.args, jsCallData.argc)); + ScopedValue result(scope, call(jsCallData.args, jsCallData.argc)); return coerce(engine, result, typedFunction->returnType, compiledFunction->returnType.isList()); } +// Note: \a to is unininitialized here! This is in contrast to most other related functions. +inline void coerce( + ExecutionEngine *engine, QMetaType fromType, const void *from, QMetaType toType, void *to) +{ + if ((fromType.flags() & QMetaType::PointerToQObject) + && (toType.flags() & QMetaType::PointerToQObject)) { + QObject *fromObj = *static_cast<QObject * const*>(from); + *static_cast<QObject **>(to) + = (fromObj && fromObj->metaObject()->inherits(toType.metaObject())) + ? fromObj + : nullptr; + return; + } + + if (toType == QMetaType::fromType<QVariant>()) { + new (to) QVariant(fromType, from); + return; + } + + if (toType == QMetaType::fromType<QJSPrimitiveValue>()) { + new (to) QJSPrimitiveValue(fromType, from); + return; + } + + if (fromType == QMetaType::fromType<QVariant>()) { + const QVariant *fromVariant = static_cast<const QVariant *>(from); + if (fromVariant->metaType() == toType) + toType.construct(to, fromVariant->data()); + else + coerce(engine, fromVariant->metaType(), fromVariant->data(), toType, to); + return; + } + + if (fromType == QMetaType::fromType<QJSPrimitiveValue>()) { + const QJSPrimitiveValue *fromPrimitive = static_cast<const QJSPrimitiveValue *>(from); + if (fromPrimitive->metaType() == toType) + toType.construct(to, fromPrimitive->data()); + else + coerce(engine, fromPrimitive->metaType(), fromPrimitive->data(), toType, to); + return; + } + + // TODO: This is expensive. We might establish a direct C++-to-C++ type coercion, like we have + // for JS-to-JS. However, we shouldn't need this very often. Most of the time the compiler + // will generate code that passes the right arguments. + if (toType.flags() & QMetaType::NeedsConstruction) + toType.construct(to); + QV4::Scope scope(engine); + QV4::ScopedValue value(scope, engine->fromData(fromType, from)); + if (!ExecutionEngine::metaTypeFromJS(value, toType, to)) + QMetaType::convert(fromType, from, toType, to); +} + +template<typename TypedFunction, typename Callable> +void coerceAndCall( + ExecutionEngine *engine, const TypedFunction *typedFunction, + void **argv, const QMetaType *types, int argc, Callable call) +{ + const qsizetype numFunctionArguments = typedFunction->parameterCount(); + + Q_ALLOCA_DECLARE(void *, transformedArguments); + Q_ALLOCA_DECLARE(void, transformedResult); + + const QMetaType returnType = typedFunction->returnMetaType(); + const QMetaType frameReturn = types[0]; + bool returnsQVariantWrapper = false; + if (argv[0] && returnType != frameReturn) { + Q_ALLOCA_ASSIGN(void *, transformedArguments, (numFunctionArguments + 1) * sizeof(void *)); + memcpy(transformedArguments, argv, (argc + 1) * sizeof(void *)); + + if (frameReturn == QMetaType::fromType<QVariant>()) { + void *returnValue = argv[0]; + new (returnValue) QVariant(returnType); + transformedResult = transformedArguments[0] + = static_cast<QVariant *>(returnValue)->data(); + returnsQVariantWrapper = true; + } else if (returnType.sizeOf() > 0) { + Q_ALLOCA_ASSIGN(void, transformedResult, returnType.sizeOf()); + transformedArguments[0] = transformedResult; + } else { + transformedResult = transformedArguments[0] = &argc; // Some non-null marker value + } + } + + for (qsizetype i = 0; i < numFunctionArguments; ++i) { + const bool isValid = argc > i; + const QMetaType frameType = isValid ? types[i + 1] : QMetaType(); + + const QMetaType argumentType = typedFunction->parameterMetaType(i); + if (isValid && argumentType == frameType) + continue; + + if (transformedArguments == nullptr) { + Q_ALLOCA_ASSIGN(void *, transformedArguments, (numFunctionArguments + 1) * sizeof(void *)); + memcpy(transformedArguments, argv, (argc + 1) * sizeof(void *)); + } + + if (argumentType.sizeOf() == 0) { + transformedArguments[i + 1] = nullptr; + continue; + } + + void *frameVal = isValid ? argv[i + 1] : nullptr; + if (isValid && frameType == QMetaType::fromType<QVariant>()) { + QVariant *variant = static_cast<QVariant *>(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 + 1] = argv[i + 1] = variant->data(); + } else { + Q_ALLOCA_VAR(void, arg, argumentType.sizeOf()); + coerce(engine, variantType, variant->constData(), argumentType, arg); + transformedArguments[i + 1] = arg; + } + continue; + } + + Q_ALLOCA_VAR(void, arg, argumentType.sizeOf()); + + if (isValid) + coerce(engine, frameType, frameVal, argumentType, arg); + else + argumentType.construct(arg); + + transformedArguments[i + 1] = arg; + } + + if (!transformedArguments) { + call(argv, numFunctionArguments); + return; + } + + call(transformedArguments, numFunctionArguments); + + if (transformedResult && !returnsQVariantWrapper) { + if (frameReturn.sizeOf() > 0) + coerce(engine, returnType, transformedResult, frameReturn, argv[0]); + if (returnType.flags() & QMetaType::NeedsDestruction) + returnType.destruct(transformedResult); + } + + for (qsizetype i = 0; i < numFunctionArguments; ++i) { + void *arg = transformedArguments[i + 1]; + if (arg == nullptr) + continue; + if (i >= argc || arg != argv[i + 1]) { + const QMetaType argumentType = typedFunction->parameterMetaType(i); + if (argumentType.flags() & QMetaType::NeedsDestruction) + argumentType.destruct(arg); + } + } +} + } // namespace QV4 QT_END_NAMESPACE |