aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime/qv4jscall_p.h
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-06-21 14:11:41 +0200
committerUlf Hermann <ulf.hermann@qt.io>2023-06-26 20:42:49 +0200
commit9eb6240ebd0b7f52ec49aba757a8c355b25203e0 (patch)
treecff2b25ad5798206830bb1cb1ea1c036610634d6 /src/qml/jsruntime/qv4jscall_p.h
parenta3389d6bf238c46816fd1133c1258102e36c4b10 (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.h229
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