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

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