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 for (; i < length; ++i) {
292 ScopedValue v(scope, array->get(idx: i));
293 listProperty->append(listProperty, coerceQObject(value: v, qmlType));
294 }
295
296 return newList->asReturnedValue();
297 }
298
299 QV4::Scoped<Sequence> sequence(
300 scope, SequencePrototype::fromData(engine, type, metaSequence: metaSequence(), data: nullptr));
301 const qsizetype length = array->getLength();
302 for (qsizetype i = 0; i < length; ++i)
303 sequence->containerPutIndexed(index: i, value: array->get(idx: i));
304 return sequence->asReturnedValue();
305}
306
307inline ReturnedValue coerce(
308 ExecutionEngine *engine, const Value &value, const QQmlType &qmlType, bool isList)
309{
310 // These are all the named non-list, non-QObject builtins. Only those need special handling.
311 // Some of them may be wrapped in VariantObject because that is how they are stored in VME
312 // properties.
313 if (isList)
314 return coerceListType(engine, value, qmlType);
315
316 const QMetaType metaType = qmlType.typeId();
317 if (!metaType.isValid()) {
318 if (!value.isUndefined())
319 warnAboutCoercionToVoid(engine, value, problem: InsufficientAnnotation);
320 return value.asReturnedValue();
321 }
322
323 switch (metaType.id()) {
324 case QMetaType::Void:
325 return Encode::undefined();
326 case QMetaType::QVariant:
327 return value.asReturnedValue();
328 case QMetaType::Int:
329 return Encode(value.toInt32());
330 case QMetaType::Double:
331 return value.convertedToNumber();
332 case QMetaType::QString:
333 return value.toString(e: engine)->asReturnedValue();
334 case QMetaType::Bool:
335 return Encode(value.toBoolean());
336 case QMetaType::QDateTime:
337 if (value.as<DateObject>())
338 return value.asReturnedValue();
339 if (const VariantObject *varObject = value.as<VariantObject>()) {
340 const QVariant &var = varObject->d()->data();
341 switch (var.metaType().id()) {
342 case QMetaType::QDateTime:
343 return engine->newDateObject(dateTime: var.value<QDateTime>())->asReturnedValue();
344 case QMetaType::QTime:
345 return engine->newDateObject(time: var.value<QTime>(), parent: nullptr, index: -1, flags: 0)->asReturnedValue();
346 case QMetaType::QDate:
347 return engine->newDateObject(date: var.value<QDate>(), parent: nullptr, index: -1, flags: 0)->asReturnedValue();
348 default:
349 break;
350 }
351 }
352 return engine->newDateObject(dateTime: QDateTime())->asReturnedValue();
353 case QMetaType::QUrl:
354 if (value.as<UrlObject>())
355 return value.asReturnedValue();
356 if (const VariantObject *varObject = value.as<VariantObject>()) {
357 const QVariant &var = varObject->d()->data();
358 return var.metaType() == QMetaType::fromType<QUrl>()
359 ? engine->newUrlObject(url: var.value<QUrl>())->asReturnedValue()
360 : engine->newUrlObject()->asReturnedValue();
361 }
362 // Since URL properties are stored as string, we need to support the string conversion here.
363 if (const String *string = value.stringValue())
364 return engine->newUrlObject(url: QUrl(string->toQString()))->asReturnedValue();
365 return engine->newUrlObject()->asReturnedValue();
366#if QT_CONFIG(regularexpression)
367 case QMetaType::QRegularExpression:
368 if (value.as<RegExpObject>())
369 return value.asReturnedValue();
370 if (const VariantObject *varObject = value.as<VariantObject>()) {
371 const QVariant &var = varObject->d()->data();
372 if (var.metaType() == QMetaType::fromType<QRegularExpression>())
373 return engine->newRegExpObject(re: var.value<QRegularExpression>())->asReturnedValue();
374 }
375 return engine->newRegExpObject(pattern: QString(), flags: 0)->asReturnedValue();
376#endif
377 default:
378 break;
379 }
380
381 if (metaType.flags() & QMetaType::PointerToQObject) {
382 return coerceQObject(value, qmlType)
383 ? value.asReturnedValue()
384 : Encode::null();
385 }
386
387 if (const QQmlValueTypeWrapper *wrapper = value.as<QQmlValueTypeWrapper>()) {
388 if (wrapper->type() == metaType)
389 return value.asReturnedValue();
390 }
391
392 if (void *target = QQmlValueTypeProvider::heapCreateValueType(targetType: qmlType, source: value, engine)) {
393 Heap::QQmlValueTypeWrapper *wrapper = engine->memoryManager->allocate<QQmlValueTypeWrapper>(
394 args: nullptr, args: metaType, args: qmlType.metaObjectForValueType(),
395 args: nullptr, args: -1, args: Heap::ReferenceObject::NoFlag);
396 Q_ASSERT(!wrapper->gadgetPtr());
397 wrapper->setGadgetPtr(target);
398 return wrapper->asReturnedValue();
399 }
400
401 return Encode::undefined();
402}
403
404template<typename Callable>
405ReturnedValue coerceAndCall(
406 ExecutionEngine *engine,
407 const Function::JSTypedFunction *typedFunction, const CompiledData::Function *compiledFunction,
408 const Value *argv, int argc, Callable call)
409{
410 Scope scope(engine);
411
412 QV4::JSCallArguments jsCallData(scope, typedFunction->types.size() - 1);
413 const CompiledData::Parameter *formals = compiledFunction->formalsTable();
414 for (qsizetype i = 0; i < jsCallData.argc; ++i) {
415 jsCallData.args[i] = coerce(
416 engine, value: i < argc ? argv[i] : Encode::undefined(),
417 qmlType: typedFunction->types[i + 1], isList: formals[i].type.isList());
418 }
419
420 ScopedValue result(scope, call(jsCallData.args, jsCallData.argc));
421 return coerce(engine, value: result, qmlType: typedFunction->types[0], isList: compiledFunction->returnType.isList());
422}
423
424// Note: \a to is unininitialized here! This is in contrast to most other related functions.
425inline void coerce(
426 ExecutionEngine *engine, QMetaType fromType, const void *from, QMetaType toType, void *to)
427{
428 if ((fromType.flags() & QMetaType::PointerToQObject)
429 && (toType.flags() & QMetaType::PointerToQObject)) {
430 QObject *fromObj = *static_cast<QObject * const*>(from);
431 *static_cast<QObject **>(to)
432 = (fromObj && fromObj->metaObject()->inherits(metaObject: toType.metaObject()))
433 ? fromObj
434 : nullptr;
435 return;
436 }
437
438 if (toType == QMetaType::fromType<QVariant>()) {
439 new (to) QVariant(fromType, from);
440 return;
441 }
442
443 if (toType == QMetaType::fromType<QJSPrimitiveValue>()) {
444 new (to) QJSPrimitiveValue(fromType, from);
445 return;
446 }
447
448 if (fromType == QMetaType::fromType<QVariant>()) {
449 const QVariant *fromVariant = static_cast<const QVariant *>(from);
450 if (fromVariant->metaType() == toType)
451 toType.construct(where: to, copy: fromVariant->data());
452 else
453 coerce(engine, fromType: fromVariant->metaType(), from: fromVariant->data(), toType, to);
454 return;
455 }
456
457 if (fromType == QMetaType::fromType<QJSPrimitiveValue>()) {
458 const QJSPrimitiveValue *fromPrimitive = static_cast<const QJSPrimitiveValue *>(from);
459 if (fromPrimitive->metaType() == toType)
460 toType.construct(where: to, copy: fromPrimitive->data());
461 else
462 coerce(engine, fromType: fromPrimitive->metaType(), from: fromPrimitive->data(), toType, to);
463 return;
464 }
465
466 // TODO: This is expensive. We might establish a direct C++-to-C++ type coercion, like we have
467 // for JS-to-JS. However, we shouldn't need this very often. Most of the time the compiler
468 // will generate code that passes the right arguments.
469 if (toType.flags() & QMetaType::NeedsConstruction)
470 toType.construct(where: to);
471 QV4::Scope scope(engine);
472 QV4::ScopedValue value(scope, engine->fromData(type: fromType, ptr: from));
473 if (!ExecutionEngine::metaTypeFromJS(value, type: toType, data: to))
474 QMetaType::convert(fromType, from, toType, to);
475}
476
477template<typename TypedFunction, typename Callable>
478void coerceAndCall(
479 ExecutionEngine *engine, const TypedFunction *typedFunction,
480 void **argv, const QMetaType *types, int argc, Callable call)
481{
482 const qsizetype numFunctionArguments = typedFunction->parameterCount();
483
484 Q_ALLOCA_DECLARE(void *, transformedArguments);
485 Q_ALLOCA_DECLARE(void, transformedResult);
486
487 const QMetaType returnType = typedFunction->returnMetaType();
488 const QMetaType frameReturn = types[0];
489 bool returnsQVariantWrapper = false;
490 if (argv[0] && returnType != frameReturn) {
491 Q_ALLOCA_ASSIGN(void *, transformedArguments, (numFunctionArguments + 1) * sizeof(void *));
492 memcpy(dest: transformedArguments, src: argv, n: (argc + 1) * sizeof(void *));
493
494 if (frameReturn == QMetaType::fromType<QVariant>()) {
495 QVariant *returnValue = static_cast<QVariant *>(argv[0]);
496 *returnValue = QVariant(returnType);
497 transformedResult = transformedArguments[0] = returnValue->data();
498 returnsQVariantWrapper = true;
499 } else if (returnType.sizeOf() > 0) {
500 Q_ALLOCA_ASSIGN(void, transformedResult, returnType.sizeOf());
501 transformedArguments[0] = transformedResult;
502 if (returnType.flags() & QMetaType::NeedsConstruction)
503 returnType.construct(where: transformedResult);
504 } else {
505 transformedResult = transformedArguments[0] = &argc; // Some non-null marker value
506 }
507 }
508
509 for (qsizetype i = 0; i < numFunctionArguments; ++i) {
510 const bool isValid = argc > i;
511 const QMetaType frameType = isValid ? types[i + 1] : QMetaType();
512
513 const QMetaType argumentType = typedFunction->parameterMetaType(i);
514 if (isValid && argumentType == frameType)
515 continue;
516
517 if (transformedArguments == nullptr) {
518 Q_ALLOCA_ASSIGN(void *, transformedArguments, (numFunctionArguments + 1) * sizeof(void *));
519 memcpy(dest: transformedArguments, src: argv, n: (argc + 1) * sizeof(void *));
520 }
521
522 if (argumentType.sizeOf() == 0) {
523 transformedArguments[i + 1] = nullptr;
524 continue;
525 }
526
527 void *frameVal = isValid ? argv[i + 1] : nullptr;
528 if (isValid && frameType == QMetaType::fromType<QVariant>()) {
529 QVariant *variant = static_cast<QVariant *>(frameVal);
530
531 const QMetaType variantType = variant->metaType();
532 if (variantType == argumentType) {
533 // Slightly nasty, but we're allowed to do this.
534 // We don't want to destruct() the QVariant's data() below.
535 transformedArguments[i + 1] = argv[i + 1] = variant->data();
536 } else {
537 Q_ALLOCA_VAR(void, arg, argumentType.sizeOf());
538 coerce(engine, fromType: variantType, from: variant->constData(), toType: argumentType, to: arg);
539 transformedArguments[i + 1] = arg;
540 }
541 continue;
542 }
543
544 Q_ALLOCA_VAR(void, arg, argumentType.sizeOf());
545
546 if (isValid)
547 coerce(engine, fromType: frameType, from: frameVal, toType: argumentType, to: arg);
548 else
549 argumentType.construct(where: arg);
550
551 transformedArguments[i + 1] = arg;
552 }
553
554 if (!transformedArguments) {
555 call(argv, numFunctionArguments);
556 return;
557 }
558
559 call(transformedArguments, numFunctionArguments);
560
561 if (transformedResult && !returnsQVariantWrapper) {
562 if (frameReturn.sizeOf() > 0) {
563 if (frameReturn.flags() & QMetaType::NeedsDestruction)
564 frameReturn.destruct(data: argv[0]);
565 coerce(engine, fromType: returnType, from: transformedResult, toType: frameReturn, to: argv[0]);
566 }
567 if (returnType.flags() & QMetaType::NeedsDestruction)
568 returnType.destruct(data: transformedResult);
569 }
570
571 for (qsizetype i = 0; i < numFunctionArguments; ++i) {
572 void *arg = transformedArguments[i + 1];
573 if (arg == nullptr)
574 continue;
575 if (i >= argc || arg != argv[i + 1]) {
576 const QMetaType argumentType = typedFunction->parameterMetaType(i);
577 if (argumentType.flags() & QMetaType::NeedsDestruction)
578 argumentType.destruct(data: arg);
579 }
580 }
581}
582
583} // namespace QV4
584
585QT_END_NAMESPACE
586
587#endif // QV4JSCALL_H
588

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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