diff options
Diffstat (limited to 'src/qml/jsruntime/qv4jscall_p.h')
-rw-r--r-- | src/qml/jsruntime/qv4jscall_p.h | 480 |
1 files changed, 404 insertions, 76 deletions
diff --git a/src/qml/jsruntime/qv4jscall_p.h b/src/qml/jsruntime/qv4jscall_p.h index 4126a25758..ed1ca983ad 100644 --- a/src/qml/jsruntime/qv4jscall_p.h +++ b/src/qml/jsruntime/qv4jscall_p.h @@ -14,14 +14,26 @@ // We mean it. // -#include "qv4object_p.h" -#include "qv4function_p.h" -#include "qv4functionobject_p.h" -#include "qv4context_p.h" -#include "qv4scopedvalue_p.h" -#include "qv4stackframe_p.h" -#include "qv4qobjectwrapper_p.h" +#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 @@ -99,47 +111,32 @@ ReturnedValue FunctionObject::call(const JSCallData &data) const void populateJSCallArguments(ExecutionEngine *v4, JSCallArguments &jsCall, int argc, void **args, const QMetaType *types); -struct ScopedStackFrame -{ - ScopedStackFrame(const Scope &scope, ExecutionContext *context) - : engine(scope.engine) - { - if (auto currentFrame = engine->currentStackFrame) { - frame.init(currentFrame->v4Function, nullptr, context, nullptr, nullptr, 0); - frame.instructionPointer = currentFrame->instructionPointer; - } else { - frame.init(nullptr, nullptr, context, nullptr, nullptr, 0); - } - frame.push(engine); - } - - ~ScopedStackFrame() - { - frame.pop(engine); - } - -private: - ExecutionEngine *engine = nullptr; - MetaTypesStackFrame frame; -}; - template<typename Callable> ReturnedValue convertAndCall( - ExecutionEngine *engine, const QQmlPrivate::TypedFunction *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); - argumentType.construct(argument); - if (i < argc) - ExecutionEngine::metaTypeFromJS(argv[i], argumentType, argument); + if (argumentType.flags() & QMetaType::NeedsConstruction) { + argumentType.construct(argument); + if (i < argc) + ExecutionEngine::metaTypeFromJS(argv[i], argumentType, argument); + } else if (i >= argc + || !ExecutionEngine::metaTypeFromJS(argv[i], argumentType, argument)) { + // If we can't convert the argument, we need to default-construct it even if it + // doesn't formally need construction. + // E.g. an int doesn't need construction, but we still want it to be 0. + argumentType.construct(argument); + } + values[i + 1] = argument; } else { values[i + 1] = nullptr; @@ -147,29 +144,37 @@ 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; + if (types[0].flags() & QMetaType::NeedsConstruction) + types[0].construct(returnValue); } else { values[0] = nullptr; } - if (const QV4::QObjectWrapper *cppThisObject = thisObject->as<QV4::QObjectWrapper>()) + if (const QV4::QObjectWrapper *cppThisObject = thisObject + ? thisObject->as<QV4::QObjectWrapper>() + : nullptr) { call(cppThisObject->object(), values, types, argc); - else + } else { call(nullptr, values, types, argc); + } ReturnedValue result; if (values[0]) { result = engine->metaTypeToJS(types[0], values[0]); - types[0].destruct(values[0]); + if (types[0].flags() & QMetaType::NeedsDestruction) + types[0].destruct(values[0]); } else { result = Encode::undefined(); } - for (qsizetype i = 1, end = numFunctionArguments + 1; i < end; ++i) - types[i].destruct(values[i]); + for (qsizetype i = 1, end = numFunctionArguments + 1; i < end; ++i) { + if (types[i].flags() & QMetaType::NeedsDestruction) + types[i].destruct(values[i]); + } return result; } @@ -202,57 +207,380 @@ bool convertAndCall(ExecutionEngine *engine, QObject *thisObject, const QMetaType resultType = types[0]; if (scope.hasException()) { // Clear the return value + resultType.destruct(result); resultType.construct(result); - } else { + } else if (resultType == QMetaType::fromType<QVariant>()) { // When the return type is QVariant, JS objects are to be returned as // QJSValue wrapped in QVariant. metaTypeFromJS unwraps them, unfortunately. - if (resultType == QMetaType::fromType<QVariant>()) { - new (result) QVariant(ExecutionEngine::toVariant(jsResult, QMetaType {})); - } else { - resultType.construct(result); - ExecutionEngine::metaTypeFromJS(jsResult, resultType, result); - } + *static_cast<QVariant *>(result) = ExecutionEngine::toVariant(jsResult, QMetaType {}); + } else if (!ExecutionEngine::metaTypeFromJS(jsResult, resultType, result)) { + // If we cannot convert, also clear the return value. + // The caller may have given us an uninitialized QObject*, expecting it to be overwritten. + resultType.destruct(result); + resultType.construct(result); } return !jsResult->isUndefined(); } +inline ReturnedValue coerce( + ExecutionEngine *engine, const Value &value, const QQmlType &qmlType, bool isList); + +inline QObject *coerceQObject(const Value &value, const QQmlType &qmlType) +{ + 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; + + return (o && qmlobject_can_qml_cast(o, qmlType)) ? o : nullptr; +} + +enum CoercionProblem +{ + InsufficientAnnotation, + InvalidListType +}; + +Q_QML_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(); + const auto metaSequence = [&]() { + // TODO: We should really add the metasequence to the same QQmlType that holds + // all the other type information. Then we can get rid of the extra + // QQmlMetaType::qmlListType() here. + return qmlType.isSequentialContainer() + ? qmlType.listMetaSequence() + : QQmlMetaType::qmlListType(type).listMetaSequence(); + }; + + if (const QV4::Sequence *sequence = value.as<QV4::Sequence>()) { + if (sequence->d()->listType() == 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, metaSequence(), nullptr); + } + + if (listValueType.flags() & QMetaType::PointerToQObject) { + QV4::Scoped<QmlListWrapper> newList(scope, QmlListWrapper::create(engine, type)); + 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(); + } + + QV4::Scoped<Sequence> sequence( + scope, SequencePrototype::fromData(engine, type, metaSequence(), 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(); + } + // Since URL properties are stored as string, we need to support the string conversion here. + if (const String *string = value.stringValue()) + return engine->newUrlObject(QUrl(string->toQString()))->asReturnedValue(); + return 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(); + } + + 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, qmlType.metaObjectForValueType(), + 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 QQmlPrivate::TypedFunction *typedFunction, - const Value *thisObject, const Value *argv, int argc, Callable call) + ExecutionEngine *engine, + const Function::JSTypedFunction *typedFunction, const CompiledData::Function *compiledFunction, + const Value *argv, int argc, Callable call) { Scope scope(engine); - QV4::JSCallArguments jsCallData(scope, argc); - 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); + 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->types[i + 1], formals[i].type.isList()); + } + + ScopedValue result(scope, call(jsCallData.args, jsCallData.argc)); + 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. +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>()) { + QVariant *returnValue = static_cast<QVariant *>(argv[0]); + *returnValue = QVariant(returnType); + transformedResult = transformedArguments[0] = returnValue->data(); + returnsQVariantWrapper = true; + } else if (returnType.sizeOf() > 0) { + Q_ALLOCA_ASSIGN(void, transformedResult, returnType.sizeOf()); + transformedArguments[0] = transformedResult; + if (returnType.flags() & QMetaType::NeedsConstruction) + returnType.construct(transformedResult); } else { - jsCallData.args[i] = argv[i]; + transformedResult = transformedArguments[0] = &argc; // Some non-null marker value } } - 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); + 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) { + if (frameReturn.flags() & QMetaType::NeedsDestruction) + frameReturn.destruct(argv[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); } - return engine->metaTypeToJS(returnType, returnValue); } - return result->asReturnedValue(); } } // namespace QV4 |