1// Copyright (C) 2020 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
4#include <QtQml/qjsmanagedvalue.h>
5#include <QtQml/qjsengine.h>
6#include <QtQml/private/qv4persistent_p.h>
7#include <QtQml/private/qv4engine_p.h>
8#include <QtQml/private/qv4mm_p.h>
9#include <QtQml/private/qjsvalue_p.h>
10#include <QtQml/private/qv4runtime_p.h>
11#include <QtQml/private/qv4functionobject_p.h>
12#include <QtQml/private/qv4jscall_p.h>
13#include <QtQml/private/qv4urlobject_p.h>
14#include <QtQml/private/qv4variantobject_p.h>
15#include <QtQml/private/qv4qobjectwrapper_p.h>
16#include <QtQml/private/qv4regexpobject_p.h>
17#include <QtQml/private/qv4dateobject_p.h>
18#include <QtQml/private/qv4errorobject_p.h>
19#include <QtQml/private/qv4identifiertable_p.h>
20
21#include <QtCore/qregularexpression.h>
22#include <QtCore/qurl.h>
23#include <QtCore/qdatetime.h>
24
25QT_BEGIN_NAMESPACE
26
27/*!
28 * \class QJSManagedValue
29 * \inmodule QtQml
30 * \since 6.1
31 *
32 * \inmodule QtQml
33 *
34 * \brief QJSManagedValue represents a value on the JavaScript heap belonging to a QJSEngine.
35 *
36 * The QJSManagedValue class allows interaction with JavaScript values in most
37 * ways you can interact with them from JavaScript itself. You can get and set
38 * properties and prototypes, and you can access arrays. Additionally, you can
39 * transform the value into the Qt counterparts of JavaScript objects. For
40 * example, a Url object may be transformed into a QUrl.
41 *
42 * A QJSManagedValue is always bound to a particular QJSEngine. You cannot use
43 * it independently. This means that you cannot have a QJSManagedValue from one
44 * engine be a property or a proptotype of a QJSManagedValue from a different
45 * engine.
46 *
47 * In contrast to QJSValue, almost all values held by QJSManagedValue live on
48 * the JavaScript heap. There is no inline or unmanaged storage. Therefore, you
49 * can get the prototype of a primitive value, and you can get the \c length
50 * property of a string.
51 *
52 * Only default-constructed or moved-from QJSManagedValues do not hold a value
53 * on the JavaScript heap. They represent \c undefined, which doesn't have any
54 * properties or prototypes.
55 *
56 * Also in contrast to QJSValue, QJSManagedValue does not catch any JavaScript
57 * exceptions. If an operation on a QJSManagedValue causes an error, it will
58 * generally return an \c undefined value and QJSEngine::hasError() will return
59 * \c true afterwards. You can then catch the exception using
60 * QJSEngine::catchError(), or pass it up the stack, at your own discretion.
61 *
62 * \note As the reference to the value on the JavaScript heap has to be freed
63 * on destruction, you cannot move a QJSManagedValue to a different thread.
64 * The destruction would take place in the new thread, which would create a race
65 * condition with the garbage collector on the original thread. This also means
66 * that you cannot hold a QJSManagedValue beyond the lifespan of its engine.
67 *
68 * The recommended way of working with a QJSManagedValue is creating it
69 * on the stack, possibly by moving a QJSValue and adding an engine, then
70 * performing the necessary operations on it, and finally moving it back into a
71 * QJSValue for storage. Moving between QJSManagedValue and QJSValue is fast.
72 */
73
74/*!
75 * \enum QJSManagedValue::Type
76 *
77 * This enum represents the JavaScript native types, as specified by
78 * \l{ECMA-262}.
79 *
80 * \value Undefined The \c undefined type
81 * \value Boolean The \c boolean type
82 * \value Number The \c number type
83 * \value String The \c string type
84 * \value Object The \c object type
85 * \value Symbol The \c symbol type
86 * \value Function The \c function type
87 *
88 * Note that the \c null value is not a type of itself but rather a special kind
89 * of object. You can query a QJSManagedValue for this condition using the
90 * isNull() method. Furthermore, JavaScript has no integer type, but it knows a
91 * special treatment of numbers in preparation for integer only operations. You
92 * can query a QJSManagedValue to find out whether it holds the result of such a
93 * treatment by using the isInteger() method.
94 */
95
96/*!
97 * \fn QJSManagedValue::QJSManagedValue()
98 *
99 * Creates a QJSManagedValue that represents the JavaScript \c undefined value.
100 * This is the only value not stored on the JavaScript heap. Calling engine()
101 * on a default-constructed QJSManagedValue will return nullptr.
102 */
103
104static QV4::ExecutionEngine *v4Engine(QV4::Value *d)
105{
106 if (!d)
107 return nullptr;
108
109 QV4::ExecutionEngine *v4 = QV4::PersistentValueStorage::getEngine(v: d);
110 Q_ASSERT(v4);
111 return v4;
112}
113
114/*!
115 * Creates a QJSManagedValue from \a value, using the heap of \a engine. If
116 * \a value is itself managed and the engine it belongs to is not \a engine,
117 * the result is an \c undefined value, and a warning is generated.
118 */
119QJSManagedValue::QJSManagedValue(QJSValue value, QJSEngine *engine)
120{
121 QV4::ExecutionEngine *v4 = engine->handle();
122
123 if (QV4::Value *m = QJSValuePrivate::takeManagedValue(jsval: &value)) {
124 if (Q_UNLIKELY(v4Engine(m) != v4)) {
125 qWarning(msg: "QJSManagedValue(QJSValue, QJSEngine *) failed: "
126 "Value was created in different engine.");
127 QV4::PersistentValueStorage::free(v: m);
128 return;
129 }
130
131 d = m;
132 return;
133 }
134
135 d = v4->memoryManager->m_persistentValues->allocate();
136
137 if (const QString *string = QJSValuePrivate::asQString(jsval: &value))
138 *d = v4->newString(s: *string);
139 else
140 *d = QJSValuePrivate::asReturnedValue(jsval: &value);
141}
142
143/*!
144 * Creates a QJSManagedValue from \a value using the heap of \a engine.
145 */
146QJSManagedValue::QJSManagedValue(const QJSPrimitiveValue &value, QJSEngine *engine) :
147 QJSManagedValue(engine->handle())
148{
149 switch (value.type()) {
150 case QJSPrimitiveValue::Undefined:
151 *d = QV4::Encode::undefined();
152 return;
153 case QJSPrimitiveValue::Null:
154 *d = QV4::Encode::null();
155 return;
156 case QJSPrimitiveValue::Boolean:
157 *d = QV4::Encode(value.asBoolean());
158 return;
159 case QJSPrimitiveValue::Integer:
160 *d = QV4::Encode(value.asInteger());
161 return;
162 case QJSPrimitiveValue::Double:
163 *d = QV4::Encode(value.asDouble());
164 return;
165 case QJSPrimitiveValue::String:
166 *d = engine->handle()->newString(s: value.asString());
167 return;
168 }
169
170 Q_UNREACHABLE();
171}
172
173/*!
174 * Creates a QJSManagedValue from \a variant using the heap of \a engine.
175 */
176QJSManagedValue::QJSManagedValue(const QVariant &variant, QJSEngine *engine) :
177 QJSManagedValue(engine->handle())
178{
179 *d = engine->handle()->fromVariant(variant);
180}
181
182/*!
183 * Creates a QJSManagedValue from \a string using the heap of \a engine.
184 */
185QJSManagedValue::QJSManagedValue(const QString &string, QJSEngine *engine) :
186 QJSManagedValue(engine->handle())
187{
188 *d = engine->handle()->newString(s: string);
189}
190
191/*!
192 * Destroys the QJSManagedValue.
193 *
194 * \note This frees the memory slot it holds on the JavaScript heap. You must
195 * not destroy a QJSManagedValue from a different thread than the one
196 * where the QJSEngine it belongs to lives.
197 */
198QJSManagedValue::~QJSManagedValue()
199{
200 QV4::PersistentValueStorage::free(v: d);
201}
202
203/*!
204 * Move-constructs a QJSManagedValue from \a other. This leaves \a other in
205 * the default-constructed state where it represents undefined and does not
206 * belong to any engine.
207 */
208QJSManagedValue::QJSManagedValue(QJSManagedValue &&other)
209{
210 qSwap(value1&: d, value2&: other.d);
211}
212
213/*!
214 * Move-assigns a QJSManagedValue from \a other. This leaves \a other in
215 * the default-constructed state where it represents undefined and does not
216 * belong to any engine.
217 *
218 * \note This frees the memory slot this QJSManagedValue holds on the
219 * JavaScript heap. You must not move-assign a QJSManagedValue on a
220 * different thread than the one where the QJSEngine it belongs to lives.
221 */
222QJSManagedValue &QJSManagedValue::operator=(QJSManagedValue &&other)
223{
224 if (this != &other) {
225 QV4::PersistentValueStorage::free(v: d);
226 d = nullptr;
227 qSwap(value1&: d, value2&: other.d);
228 }
229 return *this;
230}
231
232/*!
233 * Invokes the JavaScript '==' operator on this QJSManagedValue and \a other,
234 * and returns the result.
235 *
236 * \sa strictlyEquals
237 */
238bool QJSManagedValue::equals(const QJSManagedValue &other) const
239{
240 if (!d)
241 return !other.d || other.d->isNullOrUndefined();
242 if (!other.d)
243 return d->isNullOrUndefined();
244
245 return QV4::Runtime::CompareEqual::call(*d, *other.d);
246}
247
248/*!
249 * Invokes the JavaScript '===' operator on this QJSManagedValue and \a other,
250 * and returns the result.
251 *
252 * \sa equals
253 */
254bool QJSManagedValue::strictlyEquals(const QJSManagedValue &other) const
255{
256 if (!d)
257 return !other.d || other.d->isUndefined();
258 if (!other.d)
259 return d->isUndefined();
260
261 return QV4::RuntimeHelpers::strictEqual(x: *d, y: *other.d);
262}
263
264/*!
265 * Returns the QJSEngine this QJSManagedValue belongs to. Mind that the engine
266 * is always valid, unless the QJSManagedValue is default-constructed or moved
267 * from. In the latter case a nullptr is returned.
268 */
269QJSEngine *QJSManagedValue::engine() const
270{
271 if (!d)
272 return nullptr;
273 if (QV4::ExecutionEngine *v4 = QV4::PersistentValueStorage::getEngine(v: d))
274 return v4->jsEngine();
275 return nullptr;
276}
277
278/*!
279 * Returns the prototype for this QJSManagedValue. This works on any value. You
280 * can, for example retrieve the JavaScript \c boolean prototype from a \c boolean
281 * value.
282 */
283QJSManagedValue QJSManagedValue::prototype() const
284{
285 if (!d)
286 return QJSManagedValue();
287
288 QV4::ExecutionEngine *v4 = v4Engine(d);
289 QJSManagedValue result(v4);
290
291 if (auto object = d->as<QV4::Object>())
292 *result.d = object->getPrototypeOf();
293 else if (auto managed = d->as<QV4::Managed>())
294 *result.d = managed->internalClass()->prototype;
295 else if (d->isBoolean())
296 *result.d = v4->booleanPrototype();
297 else if (d->isNumber())
298 *result.d = v4->numberPrototype();
299
300 // If the prototype appears to be undefined, then it's actually null in JS terms.
301 if (result.d->isUndefined())
302 *result.d = QV4::Encode::null();
303
304 return result;
305}
306
307/*!
308 * Sets the prototype of this QJSManagedValue to \a prototype. A precondition
309 * is that \a prototype belongs to the same QJSEngine as this QJSManagedValue
310 * and is an object (including null). Furthermore, this QJSManagedValue has to
311 * be an object (excluding null), too, and you cannot create prototype cycles.
312 */
313void QJSManagedValue::setPrototype(const QJSManagedValue &prototype)
314{
315 auto object = d ? d->as<QV4::Object>() : nullptr;
316 if (!object) {
317 qWarning(msg: "QJSManagedValue::setPrototype() failed: "
318 "Can only set a prototype on an object (excluding null).");
319 return;
320 }
321
322 // Object includes null ...
323 if (prototype.type() != QJSManagedValue::Object) {
324 qWarning(msg: "QJSManagedValue::setPrototype() failed: "
325 "Can only set objects (including null) as prototypes.");
326 return;
327 }
328
329 if (Q_UNLIKELY(object->engine() != v4Engine(prototype.d))) {
330 qWarning(msg: "QJSManagedValue::setPrototype() failed: "
331 "Prototype was created in differen engine.");
332 return;
333 }
334
335 // ... Null becomes nullptr here. That is why it appears as undefined later.
336 if (!object->setPrototypeOf(prototype.d->as<QV4::Object>())) {
337 qWarning(msg: "QJSManagedValue::setPrototype() failed: "
338 "Prototype cycle detected.");
339 }
340}
341
342/*!
343 * Returns the JavaScript type of this QJSManagedValue.
344 */
345QJSManagedValue::Type QJSManagedValue::type() const
346{
347 if (!d || d->isUndefined())
348 return Undefined;
349 if (d->isBoolean())
350 return Boolean;
351 if (d->isNumber())
352 return Number;
353 if (d->isString())
354 return String;
355 if (d->isSymbol())
356 return Symbol;
357 if (d->isFunctionObject())
358 return Function;
359 return Object;
360}
361
362/*!
363 * \fn QJSManagedValue::isUndefined() const
364 *
365 * Returns \c true if the type of this QJSManagedValue is \c undefined,
366 * or \c false otherwise.
367 */
368
369/*!
370 * \fn QJSManagedValue::isBoolean() const
371 *
372 * Returns \c true if the type of this QJSManagedValue is \c boolean,
373 * or \c false otherwise.
374 */
375
376/*!
377 * \fn QJSManagedValue::isNumber() const
378 *
379 * Returns \c true if the type of this QJSManagedValue is \c number,
380 * or \c false otherwise.
381 */
382
383/*!
384 * \fn QJSManagedValue::isString() const
385 *
386 * Returns \c true if the type of this QJSManagedValue is \c string,
387 * or \c false otherwise.
388 */
389
390/*!
391 * \fn QJSManagedValue::isSymbol() const
392 *
393 * Returns \c true if the type of this QJSManagedValue is \c symbol,
394 * or \c false otherwise.
395 */
396
397/*!
398 * \fn QJSManagedValue::isObject() const
399 *
400 * Returns \c true if the type of this QJSManagedValue is \c object,
401 * or \c false otherwise.
402 */
403
404/*!
405 * \fn QJSManagedValue::isFunction() const
406 *
407 * Returns \c true if the type of this QJSManagedValue is \c function,
408 * \c false otherwise.
409 */
410
411/*!
412 * Returns \c true if this QJSManagedValue holds the JavaScript \c null value,
413 * or \c false otherwise.
414 */
415bool QJSManagedValue::isNull() const
416{
417 return d && d->isNull();
418}
419
420/*!
421 * Returns \c true if this QJSManagedValue holds an integer value, or \c false
422 * otherwise. The storage format of a number does not affect the result of any
423 * operations performed on it, but if an integer is stored, many operations are
424 * faster.
425 */
426bool QJSManagedValue::isInteger() const
427{
428 return d && d->isInteger();
429}
430
431/*!
432 * Returns \c true if this value represents a JavaScript regular expression
433 * object, or \c false otherwise.
434 */
435bool QJSManagedValue::isRegularExpression() const
436{
437 return d && d->as<QV4::RegExpObject>();
438}
439
440/*!
441 * Returns \c true if this value represents a JavaScript Array
442 * object, or \c false otherwise.
443 */
444bool QJSManagedValue::isArray() const
445{
446 return d && d->as<QV4::ArrayObject>();
447}
448
449/*!
450 * Returns \c true if this value represents a JavaScript Url
451 * object, or \c false otherwise.
452 */
453bool QJSManagedValue::isUrl() const
454{
455 return d && d->as<QV4::UrlObject>();
456}
457
458/*!
459 * Returns \c true if this value represents a QVariant managed on the JavaScript
460 * heap, or \c false otherwise.
461 */
462bool QJSManagedValue::isVariant() const
463{
464 return d && d->as<QV4::VariantObject>();
465}
466
467/*!
468 * Returns \c true if this value represents a QObject pointer managed on the
469 * JavaScript heap, or \c false otherwise.
470 */
471bool QJSManagedValue::isQObject() const
472{
473 return d && d->as<QV4::QObjectWrapper>();
474}
475
476/*!
477 * Returns \c true if this value represents a QMetaObject pointer managed on the
478 * JavaScript heap, or \c false otherwise.
479 */
480bool QJSManagedValue::isQMetaObject() const
481{
482 return d && d->as<QV4::QMetaObjectWrapper>();
483}
484
485/*!
486 * Returns \c true if this value represents a JavaScript Date object, or
487 * \c false otherwise.
488 */
489bool QJSManagedValue::isDate() const
490{
491 return d && d->as<QV4::DateObject>();
492}
493
494/*!
495 * Returns \c true if this value represents a JavaScript Error object, or
496 * \c false otherwise.
497 */
498bool QJSManagedValue::isError() const
499{
500 return d && d->as<QV4::ErrorObject>();
501}
502
503/*!
504 * \internal
505 *
506 * Returns \c true if this value represents a JavaScript meta type, or \c false
507 * otherwise.
508 */
509bool QJSManagedValue::isJsMetaType() const
510{
511 return d && d->as<QV4::InternalClass>();
512}
513
514/*!
515 * Converts the manged value to a string. If the managed value holds a string,
516 * that one is returned. Otherwise a string coercion by JavaScript rules is
517 * performed.
518 *
519 * \note Conversion of a managed value to a string can throw an exception. In
520 * particular, symbols cannot be coerced into strings, or a custom
521 * toString() method may throw. In this case the result is an empty
522 * string and the engine carries an error after the conversion.
523 */
524QString QJSManagedValue::toString() const
525{
526 return d ? d->toQString() : QStringLiteral("undefined");
527}
528
529/*!
530 * Converts the manged value to a number. If the managed value holds a number,
531 * that one is returned. Otherwise a number coercion by JavaScript rules is
532 * performed.
533 *
534 * \note Conversion of a managed value to a number can throw an exception. In
535 * particular, symbols cannot be coerced into numbers, or a custom
536 * valueOf() method may throw. In this case the result is 0 and the
537 * engine carries an error after the conversion.
538 */
539double QJSManagedValue::toNumber() const
540{
541 return d ? d->toNumber() : 0;
542}
543
544/*!
545 * Converts the manged value to a boolean. If the managed value holds a boolean,
546 * that one is returned. Otherwise a boolean coercion by JavaScript rules is
547 * performed.
548 */
549bool QJSManagedValue::toBoolean() const
550{
551 return d ? d->toBoolean() : false;
552}
553
554/*!
555 * Converts the manged value to an integer. This first converts the value to a
556 * number by the rules of toNumber(), and then clamps it into the integer range
557 * by the rules given for coercing the arguments to JavaScript bit shift
558 * operators into 32bit integers.
559 *
560 * Internally, the value may already be stored as an integer, in which case a
561 * fast path is taken.
562 *
563 * \note Conversion of a managed value to a number can throw an exception. In
564 * particular, symbols cannot be coerced into numbers, or a custom
565 * valueOf() method may throw. In this case the result is 0 and the
566 * engine carries an error after the conversion.
567 *
568 * \note The JavaScript rules for coercing numbers into 32bit integers are
569 * unintuitive.
570 */
571int QJSManagedValue::toInteger() const
572{
573 return d ? d->toInt32() : 0;
574}
575
576/*!
577 * Converts the manged value to a QJSPrimitiveValue. If the managed value holds
578 * a type supported by QJSPrimitiveValue, the value is copied. Otherwise the
579 * value is converted to a string, and the string is stored in
580 * QJSPrimitiveValue.
581 *
582 * \note Conversion of a managed value to a string can throw an exception. In
583 * particular, symbols cannot be coerced into strings, or a custom
584 * toString() method may throw. In this case the result is the undefined
585 * value and the engine carries an error after the conversion.
586 */
587QJSPrimitiveValue QJSManagedValue::toPrimitive() const
588{
589 if (!d || d->isUndefined())
590 return QJSPrimitiveUndefined();
591 if (d->isNull())
592 return QJSPrimitiveNull();
593 if (d->isBoolean())
594 return d->booleanValue();
595 if (d->isInteger())
596 return d->integerValue();
597 if (d->isNumber())
598 return d->doubleValue();
599
600 bool ok;
601 const QString result = d->toQString(ok: &ok);
602 return ok ? QJSPrimitiveValue(result) : QJSPrimitiveValue(QJSPrimitiveUndefined());
603}
604
605/*!
606 * Copies this QJSManagedValue into a new QJSValue. This is less efficient than
607 * move-constructing a QJSValue from a QJSManagedValue, but retains the
608 * QJSManagedValue.
609 */
610QJSValue QJSManagedValue::toJSValue() const
611{
612 return d ? QJSValuePrivate::fromReturnedValue(d: d->asReturnedValue()) : QJSValue();
613}
614
615/*!
616 * Copies this QJSManagedValue into a new QVariant. This also creates a useful
617 * QVariant if QJSManagedValue::isVariant() returns false. QVariant can hold all
618 * types supported by QJSManagedValue.
619 */
620QVariant QJSManagedValue::toVariant() const
621{
622 if (!d || d->isUndefined())
623 return QVariant();
624 if (d->isNull())
625 return QVariant(QMetaType::fromType<std::nullptr_t>(), nullptr);
626 if (d->isBoolean())
627 return QVariant(d->booleanValue());
628 if (d->isInteger())
629 return QVariant(d->integerValue());
630 if (d->isNumber())
631 return QVariant(d->doubleValue());
632 if (d->isString())
633 return QVariant(d->toQString());
634 if (d->as<QV4::Managed>())
635 return QV4::ExecutionEngine::toVariant(value: *d, typeHint: QMetaType{}, createJSValueForObjectsAndSymbols: true);
636
637 Q_UNREACHABLE_RETURN(QVariant());
638}
639
640/*!
641 * If this QJSManagedValue holds a JavaScript regular expression object, returns
642 * an equivalent QRegularExpression. Otherwise returns an invalid one.
643 */
644QRegularExpression QJSManagedValue::toRegularExpression() const
645{
646 if (const auto *r = d ? d->as<QV4::RegExpObject>() : nullptr)
647 return r->toQRegularExpression();
648 return {};
649}
650
651/*!
652 * If this QJSManagedValue holds a JavaScript Url object, returns
653 * an equivalent QUrl. Otherwise returns an invalid one.
654 */
655QUrl QJSManagedValue::toUrl() const
656{
657 if (const auto *u = d ? d->as<QV4::UrlObject>() : nullptr)
658 return u->toQUrl();
659 return {};
660}
661
662/*!
663 * If this QJSManagedValue holds a QObject pointer, returns it. Otherwise
664 * returns nullptr.
665 */
666QObject *QJSManagedValue::toQObject() const
667{
668 if (const auto *o = d ? d->as<QV4::QObjectWrapper>() : nullptr)
669 return o->object();
670 return {};
671}
672
673/*!
674 * If this QJSManagedValue holds a QMetaObject pointer, returns it.
675 * Otherwise returns nullptr.
676 */
677const QMetaObject *QJSManagedValue::toQMetaObject() const
678{
679 if (const auto *m = d ? d->as<QV4::QMetaObjectWrapper>() : nullptr)
680 return m->metaObject();
681 return {};
682}
683
684/*!
685 * If this QJSManagedValue holds a JavaScript Date object, returns an equivalent
686 * QDateTime. Otherwise returns an invalid one.
687 */
688QDateTime QJSManagedValue::toDateTime() const
689{
690 if (const auto *t = d ? d->as<QV4::DateObject>() : nullptr)
691 return t->toQDateTime();
692 return {};
693}
694
695/*!
696 * Returns \c true if this QJSManagedValue has a property \a name, otherwise
697 * returns \c false. The properties of the prototype chain are considered.
698 */
699bool QJSManagedValue::hasProperty(const QString &name) const
700{
701 if (!d || d->isNullOrUndefined())
702 return false;
703
704 if (d->isString() && name == QStringLiteral("length"))
705 return true;
706
707 if (QV4::Object *obj = d->as<QV4::Object>()) {
708 QV4::Scope scope(obj->engine());
709 QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name));
710 return obj->hasProperty(id: key);
711 }
712
713 return prototype().hasProperty(name);
714}
715
716/*!
717 * Returns \c true if this QJSManagedValue has a property \a name, otherwise
718 * returns \c false. The properties of the prototype chain are not considered.
719 */
720bool QJSManagedValue::hasOwnProperty(const QString &name) const
721{
722 if (!d || d->isNullOrUndefined())
723 return false;
724
725 if (d->isString() && name == QStringLiteral("length"))
726 return true;
727
728 if (QV4::Object *obj = d->as<QV4::Object>()) {
729 QV4::Scope scope(obj->engine());
730 QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name));
731 return obj->getOwnProperty(id: key) != QV4::Attr_Invalid;
732 }
733
734 return false;
735}
736
737/*!
738 * Returns the property \a name of this QJSManagedValue. The prototype chain
739 * is searched if the property is not found on the actual object.
740 */
741QJSValue QJSManagedValue::property(const QString &name) const
742{
743 if (!d)
744 return QJSValue();
745
746 if (d->isNullOrUndefined()) {
747 QV4::ExecutionEngine *e = v4Engine(d);
748 e->throwTypeError(QStringLiteral("Cannot read property '%1' of null").arg(a: name));
749 return QJSValue();
750 }
751
752 if (QV4::String *string = d->as<QV4::String>()) {
753 if (name == QStringLiteral("length"))
754 return QJSValue(string->d()->length());
755 }
756
757 if (QV4::Object *obj = d->as<QV4::Object>()) {
758 QV4::Scope scope(obj->engine());
759 QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name));
760 return QJSValuePrivate::fromReturnedValue(d: obj->get(id: key));
761 }
762
763 return prototype().property(name);
764}
765
766/*!
767 * Sets the property \a name to \a value on this QJSManagedValue. This can only
768 * be done on JavaScript values of type \c object. Furhermore, \a value has to be
769 * either a primitive or belong to the same engine as this value.
770 */
771void QJSManagedValue::setProperty(const QString &name, const QJSValue &value)
772{
773 if (!d)
774 return;
775
776 if (d->isNullOrUndefined()) {
777 v4Engine(d)->throwTypeError(
778 QStringLiteral("Value is null and could not be converted to an object"));
779 }
780
781 if (QV4::Object *obj = d->as<QV4::Object>()) {
782 QV4::Scope scope(obj->engine());
783 QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(jsval: &value);
784 if (Q_UNLIKELY(v4 && v4 != scope.engine)) {
785 qWarning(msg: "QJSManagedValue::setProperty() failed: "
786 "Value was created in different engine.");
787 return;
788 }
789 QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name));
790 obj->put(id: key, v: QJSValuePrivate::convertToReturnedValue(e: scope.engine, jsval: value));
791 }
792}
793
794/*!
795 * Deletes the property \a name from this QJSManagedValue. Returns \c true if
796 * the deletion succeeded, or \c false otherwise.
797 */
798bool QJSManagedValue::deleteProperty(const QString &name)
799{
800 if (!d)
801 return false;
802
803 if (QV4::Object *obj = d->as<QV4::Object>()) {
804 QV4::Scope scope(obj->engine());
805 QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(s: name));
806 return obj->deleteProperty(id: key);
807 }
808
809 return false;
810}
811
812/*!
813 * Returns \c true if this QJSManagedValue has an array index \a arrayIndex,
814 * otherwise returns \c false. The properties of the prototype chain are
815 * considered.
816 */
817bool QJSManagedValue::hasProperty(quint32 arrayIndex) const
818{
819 if (!d || d->isNullOrUndefined())
820 return false;
821
822 if (QV4::String *string = d->as<QV4::String>())
823 return arrayIndex < quint32(string->d()->length());
824
825 if (QV4::Object *obj = d->as<QV4::Object>()) {
826 bool hasProperty = false;
827 if (arrayIndex == std::numeric_limits<quint32>::max())
828 obj->get(name: obj->engine()->id_uintMax(), hasProperty: &hasProperty);
829 else
830 obj->get(idx: arrayIndex, hasProperty: &hasProperty);
831 return hasProperty;
832 }
833
834 return prototype().hasProperty(arrayIndex);
835}
836
837/*!
838 * Returns \c true if this QJSManagedValue has an array index \a arrayIndex,
839 * otherwise returns \c false. The properties of the prototype chain are not
840 * considered.
841 */
842bool QJSManagedValue::hasOwnProperty(quint32 arrayIndex) const
843{
844 if (!d || d->isNullOrUndefined())
845 return false;
846
847 if (QV4::String *string = d->as<QV4::String>())
848 return arrayIndex < quint32(string->d()->length());
849
850 if (QV4::Object *obj = d->as<QV4::Object>()) {
851 if (arrayIndex == std::numeric_limits<quint32>::max()) {
852 return obj->getOwnProperty(id: obj->engine()->id_uintMax()->toPropertyKey())
853 != QV4::Attr_Invalid;
854 } else {
855 return obj->getOwnProperty(id: QV4::PropertyKey::fromArrayIndex(idx: arrayIndex))
856 != QV4::Attr_Invalid;
857 }
858 }
859
860 return false;
861}
862
863/*!
864 * Returns the property stored at \a arrayIndex of this QJSManagedValue. The
865 * prototype chain is searched if the property is not found on the actual
866 * object.
867 */
868QJSValue QJSManagedValue::property(quint32 arrayIndex) const
869{
870 if (!d || d->isNullOrUndefined())
871 return QJSValue();
872
873 if (QV4::String *string = d->as<QV4::String>()) {
874 const QString qString = string->toQString();
875 if (arrayIndex < quint32(qString.size()))
876 return qString.sliced(pos: arrayIndex, n: 1);
877 return QJSValue();
878 }
879
880 if (QV4::Object *obj = d->as<QV4::Object>()) {
881 if (arrayIndex == std::numeric_limits<quint32>::max())
882 return QJSValuePrivate::fromReturnedValue(d: obj->get(name: obj->engine()->id_uintMax()));
883 else
884 return QJSValuePrivate::fromReturnedValue(d: obj->get(idx: arrayIndex));
885 }
886
887 return prototype().property(arrayIndex);
888}
889
890/*!
891 * Stores the \a value at \a arrayIndex in this QJSManagedValue. This can only
892 * be done on JavaScript values of type \c object, and it's not recommended if the
893 * value is not an array. Furhermore, \a value has to be either a primitive or
894 * belong to the same engine as this value.
895 */
896void QJSManagedValue::setProperty(quint32 arrayIndex, const QJSValue &value)
897{
898 if (!d)
899 return;
900
901 if (QV4::Object *obj = d->as<QV4::Object>()) {
902 QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(jsval: &value);
903 if (Q_UNLIKELY(v4 && v4 != obj->engine())) {
904 qWarning(msg: "QJSManagedValue::setProperty() failed: "
905 "Value was created in different engine.");
906 return;
907 }
908 obj->put(idx: arrayIndex, v: QJSValuePrivate::convertToReturnedValue(e: v4, jsval: value));
909 }
910}
911
912/*!
913 * Deletes the value stored at \a arrayIndex from this QJSManagedValue. Returns
914 * \c true if the deletion succeeded, or \c false otherwise.
915 */
916bool QJSManagedValue::deleteProperty(quint32 arrayIndex)
917{
918 if (!d)
919 return false;
920
921 if (QV4::Object *obj = d->as<QV4::Object>())
922 return obj->deleteProperty(id: QV4::PropertyKey::fromArrayIndex(idx: arrayIndex));
923
924 return false;
925}
926
927static const QV4::FunctionObject *functionObjectForCall(QV4::Value *d)
928{
929 if (Q_UNLIKELY(!d)) {
930 qWarning(msg: "QJSManagedValue: Calling a default-constructed or moved-from managed value"
931 "should throw an exception, but there is no engine to receive it.");
932 return nullptr;
933 }
934
935 if (const QV4::FunctionObject *f = d->as<QV4::FunctionObject>())
936 return f;
937
938 v4Engine(d)->throwTypeError(QStringLiteral("Value is not a function"));
939 return nullptr;
940}
941
942/*!
943 * If this QJSManagedValue represents a JavaScript FunctionObject, calls it with
944 * the given \a arguments, and returns the result. Otherwise returns a
945 * JavaScript \c undefined value.
946 *
947 * The \a arguments have to be either primitive values or belong to the same
948 * QJSEngine as this QJSManagedValue. Otherwise the call is not carried
949 * out and a JavaScript \c undefined value is returned.
950 */
951QJSValue QJSManagedValue::call(const QJSValueList &arguments) const
952{
953 const QV4::FunctionObject *f = functionObjectForCall(d);
954 if (!f)
955 return QJSValue();
956
957 QV4::ExecutionEngine *engine = f->engine();
958
959 QV4::Scope scope(engine);
960 QV4::JSCallArguments jsCallData(scope, arguments.size());
961 *jsCallData.thisObject = engine->globalObject;
962 int i = 0;
963 for (const QJSValue &arg : arguments) {
964 if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) {
965 qWarning(msg: "QJSManagedValue::call() failed: Argument was created in different engine.");
966 return QJSValue();
967 }
968 jsCallData.args[i++] = QJSValuePrivate::convertToReturnedValue(e: engine, jsval: arg);
969 }
970
971 return QJSValuePrivate::fromReturnedValue(d: f->call(data: jsCallData));
972}
973
974/*!
975 * If this QJSManagedValue represents a JavaScript FunctionObject, calls it on
976 * \a instance with the given \a arguments, and returns the result. Otherwise
977 * returns a JavaScript \c undefined value.
978 *
979 * The \a arguments and the \a instance have to be either primitive values or
980 * belong to the same QJSEngine as this QJSManagedValue. Otherwise the call is
981 * not carried out and a JavaScript \c undefined value is returned.
982 */
983QJSValue QJSManagedValue::callWithInstance(const QJSValue &instance,
984 const QJSValueList &arguments) const
985{
986 const QV4::FunctionObject *f = functionObjectForCall(d);
987 if (!f)
988 return QJSValue();
989
990 QV4::ExecutionEngine *engine = f->engine();
991
992 if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, instance))) {
993 qWarning(msg: "QJSManagedValue::callWithInstance() failed: "
994 "Instance was created in different engine.");
995 return QJSValue();
996 }
997
998 QV4::Scope scope(engine);
999 QV4::JSCallArguments jsCallData(scope, arguments.size());
1000 *jsCallData.thisObject = QJSValuePrivate::convertToReturnedValue(e: engine, jsval: instance);
1001 int i = 0;
1002 for (const QJSValue &arg : arguments) {
1003 if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) {
1004 qWarning(msg: "QJSManagedValue::callWithInstance() failed: "
1005 "Argument was created in different engine.");
1006 return QJSValue();
1007 }
1008 jsCallData.args[i++] = QJSValuePrivate::convertToReturnedValue(e: engine, jsval: arg);
1009 }
1010
1011 return QJSValuePrivate::fromReturnedValue(d: f->call(data: jsCallData));
1012}
1013
1014/*!
1015 * If this QJSManagedValue represents a JavaScript FunctionObject, calls it as
1016 * constructor with the given \a arguments, and returns the result. Otherwise
1017 * returns a JavaScript \c undefined value.
1018 *
1019 * The \a arguments have to be either primitive values or belong to the same
1020 * QJSEngine as this QJSManagedValue. Otherwise the call is not carried
1021 * out and a JavaScript \c undefined value is returned.
1022 */
1023QJSValue QJSManagedValue::callAsConstructor(const QJSValueList &arguments) const
1024{
1025 const QV4::FunctionObject *f = functionObjectForCall(d);
1026 if (!f)
1027 return QJSValue();
1028
1029 QV4::ExecutionEngine *engine = f->engine();
1030
1031 QV4::Scope scope(engine);
1032 QV4::JSCallArguments jsCallData(scope, arguments.size());
1033 int i = 0;
1034 for (const QJSValue &arg : arguments) {
1035 if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) {
1036 qWarning(msg: "QJSManagedValue::callAsConstructor() failed: "
1037 "Argument was created in different engine.");
1038 return QJSValue();
1039 }
1040 jsCallData.args[i++] = QJSValuePrivate::convertToReturnedValue(e: engine, jsval: arg);
1041 }
1042
1043 return QJSValuePrivate::fromReturnedValue(d: f->callAsConstructor(data: jsCallData));
1044}
1045
1046/*!
1047 * \internal
1048 *
1049 * Retrieves the JavaScript meta type of this value. The JavaScript meta type
1050 * represents the layout of members in an object. Instantiating a meta type is
1051 * faster than re-constructing the same object using a sequence of setProperty()
1052 * calls on a new object.
1053 *
1054 * \sa members(), instantiate()
1055 */
1056QJSManagedValue QJSManagedValue::jsMetaType() const
1057{
1058 if (!d)
1059 return QJSManagedValue();
1060
1061 QJSManagedValue result(v4Engine(d));
1062 if (QV4::Managed *m = d->as<QV4::Managed>())
1063 *result.d = m->internalClass();
1064
1065 return result;
1066}
1067
1068/*!
1069 * \internal
1070 *
1071 * If this value is a JavaScript meta type, retrieves the names of its members
1072 * The ordering of the names corresponds to the ordering of the values to be
1073 * passed to instantiate().
1074 *
1075 * If the value is not a meta type, an empty list is returned.
1076 *
1077 * \sa isMetaType(), metaType(), instantiate()
1078 */
1079QStringList QJSManagedValue::jsMetaMembers() const
1080{
1081 if (!d)
1082 return {};
1083
1084 if (QV4::InternalClass *c = d->as<QV4::InternalClass>()) {
1085 const auto heapClass = c->d();
1086 const int size = heapClass->size;
1087 QStringList result;
1088 result.reserve(asize: size);
1089 for (int i = 0; i < size; ++i)
1090 result.append(t: heapClass->keyAt(index: i));
1091 return result;
1092 }
1093
1094 return {};
1095}
1096
1097/*!
1098 * \internal
1099 *
1100 * If this value is a JavaScript meta type, instantiates it using the
1101 * \a values, and returns the result. Otherwise returns undefined.
1102 *
1103 * The values are expected in the same order as the keys in the return value of
1104 * members(), and that is the order in which properties were added to the object
1105 * this meta type originally belongs to.
1106 *
1107 * \sa members(), metaType(), isMetaType().
1108 */
1109QJSManagedValue QJSManagedValue::jsMetaInstantiate(const QJSValueList &values) const
1110{
1111 if (!d)
1112 return {};
1113
1114 if (QV4::InternalClass *c = d->as<QV4::InternalClass>()) {
1115 QV4::ExecutionEngine *engine = c->engine();
1116 QJSManagedValue result(engine);
1117 *result.d = c->engine()->newObject(internalClass: c->d());
1118 QV4::Object *o = result.d->as<QV4::Object>();
1119
1120 for (uint i = 0, end = qMin(a: qsizetype(c->d()->size), b: values.size()); i < end; ++i) {
1121 const QJSValue &arg = values[i];
1122 if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) {
1123 qWarning(msg: "QJSManagedValue::instantiate() failed: "
1124 "Argument was created in different engine.");
1125 return QJSManagedValue();
1126 }
1127 o->setProperty(index: i, v: QJSValuePrivate::convertToReturnedValue(e: engine, jsval: arg));
1128 }
1129
1130 return result;
1131 }
1132
1133 return {};
1134}
1135
1136QJSManagedValue::QJSManagedValue(QV4::ExecutionEngine *engine) :
1137 d(engine->memoryManager->m_persistentValues->allocate())
1138{
1139}
1140
1141QT_END_NAMESPACE
1142

source code of qtdeclarative/src/qml/jsapi/qjsmanagedvalue.cpp