| 1 | // Copyright (C) 2017 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | #ifndef QV4JSCALL_H |
| 4 | #define QV4JSCALL_H |
| 5 | |
| 6 | // |
| 7 | // W A R N I N G |
| 8 | // ------------- |
| 9 | // |
| 10 | // This file is not part of the Qt API. It exists purely as an |
| 11 | // implementation detail. This header file may change from version to |
| 12 | // version without notice, or even be removed. |
| 13 | // |
| 14 | // We mean it. |
| 15 | // |
| 16 | |
| 17 | #include <private/qqmllistwrapper_p.h> |
| 18 | #include <private/qqmlvaluetypewrapper_p.h> |
| 19 | |
| 20 | #include <private/qv4alloca_p.h> |
| 21 | #include <private/qv4dateobject_p.h> |
| 22 | #include <private/qv4function_p.h> |
| 23 | #include <private/qv4functionobject_p.h> |
| 24 | #include <private/qv4qobjectwrapper_p.h> |
| 25 | #include <private/qv4regexpobject_p.h> |
| 26 | #include <private/qv4scopedvalue_p.h> |
| 27 | #include <private/qv4sequenceobject_p.h> |
| 28 | #include <private/qv4urlobject_p.h> |
| 29 | #include <private/qv4variantobject_p.h> |
| 30 | |
| 31 | #if QT_CONFIG(regularexpression) |
| 32 | #include <QtCore/qregularexpression.h> |
| 33 | #endif |
| 34 | |
| 35 | QT_BEGIN_NAMESPACE |
| 36 | |
| 37 | namespace QV4 { |
| 38 | |
| 39 | template<typename Args> |
| 40 | CallData *callDatafromJS(const Scope &scope, const Args *args, const FunctionObject *f = nullptr) |
| 41 | { |
| 42 | int size = int(offsetof(QV4::CallData, args)/sizeof(QV4::Value)) + args->argc; |
| 43 | CallData *ptr = reinterpret_cast<CallData *>(scope.alloc<Scope::Uninitialized>(nValues: size)); |
| 44 | ptr->function = Encode::undefined(); |
| 45 | ptr->context = Encode::undefined(); |
| 46 | ptr->accumulator = Encode::undefined(); |
| 47 | ptr->thisObject = args->thisObject ? args->thisObject->asReturnedValue() : Encode::undefined(); |
| 48 | ptr->newTarget = Encode::undefined(); |
| 49 | ptr->setArgc(args->argc); |
| 50 | if (args->argc) |
| 51 | memcpy(ptr->args, args->args, args->argc*sizeof(Value)); |
| 52 | if (f) |
| 53 | ptr->function = f->asReturnedValue(); |
| 54 | return ptr; |
| 55 | } |
| 56 | |
| 57 | struct JSCallArguments |
| 58 | { |
| 59 | JSCallArguments(const Scope &scope, int argc = 0) |
| 60 | : thisObject(scope.alloc()), args(scope.alloc(nValues: argc)), argc(argc) |
| 61 | { |
| 62 | } |
| 63 | |
| 64 | CallData *callData(const Scope &scope, const FunctionObject *f = nullptr) const |
| 65 | { |
| 66 | return callDatafromJS(scope, args: this, f); |
| 67 | } |
| 68 | |
| 69 | Value *thisObject; |
| 70 | Value *args; |
| 71 | const int argc; |
| 72 | }; |
| 73 | |
| 74 | struct JSCallData |
| 75 | { |
| 76 | JSCallData(const Value *thisObject, const Value *argv, int argc) |
| 77 | : thisObject(thisObject), args(argv), argc(argc) |
| 78 | { |
| 79 | } |
| 80 | |
| 81 | Q_IMPLICIT JSCallData(const JSCallArguments &args) |
| 82 | : thisObject(args.thisObject), args(args.args), argc(args.argc) |
| 83 | { |
| 84 | } |
| 85 | |
| 86 | CallData *callData(const Scope &scope, const FunctionObject *f = nullptr) const |
| 87 | { |
| 88 | return callDatafromJS(scope, args: this, f); |
| 89 | } |
| 90 | |
| 91 | const Value *thisObject; |
| 92 | const Value *args; |
| 93 | const int argc; |
| 94 | }; |
| 95 | |
| 96 | inline |
| 97 | ReturnedValue FunctionObject::callAsConstructor(const JSCallData &data) const |
| 98 | { |
| 99 | return callAsConstructor(argv: data.args, argc: data.argc, newTarget: this); |
| 100 | } |
| 101 | |
| 102 | inline |
| 103 | ReturnedValue FunctionObject::call(const JSCallData &data) const |
| 104 | { |
| 105 | return call(thisObject: data.thisObject, argv: data.args, argc: data.argc); |
| 106 | } |
| 107 | |
| 108 | void populateJSCallArguments(ExecutionEngine *v4, JSCallArguments &jsCall, int argc, |
| 109 | void **args, const QMetaType *types); |
| 110 | |
| 111 | template<typename Callable> |
| 112 | ReturnedValue convertAndCall( |
| 113 | ExecutionEngine *engine, const Function::AOTCompiledFunction *aotFunction, |
| 114 | const Value *thisObject, const Value *argv, int argc, Callable call) |
| 115 | { |
| 116 | const qsizetype numFunctionArguments = aotFunction->types.length() - 1; |
| 117 | Q_ALLOCA_VAR(void *, values, (numFunctionArguments + 1) * sizeof(void *)); |
| 118 | Q_ALLOCA_VAR(QMetaType, types, (numFunctionArguments + 1) * sizeof(QMetaType)); |
| 119 | |
| 120 | for (qsizetype i = 0; i < numFunctionArguments; ++i) { |
| 121 | const QMetaType argumentType = aotFunction->types[i + 1]; |
| 122 | types[i + 1] = argumentType; |
| 123 | if (const qsizetype argumentSize = argumentType.sizeOf()) { |
| 124 | Q_ALLOCA_VAR(void, argument, argumentSize); |
| 125 | if (argumentType.flags() & QMetaType::NeedsConstruction) { |
| 126 | argumentType.construct(where: argument); |
| 127 | if (i < argc) |
| 128 | ExecutionEngine::metaTypeFromJS(value: argv[i], type: argumentType, data: argument); |
| 129 | } else if (i >= argc |
| 130 | || !ExecutionEngine::metaTypeFromJS(value: argv[i], type: argumentType, data: argument)) { |
| 131 | // If we can't convert the argument, we need to default-construct it even if it |
| 132 | // doesn't formally need construction. |
| 133 | // E.g. an int doesn't need construction, but we still want it to be 0. |
| 134 | argumentType.construct(where: argument); |
| 135 | } |
| 136 | |
| 137 | values[i + 1] = argument; |
| 138 | } else { |
| 139 | values[i + 1] = nullptr; |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | Q_ALLOCA_DECLARE(void, returnValue); |
| 144 | types[0] = aotFunction->types[0]; |
| 145 | if (const qsizetype returnSize = types[0].sizeOf()) { |
| 146 | Q_ALLOCA_ASSIGN(void, returnValue, returnSize); |
| 147 | values[0] = returnValue; |
| 148 | if (types[0].flags() & QMetaType::NeedsConstruction) |
| 149 | types[0].construct(where: returnValue); |
| 150 | } else { |
| 151 | values[0] = nullptr; |
| 152 | } |
| 153 | |
| 154 | if (const QV4::QObjectWrapper *cppThisObject = thisObject |
| 155 | ? thisObject->as<QV4::QObjectWrapper>() |
| 156 | : nullptr) { |
| 157 | call(cppThisObject->object(), values, types, argc); |
| 158 | } else { |
| 159 | call(nullptr, values, types, argc); |
| 160 | } |
| 161 | |
| 162 | ReturnedValue result; |
| 163 | if (values[0]) { |
| 164 | result = engine->metaTypeToJS(type: types[0], data: values[0]); |
| 165 | if (types[0].flags() & QMetaType::NeedsDestruction) |
| 166 | types[0].destruct(data: values[0]); |
| 167 | } else { |
| 168 | result = Encode::undefined(); |
| 169 | } |
| 170 | |
| 171 | for (qsizetype i = 1, end = numFunctionArguments + 1; i < end; ++i) { |
| 172 | if (types[i].flags() & QMetaType::NeedsDestruction) |
| 173 | types[i].destruct(data: values[i]); |
| 174 | } |
| 175 | |
| 176 | return result; |
| 177 | } |
| 178 | |
| 179 | template<typename Callable> |
| 180 | bool convertAndCall(ExecutionEngine *engine, QObject *thisObject, |
| 181 | void **a, const QMetaType *types, int argc, Callable call) |
| 182 | { |
| 183 | Scope scope(engine); |
| 184 | QV4::JSCallArguments jsCallData(scope, argc); |
| 185 | |
| 186 | for (int ii = 0; ii < argc; ++ii) |
| 187 | jsCallData.args[ii] = engine->metaTypeToJS(type: types[ii + 1], data: a[ii + 1]); |
| 188 | |
| 189 | ScopedObject jsThisObject(scope); |
| 190 | if (thisObject) { |
| 191 | // The result of wrap() can only be null, undefined, or an object. |
| 192 | jsThisObject = QV4::QObjectWrapper::wrap(engine, object: thisObject); |
| 193 | if (!jsThisObject) |
| 194 | jsThisObject = engine->globalObject; |
| 195 | } else { |
| 196 | jsThisObject = engine->globalObject; |
| 197 | } |
| 198 | |
| 199 | ScopedValue jsResult(scope, call(jsThisObject, jsCallData.args, argc)); |
| 200 | void *result = a[0]; |
| 201 | if (!result) |
| 202 | return !jsResult->isUndefined(); |
| 203 | |
| 204 | const QMetaType resultType = types[0]; |
| 205 | if (scope.hasException()) { |
| 206 | // Clear the return value |
| 207 | resultType.destruct(data: result); |
| 208 | resultType.construct(where: result); |
| 209 | } else if (resultType == QMetaType::fromType<QVariant>()) { |
| 210 | // When the return type is QVariant, JS objects are to be returned as |
| 211 | // QJSValue wrapped in QVariant. metaTypeFromJS unwraps them, unfortunately. |
| 212 | *static_cast<QVariant *>(result) = ExecutionEngine::toVariant(value: jsResult, typeHint: QMetaType {}); |
| 213 | } else if (!ExecutionEngine::metaTypeFromJS(value: jsResult, type: resultType, data: result)) { |
| 214 | // If we cannot convert, also clear the return value. |
| 215 | // The caller may have given us an uninitialized QObject*, expecting it to be overwritten. |
| 216 | resultType.destruct(data: result); |
| 217 | resultType.construct(where: result); |
| 218 | } |
| 219 | return !jsResult->isUndefined(); |
| 220 | } |
| 221 | |
| 222 | inline ReturnedValue coerce( |
| 223 | ExecutionEngine *engine, const Value &value, const QQmlType &qmlType, bool isList); |
| 224 | |
| 225 | inline QObject *coerceQObject(const Value &value, const QQmlType &qmlType) |
| 226 | { |
| 227 | QObject *o; |
| 228 | if (const QV4::QObjectWrapper *wrapper = value.as<QV4::QObjectWrapper>()) |
| 229 | o = wrapper->object(); |
| 230 | else if (const QV4::QQmlTypeWrapper *wrapper = value.as<QQmlTypeWrapper>()) |
| 231 | o = wrapper->object(); |
| 232 | else |
| 233 | return nullptr; |
| 234 | |
| 235 | return (o && qmlobject_can_qml_cast(object: o, type: qmlType)) ? o : nullptr; |
| 236 | } |
| 237 | |
| 238 | enum CoercionProblem |
| 239 | { |
| 240 | InsufficientAnnotation, |
| 241 | InvalidListType |
| 242 | }; |
| 243 | |
| 244 | Q_QML_EXPORT void warnAboutCoercionToVoid( |
| 245 | ExecutionEngine *engine, const Value &value, CoercionProblem problem); |
| 246 | |
| 247 | inline ReturnedValue coerceListType( |
| 248 | ExecutionEngine *engine, const Value &value, const QQmlType &qmlType) |
| 249 | { |
| 250 | QMetaType type = qmlType.qListTypeId(); |
| 251 | const auto metaSequence = [&]() { |
| 252 | // TODO: We should really add the metasequence to the same QQmlType that holds |
| 253 | // all the other type information. Then we can get rid of the extra |
| 254 | // QQmlMetaType::qmlListType() here. |
| 255 | return qmlType.isSequentialContainer() |
| 256 | ? qmlType.listMetaSequence() |
| 257 | : QQmlMetaType::qmlListType(metaType: type).listMetaSequence(); |
| 258 | }; |
| 259 | |
| 260 | if (const QV4::Sequence *sequence = value.as<QV4::Sequence>()) { |
| 261 | if (sequence->d()->listType() == type) |
| 262 | return value.asReturnedValue(); |
| 263 | } |
| 264 | |
| 265 | if (const QmlListWrapper *list = value.as<QmlListWrapper>()) { |
| 266 | if (list->d()->propertyType() == type) |
| 267 | return value.asReturnedValue(); |
| 268 | } |
| 269 | |
| 270 | QMetaType listValueType = qmlType.typeId(); |
| 271 | if (!listValueType.isValid()) { |
| 272 | warnAboutCoercionToVoid(engine, value, problem: InvalidListType); |
| 273 | return value.asReturnedValue(); |
| 274 | } |
| 275 | |
| 276 | QV4::Scope scope(engine); |
| 277 | |
| 278 | const ArrayObject *array = value.as<ArrayObject>(); |
| 279 | if (!array) { |
| 280 | return (listValueType.flags() & QMetaType::PointerToQObject) |
| 281 | ? QmlListWrapper::create(engine, propType: listValueType) |
| 282 | : SequencePrototype::fromData(engine, type, metaSequence: metaSequence(), data: nullptr); |
| 283 | } |
| 284 | |
| 285 | if (listValueType.flags() & QMetaType::PointerToQObject) { |
| 286 | QV4::Scoped<QmlListWrapper> newList(scope, QmlListWrapper::create(engine, propType: type)); |
| 287 | QQmlListProperty<QObject> *listProperty = newList->d()->property(); |
| 288 | |
| 289 | const qsizetype length = array->getLength(); |
| 290 | qsizetype i = 0; |
| 291 | ScopedValue v(scope); |
| 292 | for (; i < length; ++i) { |
| 293 | v = array->get(idx: i); |
| 294 | listProperty->append(listProperty, coerceQObject(value: v, qmlType)); |
| 295 | } |
| 296 | |
| 297 | return newList->asReturnedValue(); |
| 298 | } |
| 299 | |
| 300 | QV4::Scoped<Sequence> sequence( |
| 301 | scope, SequencePrototype::fromData(engine, type, metaSequence: metaSequence(), data: nullptr)); |
| 302 | const qsizetype length = array->getLength(); |
| 303 | ScopedValue v(scope); |
| 304 | for (qsizetype i = 0; i < length; ++i) { |
| 305 | v = array->get(idx: i); |
| 306 | sequence->put(id: PropertyKey::fromArrayIndex(idx: i), v); |
| 307 | } |
| 308 | return sequence->asReturnedValue(); |
| 309 | } |
| 310 | |
| 311 | inline ReturnedValue coerce( |
| 312 | ExecutionEngine *engine, const Value &value, const QQmlType &qmlType, bool isList) |
| 313 | { |
| 314 | // These are all the named non-list, non-QObject builtins. Only those need special handling. |
| 315 | // Some of them may be wrapped in VariantObject because that is how they are stored in VME |
| 316 | // properties. |
| 317 | if (isList) |
| 318 | return coerceListType(engine, value, qmlType); |
| 319 | |
| 320 | const QMetaType metaType = qmlType.typeId(); |
| 321 | if (!metaType.isValid()) { |
| 322 | if (!value.isUndefined()) |
| 323 | warnAboutCoercionToVoid(engine, value, problem: InsufficientAnnotation); |
| 324 | return value.asReturnedValue(); |
| 325 | } |
| 326 | |
| 327 | switch (metaType.id()) { |
| 328 | case QMetaType::Void: |
| 329 | return Encode::undefined(); |
| 330 | case QMetaType::QVariant: |
| 331 | return value.asReturnedValue(); |
| 332 | case QMetaType::Int: |
| 333 | return Encode(value.toInt32()); |
| 334 | case QMetaType::Double: |
| 335 | return value.convertedToNumber(); |
| 336 | case QMetaType::QString: |
| 337 | return value.toString(e: engine)->asReturnedValue(); |
| 338 | case QMetaType::Bool: |
| 339 | return Encode(value.toBoolean()); |
| 340 | case QMetaType::QDateTime: |
| 341 | if (value.as<DateObject>()) |
| 342 | return value.asReturnedValue(); |
| 343 | if (const VariantObject *varObject = value.as<VariantObject>()) { |
| 344 | const QVariant &var = varObject->d()->data(); |
| 345 | switch (var.metaType().id()) { |
| 346 | case QMetaType::QDateTime: |
| 347 | return engine->newDateObject(dateTime: var.value<QDateTime>())->asReturnedValue(); |
| 348 | case QMetaType::QTime: |
| 349 | return engine->newDateObject(time: var.value<QTime>(), parent: nullptr, index: -1, flags: 0)->asReturnedValue(); |
| 350 | case QMetaType::QDate: |
| 351 | return engine->newDateObject(date: var.value<QDate>(), parent: nullptr, index: -1, flags: 0)->asReturnedValue(); |
| 352 | default: |
| 353 | break; |
| 354 | } |
| 355 | } |
| 356 | return engine->newDateObject(dateTime: QDateTime())->asReturnedValue(); |
| 357 | case QMetaType::QUrl: |
| 358 | if (value.as<UrlObject>()) |
| 359 | return value.asReturnedValue(); |
| 360 | if (const VariantObject *varObject = value.as<VariantObject>()) { |
| 361 | const QVariant &var = varObject->d()->data(); |
| 362 | return var.metaType() == QMetaType::fromType<QUrl>() |
| 363 | ? engine->newUrlObject(url: var.value<QUrl>())->asReturnedValue() |
| 364 | : engine->newUrlObject()->asReturnedValue(); |
| 365 | } |
| 366 | // Since URL properties are stored as string, we need to support the string conversion here. |
| 367 | if (const String *string = value.stringValue()) |
| 368 | return engine->newUrlObject(url: QUrl(string->toQString()))->asReturnedValue(); |
| 369 | return engine->newUrlObject()->asReturnedValue(); |
| 370 | #if QT_CONFIG(regularexpression) |
| 371 | case QMetaType::QRegularExpression: |
| 372 | if (value.as<RegExpObject>()) |
| 373 | return value.asReturnedValue(); |
| 374 | if (const VariantObject *varObject = value.as<VariantObject>()) { |
| 375 | const QVariant &var = varObject->d()->data(); |
| 376 | if (var.metaType() == QMetaType::fromType<QRegularExpression>()) |
| 377 | return engine->newRegExpObject(re: var.value<QRegularExpression>())->asReturnedValue(); |
| 378 | } |
| 379 | return engine->newRegExpObject(pattern: QString(), flags: 0)->asReturnedValue(); |
| 380 | #endif |
| 381 | default: |
| 382 | break; |
| 383 | } |
| 384 | |
| 385 | if (metaType.flags() & QMetaType::PointerToQObject) { |
| 386 | return coerceQObject(value, qmlType) |
| 387 | ? value.asReturnedValue() |
| 388 | : Encode::null(); |
| 389 | } |
| 390 | |
| 391 | if (const QQmlValueTypeWrapper *wrapper = value.as<QQmlValueTypeWrapper>()) { |
| 392 | if (wrapper->type() == metaType) |
| 393 | return value.asReturnedValue(); |
| 394 | } |
| 395 | |
| 396 | if (void *target = QQmlValueTypeProvider::heapCreateValueType(targetType: qmlType, source: value, engine)) { |
| 397 | Heap::QQmlValueTypeWrapper *wrapper = engine->memoryManager->allocate<QQmlValueTypeWrapper>( |
| 398 | args: nullptr, args: metaType, args: qmlType.metaObjectForValueType(), |
| 399 | args: nullptr, args: -1, args: Heap::ReferenceObject::NoFlag); |
| 400 | Q_ASSERT(!wrapper->gadgetPtr()); |
| 401 | wrapper->setGadgetPtr(target); |
| 402 | return wrapper->asReturnedValue(); |
| 403 | } |
| 404 | |
| 405 | return Encode::undefined(); |
| 406 | } |
| 407 | |
| 408 | template<typename Callable> |
| 409 | ReturnedValue coerceAndCall( |
| 410 | ExecutionEngine *engine, |
| 411 | const Function::JSTypedFunction *typedFunction, const CompiledData::Function *compiledFunction, |
| 412 | const Value *argv, int argc, Callable call) |
| 413 | { |
| 414 | Scope scope(engine); |
| 415 | |
| 416 | QV4::JSCallArguments jsCallData(scope, typedFunction->types.size() - 1); |
| 417 | const CompiledData::Parameter *formals = compiledFunction->formalsTable(); |
| 418 | for (qsizetype i = 0; i < jsCallData.argc; ++i) { |
| 419 | jsCallData.args[i] = coerce( |
| 420 | engine, value: i < argc ? argv[i] : QV4::Value::fromReturnedValue(val: Encode::undefined()), |
| 421 | qmlType: typedFunction->types[i + 1], isList: formals[i].type.isList()); |
| 422 | } |
| 423 | |
| 424 | ScopedValue result(scope, call(jsCallData.args, jsCallData.argc)); |
| 425 | return coerce(engine, value: result, qmlType: typedFunction->types[0], isList: compiledFunction->returnType.isList()); |
| 426 | } |
| 427 | |
| 428 | // Note: \a to is unininitialized here! This is in contrast to most other related functions. |
| 429 | inline void coerce( |
| 430 | ExecutionEngine *engine, QMetaType fromType, const void *from, QMetaType toType, void *to) |
| 431 | { |
| 432 | if ((fromType.flags() & QMetaType::PointerToQObject) |
| 433 | && (toType.flags() & QMetaType::PointerToQObject)) { |
| 434 | QObject *fromObj = *static_cast<QObject * const*>(from); |
| 435 | *static_cast<QObject **>(to) |
| 436 | = (fromObj && fromObj->metaObject()->inherits(metaObject: toType.metaObject())) |
| 437 | ? fromObj |
| 438 | : nullptr; |
| 439 | return; |
| 440 | } |
| 441 | |
| 442 | if (toType == QMetaType::fromType<QVariant>()) { |
| 443 | new (to) QVariant(fromType, from); |
| 444 | return; |
| 445 | } |
| 446 | |
| 447 | if (toType == QMetaType::fromType<QJSPrimitiveValue>()) { |
| 448 | new (to) QJSPrimitiveValue(fromType, from); |
| 449 | return; |
| 450 | } |
| 451 | |
| 452 | if (fromType == QMetaType::fromType<QVariant>()) { |
| 453 | const QVariant *fromVariant = static_cast<const QVariant *>(from); |
| 454 | if (fromVariant->metaType() == toType) |
| 455 | toType.construct(where: to, copy: fromVariant->data()); |
| 456 | else |
| 457 | coerce(engine, fromType: fromVariant->metaType(), from: fromVariant->data(), toType, to); |
| 458 | return; |
| 459 | } |
| 460 | |
| 461 | if (fromType == QMetaType::fromType<QJSPrimitiveValue>()) { |
| 462 | const QJSPrimitiveValue *fromPrimitive = static_cast<const QJSPrimitiveValue *>(from); |
| 463 | if (fromPrimitive->metaType() == toType) |
| 464 | toType.construct(where: to, copy: fromPrimitive->data()); |
| 465 | else |
| 466 | coerce(engine, fromType: fromPrimitive->metaType(), from: fromPrimitive->data(), toType, to); |
| 467 | return; |
| 468 | } |
| 469 | |
| 470 | // TODO: This is expensive. We might establish a direct C++-to-C++ type coercion, like we have |
| 471 | // for JS-to-JS. However, we shouldn't need this very often. Most of the time the compiler |
| 472 | // will generate code that passes the right arguments. |
| 473 | if (toType.flags() & QMetaType::NeedsConstruction) |
| 474 | toType.construct(where: to); |
| 475 | QV4::Scope scope(engine); |
| 476 | QV4::ScopedValue value(scope, engine->fromData(type: fromType, ptr: from)); |
| 477 | if (!ExecutionEngine::metaTypeFromJS(value, type: toType, data: to)) |
| 478 | QMetaType::convert(fromType, from, toType, to); |
| 479 | } |
| 480 | |
| 481 | template<typename TypedFunction, typename Callable> |
| 482 | void coerceAndCall( |
| 483 | ExecutionEngine *engine, const TypedFunction *typedFunction, |
| 484 | void **argv, const QMetaType *types, int argc, Callable call) |
| 485 | { |
| 486 | const qsizetype numFunctionArguments = typedFunction->parameterCount(); |
| 487 | |
| 488 | Q_ALLOCA_DECLARE(void *, transformedArguments); |
| 489 | Q_ALLOCA_DECLARE(void, transformedResult); |
| 490 | |
| 491 | const QMetaType returnType = typedFunction->returnMetaType(); |
| 492 | const QMetaType frameReturn = types[0]; |
| 493 | bool returnsQVariantWrapper = false; |
| 494 | if (argv[0] && returnType != frameReturn) { |
| 495 | Q_ALLOCA_ASSIGN(void *, transformedArguments, (numFunctionArguments + 1) * sizeof(void *)); |
| 496 | memcpy(dest: transformedArguments, src: argv, n: (argc + 1) * sizeof(void *)); |
| 497 | |
| 498 | if (frameReturn == QMetaType::fromType<QVariant>()) { |
| 499 | QVariant *returnValue = static_cast<QVariant *>(argv[0]); |
| 500 | *returnValue = QVariant(returnType); |
| 501 | transformedResult = transformedArguments[0] = returnValue->data(); |
| 502 | returnsQVariantWrapper = true; |
| 503 | } else if (returnType.sizeOf() > 0) { |
| 504 | Q_ALLOCA_ASSIGN(void, transformedResult, returnType.sizeOf()); |
| 505 | transformedArguments[0] = transformedResult; |
| 506 | if (returnType.flags() & QMetaType::NeedsConstruction) |
| 507 | returnType.construct(where: transformedResult); |
| 508 | } else { |
| 509 | transformedResult = transformedArguments[0] = &argc; // Some non-null marker value |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | for (qsizetype i = 0; i < numFunctionArguments; ++i) { |
| 514 | const bool isValid = argc > i; |
| 515 | const QMetaType frameType = isValid ? types[i + 1] : QMetaType(); |
| 516 | |
| 517 | const QMetaType argumentType = typedFunction->parameterMetaType(i); |
| 518 | if (isValid && argumentType == frameType) |
| 519 | continue; |
| 520 | |
| 521 | if (transformedArguments == nullptr) { |
| 522 | Q_ALLOCA_ASSIGN(void *, transformedArguments, (numFunctionArguments + 1) * sizeof(void *)); |
| 523 | memcpy(dest: transformedArguments, src: argv, n: (argc + 1) * sizeof(void *)); |
| 524 | } |
| 525 | |
| 526 | if (argumentType.sizeOf() == 0) { |
| 527 | transformedArguments[i + 1] = nullptr; |
| 528 | continue; |
| 529 | } |
| 530 | |
| 531 | void *frameVal = isValid ? argv[i + 1] : nullptr; |
| 532 | if (isValid && frameType == QMetaType::fromType<QVariant>()) { |
| 533 | QVariant *variant = static_cast<QVariant *>(frameVal); |
| 534 | |
| 535 | const QMetaType variantType = variant->metaType(); |
| 536 | if (variantType == argumentType) { |
| 537 | // Slightly nasty, but we're allowed to do this. |
| 538 | // We don't want to destruct() the QVariant's data() below. |
| 539 | transformedArguments[i + 1] = argv[i + 1] = variant->data(); |
| 540 | } else { |
| 541 | Q_ALLOCA_VAR(void, arg, argumentType.sizeOf()); |
| 542 | coerce(engine, fromType: variantType, from: variant->constData(), toType: argumentType, to: arg); |
| 543 | transformedArguments[i + 1] = arg; |
| 544 | } |
| 545 | continue; |
| 546 | } |
| 547 | |
| 548 | Q_ALLOCA_VAR(void, arg, argumentType.sizeOf()); |
| 549 | |
| 550 | if (isValid) |
| 551 | coerce(engine, fromType: frameType, from: frameVal, toType: argumentType, to: arg); |
| 552 | else |
| 553 | argumentType.construct(where: arg); |
| 554 | |
| 555 | transformedArguments[i + 1] = arg; |
| 556 | } |
| 557 | |
| 558 | if (!transformedArguments) { |
| 559 | call(argv, numFunctionArguments); |
| 560 | return; |
| 561 | } |
| 562 | |
| 563 | call(transformedArguments, numFunctionArguments); |
| 564 | |
| 565 | if (transformedResult && !returnsQVariantWrapper) { |
| 566 | if (frameReturn.sizeOf() > 0) { |
| 567 | if (frameReturn.flags() & QMetaType::NeedsDestruction) |
| 568 | frameReturn.destruct(data: argv[0]); |
| 569 | coerce(engine, fromType: returnType, from: transformedResult, toType: frameReturn, to: argv[0]); |
| 570 | } |
| 571 | if (returnType.flags() & QMetaType::NeedsDestruction) |
| 572 | returnType.destruct(data: transformedResult); |
| 573 | } |
| 574 | |
| 575 | for (qsizetype i = 0; i < numFunctionArguments; ++i) { |
| 576 | void *arg = transformedArguments[i + 1]; |
| 577 | if (arg == nullptr) |
| 578 | continue; |
| 579 | if (i >= argc || arg != argv[i + 1]) { |
| 580 | const QMetaType argumentType = typedFunction->parameterMetaType(i); |
| 581 | if (argumentType.flags() & QMetaType::NeedsDestruction) |
| 582 | argumentType.destruct(data: arg); |
| 583 | } |
| 584 | } |
| 585 | } |
| 586 | |
| 587 | } // namespace QV4 |
| 588 | |
| 589 | QT_END_NAMESPACE |
| 590 | |
| 591 | #endif // QV4JSCALL_H |
| 592 | |