diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2023-06-21 14:11:41 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2023-06-26 20:42:49 +0200 |
commit | 9eb6240ebd0b7f52ec49aba757a8c355b25203e0 (patch) | |
tree | cff2b25ad5798206830bb1cb1ea1c036610634d6 /src/qml/jsruntime/qv4jscall_p.h | |
parent | a3389d6bf238c46816fd1133c1258102e36c4b10 (diff) |
QML: Improve the JS-to-JS type check when enforcing signatures
We do not have to coerce via the C++ type. Rather, we match the
JavaScript representations of the types and coerce as needed.
Task-number: QTBUG-113527
Change-Id: Id5c30cd46293f2d7aedd699f141a9fe19511b622
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 | 229 |
1 files changed, 199 insertions, 30 deletions
diff --git a/src/qml/jsruntime/qv4jscall_p.h b/src/qml/jsruntime/qv4jscall_p.h index 2f6af8dd8b..e824863b29 100644 --- a/src/qml/jsruntime/qv4jscall_p.h +++ b/src/qml/jsruntime/qv4jscall_p.h @@ -14,11 +14,26 @@ // We mean it. // +#include <private/qqmlengine_p.h> +#include <private/qqmllistwrapper_p.h> +#include <private/qqmlvaluetype_p.h> +#include <private/qqmlvaluetypewrapper_p.h> #include <private/qv4alloca_p.h> +#include <private/qv4context_p.h> +#include <private/qv4dateobject_p.h> +#include <private/qv4function_p.h> #include <private/qv4functionobject_p.h> #include <private/qv4object_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qv4regexpobject_p.h> #include <private/qv4scopedvalue_p.h> +#include <private/qv4stackframe_p.h> +#include <private/qv4urlobject_p.h> +#include <private/qv4variantobject_p.h> + +#if QT_CONFIG(regularexpression) +#include <QtCore/qregularexpression.h> +#endif QT_BEGIN_NAMESPACE @@ -189,43 +204,197 @@ bool convertAndCall(ExecutionEngine *engine, QObject *thisObject, return !jsResult->isUndefined(); } -template<typename Callable> -ReturnedValue coerceAndCall( - ExecutionEngine *engine, const QQmlPrivate::AOTCompiledFunction *typedFunction, - const Value *thisObject, const Value *argv, int argc, Callable call) +inline ReturnedValue coerce( + ExecutionEngine *engine, const Value &value, const QQmlType &qmlType, bool isList); + +inline QObject *coerceQObject(const Value &value, const QQmlType &qmlType) { - Scope scope(engine); - QV4::JSCallArguments jsCallData(scope, argc); + QObject *o; + if (const QV4::QObjectWrapper *wrapper = value.as<QV4::QObjectWrapper>()) + o = wrapper->object(); + else if (const QV4::QQmlTypeWrapper *wrapper = value.as<QQmlTypeWrapper>()) + o = wrapper->object(); + else + return nullptr; - const qsizetype numFunctionArguments = typedFunction->argumentTypes.size(); - for (qsizetype i = 0; i < numFunctionArguments; ++i) { - const QMetaType argumentType = typedFunction->argumentTypes[i]; - if (const qsizetype argumentSize = argumentType.sizeOf()) { - Q_ALLOCA_VAR(void, argument, argumentSize); - argumentType.construct(argument); - if (i < argc) - ExecutionEngine::metaTypeFromJS(argv[i], argumentType, argument); - jsCallData.args[i] = engine->metaTypeToJS(argumentType, argument); - } else { - jsCallData.args[i] = argv[i]; + return (o && qmlobject_can_qml_cast(o, qmlType)) ? o : nullptr; +} + +enum CoercionProblem +{ + InsufficientAnnotation, + InvalidListType +}; + +Q_QML_PRIVATE_EXPORT void warnAboutCoercionToVoid( + ExecutionEngine *engine, const Value &value, CoercionProblem problem); + +inline ReturnedValue coerceListType( + ExecutionEngine *engine, const Value &value, const QQmlType &qmlType) +{ + QMetaType type = qmlType.qListTypeId(); + if (const QV4::Sequence *sequence = value.as<QV4::Sequence>()) { + const QQmlTypePrivate *typePrivate = sequence->d()->typePrivate(); + if (typePrivate->listId == type) + return value.asReturnedValue(); + } + + if (const QmlListWrapper *list = value.as<QmlListWrapper>()) { + if (list->d()->propertyType() == type) + return value.asReturnedValue(); + } + + QMetaType listValueType = qmlType.typeId(); + if (!listValueType.isValid()) { + warnAboutCoercionToVoid(engine, value, InvalidListType); + return value.asReturnedValue(); + } + + QV4::Scope scope(engine); + + const ArrayObject *array = value.as<ArrayObject>(); + if (!array) { + return (listValueType.flags() & QMetaType::PointerToQObject) + ? QmlListWrapper::create(engine, listValueType) + : SequencePrototype::fromData(engine, type, nullptr); + } + + if (listValueType.flags() & QMetaType::PointerToQObject) { + QV4::Scoped<QmlListWrapper> newList(scope, QmlListWrapper::create(engine, listValueType)); + QQmlListProperty<QObject> *listProperty = newList->d()->property(); + + const qsizetype length = array->getLength(); + qsizetype i = 0; + for (; i < length; ++i) { + ScopedValue v(scope, array->get(i)); + listProperty->append(listProperty, coerceQObject(v, qmlType)); } + + return newList->asReturnedValue(); } - ScopedValue result(scope, call(thisObject, jsCallData.args, argc)); - const QMetaType returnType = typedFunction->returnType; - if (const qsizetype returnSize = returnType.sizeOf()) { - Q_ALLOCA_VAR(void, returnValue, returnSize); - if (scope.hasException()) { - returnType.construct(returnValue); - } else if (returnType == QMetaType::fromType<QVariant>()) { - new (returnValue) QVariant(ExecutionEngine::toVariant(result, QMetaType {})); - } else { - returnType.construct(returnValue); - ExecutionEngine::metaTypeFromJS(result, returnType, returnValue); + QV4::Scoped<Sequence> sequence(scope, SequencePrototype::fromData(engine, type, nullptr)); + const qsizetype length = array->getLength(); + for (qsizetype i = 0; i < length; ++i) + sequence->containerPutIndexed(i, array->get(i)); + return sequence->asReturnedValue(); +} + +inline ReturnedValue coerce( + ExecutionEngine *engine, const Value &value, const QQmlType &qmlType, bool isList) +{ + // These are all the named non-list, non-QObject builtins. Only those need special handling. + // Some of them may be wrapped in VariantObject because that is how they are stored in VME + // properties. + if (isList) + return coerceListType(engine, value, qmlType); + + const QMetaType metaType = qmlType.typeId(); + if (!metaType.isValid()) { + if (!value.isUndefined()) + warnAboutCoercionToVoid(engine, value, InsufficientAnnotation); + return value.asReturnedValue(); + } + + switch (metaType.id()) { + case QMetaType::Void: + return Encode::undefined(); + case QMetaType::QVariant: + return value.asReturnedValue(); + case QMetaType::Int: + return Encode(value.toInt32()); + case QMetaType::Double: + return value.convertedToNumber(); + case QMetaType::QString: + return value.toString(engine)->asReturnedValue(); + case QMetaType::Bool: + return Encode(value.toBoolean()); + case QMetaType::QDateTime: + if (value.as<DateObject>()) + return value.asReturnedValue(); + if (const VariantObject *varObject = value.as<VariantObject>()) { + const QVariant &var = varObject->d()->data(); + switch (var.metaType().id()) { + case QMetaType::QDateTime: + return engine->newDateObject(var.value<QDateTime>())->asReturnedValue(); + case QMetaType::QTime: + return engine->newDateObject(var.value<QTime>(), nullptr, -1, 0)->asReturnedValue(); + case QMetaType::QDate: + return engine->newDateObject(var.value<QDate>(), nullptr, -1, 0)->asReturnedValue(); + default: + break; + } + } + return engine->newDateObject(QDateTime())->asReturnedValue(); + case QMetaType::QUrl: + if (value.as<UrlObject>()) + return value.asReturnedValue(); + if (const VariantObject *varObject = value.as<VariantObject>()) { + const QVariant &var = varObject->d()->data(); + return var.metaType() == QMetaType::fromType<QUrl>() + ? engine->newUrlObject(var.value<QUrl>())->asReturnedValue() + : engine->newUrlObject()->asReturnedValue(); } - return engine->metaTypeToJS(returnType, returnValue); + // Since URL properties are stored as string, we need to support the string conversion here. + return value.isString() + ? engine->newUrlObject(QUrl(value.stringValue()->toQString()))->asReturnedValue() + : engine->newUrlObject()->asReturnedValue(); +#if QT_CONFIG(regularexpression) + case QMetaType::QRegularExpression: + if (value.as<RegExpObject>()) + return value.asReturnedValue(); + if (const VariantObject *varObject = value.as<VariantObject>()) { + const QVariant &var = varObject->d()->data(); + if (var.metaType() == QMetaType::fromType<QRegularExpression>()) + return engine->newRegExpObject(var.value<QRegularExpression>())->asReturnedValue(); + } + return engine->newRegExpObject(QString(), 0)->asReturnedValue(); +#endif + default: + break; + } + + if (metaType.flags() & QMetaType::PointerToQObject) { + return coerceQObject(value, qmlType) + ? value.asReturnedValue() + : Encode::null(); } - return result->asReturnedValue(); + + if (const QQmlValueTypeWrapper *wrapper = value.as<QQmlValueTypeWrapper>()) { + if (wrapper->type() == metaType) + return value.asReturnedValue(); + } + + if (void *target = QQmlValueTypeProvider::heapCreateValueType(qmlType, value)) { + Heap::QQmlValueTypeWrapper *wrapper = engine->memoryManager->allocate<QQmlValueTypeWrapper>( + nullptr, metaType, QQmlMetaType::metaObjectForValueType(qmlType), + nullptr, -1, Heap::ReferenceObject::NoFlag); + Q_ASSERT(!wrapper->gadgetPtr()); + wrapper->setGadgetPtr(target); + return wrapper->asReturnedValue(); + } + + return Encode::undefined(); +} + +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) +{ + Scope scope(engine); + + QV4::JSCallArguments jsCallData(scope, typedFunction->argumentTypes.size()); + 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()); + } + + ScopedValue result(scope, call(thisObject, jsCallData.args, jsCallData.argc)); + return coerce(engine, result, typedFunction->returnType, compiledFunction->returnType.isList()); } } // namespace QV4 |