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
35QT_BEGIN_NAMESPACE
36
37namespace QV4 {
38
39template<typename Args>
40CallData *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
57struct 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
74struct 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
96inline
97ReturnedValue FunctionObject::callAsConstructor(const JSCallData &data) const
98{
99 return callAsConstructor(argv: data.args, argc: data.argc, newTarget: this);
100}
101
102inline
103ReturnedValue FunctionObject::call(const JSCallData &data) const
104{
105 return call(thisObject: data.thisObject, argv: data.args, argc: data.argc);
106}
107
108void populateJSCallArguments(ExecutionEngine *v4, JSCallArguments &jsCall, int argc,
109 void **args, const QMetaType *types);
110
111template<typename Callable>
112ReturnedValue 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
179template<typename Callable>
180bool 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
222inline ReturnedValue coerce(
223 ExecutionEngine *engine, const Value &value, const QQmlType &qmlType, bool isList);
224
225inline 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
238enum CoercionProblem
239{
240 InsufficientAnnotation,
241 InvalidListType
242};
243
244Q_QML_EXPORT void warnAboutCoercionToVoid(
245 ExecutionEngine *engine, const Value &value, CoercionProblem problem);
246
247inline 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->containerPutIndexed(index: i, value: v);
307 }
308 return sequence->asReturnedValue();
309}
310
311inline 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
408template<typename Callable>
409ReturnedValue 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.
429inline 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
481template<typename TypedFunction, typename Callable>
482void 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
589QT_END_NAMESPACE
590
591#endif // QV4JSCALL_H
592

source code of qtdeclarative/src/qml/jsruntime/qv4jscall_p.h