From 6f181768a3147bbfa9a33cf2c05453365693f5b9 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 11 Dec 2020 13:35:53 +0100 Subject: Add a QJSManagedValue A QJSManagedValue is a view on a QJSValue which always knows the engine the value belongs to. This allows us to implement the JavaScript semantics of the various QJSValue methods in a much more rigorous way. [ChangeLog][QtQml] The new QJSManagedValue should be used instead of QJSValue for manipulating properties and prototypes of JavaScript values, as well as for calling JavaScript functions. Change-Id: I9d445ffcf68dfa72dba9bae0818e83c80665ad66 Reviewed-by: Fabian Kosmale --- src/qml/.prev_CMakeLists.txt | 1 + src/qml/CMakeLists.txt | 1 + src/qml/jsapi/jsapi.pri | 2 + src/qml/jsapi/qjsengine.cpp | 12 + src/qml/jsapi/qjsengine.h | 38 +- src/qml/jsapi/qjsmanagedvalue.cpp | 1028 +++++++++++++++++++++++++++++++++++++ src/qml/jsapi/qjsmanagedvalue.h | 158 ++++++ src/qml/jsapi/qjsprimitivevalue.h | 1 + src/qml/jsapi/qjsvalue.cpp | 15 + src/qml/jsapi/qjsvalue.h | 4 + src/qml/jsapi/qjsvalue_p.h | 29 +- src/qml/jsruntime/qv4value.cpp | 147 +++--- src/qml/jsruntime/qv4value_p.h | 3 + 13 files changed, 1374 insertions(+), 65 deletions(-) create mode 100644 src/qml/jsapi/qjsmanagedvalue.cpp create mode 100644 src/qml/jsapi/qjsmanagedvalue.h (limited to 'src') diff --git a/src/qml/.prev_CMakeLists.txt b/src/qml/.prev_CMakeLists.txt index 8c0534c846..93f1a9d50b 100644 --- a/src/qml/.prev_CMakeLists.txt +++ b/src/qml/.prev_CMakeLists.txt @@ -114,6 +114,7 @@ qt_internal_add_module(Qml debugger/qqmlprofiler_p.h inlinecomponentutils_p.h jsapi/qjsengine.cpp jsapi/qjsengine.h jsapi/qjsengine_p.h + jsapi/qjsmanagedvalue.cpp jsapi/qjsmanagedvalue.h jsapi/qjsprimitivevalue.cpp jsapi/qjsprimitivevalue.h jsapi/qjsvalue.cpp jsapi/qjsvalue.h jsapi/qjsvalue_p.h jsapi/qjsvalueiterator.cpp jsapi/qjsvalueiterator.h jsapi/qjsvalueiterator_p.h diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt index 41ca9c80f5..6407c90b21 100644 --- a/src/qml/CMakeLists.txt +++ b/src/qml/CMakeLists.txt @@ -115,6 +115,7 @@ qt_internal_add_module(Qml debugger/qqmlprofiler_p.h inlinecomponentutils_p.h jsapi/qjsengine.cpp jsapi/qjsengine.h jsapi/qjsengine_p.h + jsapi/qjsmanagedvalue.cpp jsapi/qjsmanagedvalue.h jsapi/qjsprimitivevalue.cpp jsapi/qjsprimitivevalue.h jsapi/qjsvalue.cpp jsapi/qjsvalue.h jsapi/qjsvalue_p.h jsapi/qjsvalueiterator.cpp jsapi/qjsvalueiterator.h jsapi/qjsvalueiterator_p.h diff --git a/src/qml/jsapi/jsapi.pri b/src/qml/jsapi/jsapi.pri index f96f8c959b..3463a3aafe 100644 --- a/src/qml/jsapi/jsapi.pri +++ b/src/qml/jsapi/jsapi.pri @@ -1,5 +1,6 @@ SOURCES += \ $$PWD/qjsengine.cpp \ + $$PWD/qjsmanagedvalue.cpp \ $$PWD/qjsprimitivevalue.cpp \ $$PWD/qjsvalue.cpp \ $$PWD/qjsvalueiterator.cpp \ @@ -7,6 +8,7 @@ SOURCES += \ HEADERS += \ $$PWD/qjsengine.h \ $$PWD/qjsengine_p.h \ + $$PWD/qjsmanagedvalue.h \ $$PWD/qjsprimitivevalue.h \ $$PWD/qjsvalue.h \ $$PWD/qjsvalue_p.h \ diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index a0e2cb2c3a..f0e3e69fbb 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -728,6 +728,13 @@ QJSValue QJSEngine::globalObject() const return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } +QJSManagedValue QJSEngine::createManaged(QMetaType type, const void *ptr) +{ + QJSManagedValue result(m_v4Engine); + *result.d = m_v4Engine->metaTypeToJS(type.id(), ptr); + return result; +} + /*! * \internal * used by QJSEngine::toScriptValue @@ -739,6 +746,11 @@ QJSValue QJSEngine::create(int type, const void *ptr) return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } +bool QJSEngine::convertManaged(const QJSManagedValue &value, int type, void *ptr) +{ + return QV4::ExecutionEngine::metaTypeFromJS(*value.d, type, ptr); +} + /*! \internal convert \a value to \a type, store the result in \a ptr diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index c480a77599..9b02469b23 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -46,7 +46,7 @@ #include #include #include - +#include #include QT_BEGIN_NAMESPACE @@ -92,12 +92,25 @@ public: { return create(qMetaTypeId(), &value); } + + template + inline QJSManagedValue toManagedValue(const T &value) + { + return createManaged(QMetaType::fromType(), &value); + } + template inline T fromScriptValue(const QJSValue &value) { return qjsvalue_cast(value); } + template + inline T fromManagedValue(const QJSManagedValue &value) + { + return qjsvalue_cast(value); + } + void collectGarbage(); enum ObjectOwnership { CppOwnership, JavaScriptOwnership }; @@ -131,13 +144,18 @@ Q_SIGNALS: void uiLanguageChanged(); private: + QJSManagedValue createManaged(QMetaType type, const void *ptr); QJSValue create(int type, const void *ptr); + static bool convertManaged(const QJSManagedValue &value, int type, void *ptr); static bool convertV2(const QJSValue &value, int type, void *ptr); template friend inline T qjsvalue_cast(const QJSValue &); + template + friend inline T qjsvalue_cast(const QJSManagedValue &); + protected: QJSEngine(QJSEnginePrivate &dd, QObject *parent = nullptr); @@ -163,12 +181,30 @@ T qjsvalue_cast(const QJSValue &value) return T(); } +template +T qjsvalue_cast(const QJSManagedValue &value) +{ + { + T t; + if (QJSEngine::convertManaged(value, qMetaTypeId(), &t)) + return t; + } + + return qvariant_cast(value.toVariant()); +} + template <> inline QVariant qjsvalue_cast(const QJSValue &value) { return value.toVariant(); } +template <> +inline QVariant qjsvalue_cast(const QJSManagedValue &value) +{ + return value.toVariant(); +} + Q_QML_EXPORT QJSEngine *qjsEngine(const QObject *); QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjsmanagedvalue.cpp b/src/qml/jsapi/qjsmanagedvalue.cpp new file mode 100644 index 0000000000..fee2d9da10 --- /dev/null +++ b/src/qml/jsapi/qjsmanagedvalue.cpp @@ -0,0 +1,1028 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + * \class QJSManagedValue + * \since 6.1 + * + * \brief QJSManagedValue represents a value on the JavaScript heap belonging to a QJSEngine. + * + * The QJSManagedValue class allows interaction with JavaScript values in most + * ways you can interact with them from JavaScript itself. You can get and set + * properties and prototypes, and you can access arrays. Additionally, you can + * transform the value into the Qt counterparts of JavaScript objects. For + * example, a Url object may be transformed into a QUrl. + * + * A QJSManagedValue is always bound to a particular QJSEngine. You cannot use + * it independently. This means that you cannot have a QJSManagedValue from one + * engine be a property or a proptotype of a QJSManagedValue from a different + * engine. + * + * In contrast to QJSValue, almost all values held by QJSManagedValue live on + * the JavaScript heap. There is no inline or unmanaged storage. Therefore, you + * can get the prototype of a primitive value, and you can get the \c length + * property of a string. + * + * Only default-constructed or moved-from QJSManagedValues do not hold a value + * on the JavaScript heap. They represent \c undefined, which doesn't have any + * properties or prototypes. + * + * Also in contrast to QJSValue, QJSManagedValue does not catch any JavaScript + * exceptions. If an operation on a QJSManagedValue causes an error, it will + * generally return an \c undefined value and QJSEngine::hasError() will return + * \c true afterwards. You can then catch the exception using + * QJSEngine::catchError(), or pass it up the stack, at your own discretion. + * + * \note As the reference to the value on the JavaScript heap has to be freed + * on destruction, you cannot move a QJSManagedValue to a different thread. + * The destruction would take place in the new thread, which would create a race + * condition with the garbage collector on the original thread. This also means + * that you cannot hold a QJSManagedValue beyond the lifespan of its engine. + * + * The recommended way of working with a QJSManagedValue is creating it + * on the stack, possibly by moving a QJSValue and adding an engine, then + * performing the necessary operations on it, and finally moving it back into a + * QJSValue for storage. Moving between QJSManagedValue and QJSValue is fast. + */ + +/*! + * \enum QJSManagedValue::Type + * + * This enum represents the JavaScript native types, as specified by + * \l{ECMA-26}. + * + * \value Undefined The \c undefined type + * \value Boolean The \c boolean type + * \value Number The \c number type + * \value String The \c string type + * \value Object The \c object type + * \value Symbol The \c symbol type + * + * Note that the \c null value is not a type of itself but rather a special kind + * of object. You can query a QJSManagedValue for this condition using the + * isNull() method. Furthermore, JavaScript has no integer type, but it knows a + * special treatment of numbers in preparation for integer only operations. You + * can query a QJSManagedValue to find out whether it holds the result of such a + * treatment by using the isInteger() method. + */ + +/*! + * \fn QJSManagedValue::QJSManagedValue() + * + * Creates a QJSManagedValue that represents the JavaScript \c undefined value. + * This is the only value not stored on the JavaScript heap. Calling engine() + * on a default-constructed QJSManagedValue will return nullptr. + */ + +static QV4::ExecutionEngine *v4Engine(QV4::Value *d) +{ + return d ? QV4::PersistentValueStorage::getEngine(d) : nullptr; +} + +/*! + * Creates a QJSManagedValue from \a value, using the heap of \a engine. If + * \a value is itself managed and the engine it belongs to is not \a engine, + * the result is an \c undefined value, and a warning is generated. + */ +QJSManagedValue::QJSManagedValue(QJSValue value, QJSEngine *engine) +{ + QV4::ExecutionEngine *v4 = engine->handle(); + + if (QV4::Value *m = QJSValuePrivate::takeManagedValue(&value)) { + if (Q_UNLIKELY(v4Engine(m) != v4)) { + qWarning("QJSManagedValue(QJSValue, QJSEngine *) failed: " + "Value was created in different engine."); + QV4::PersistentValueStorage::free(m); + return; + } + + d = m; + return; + } + + d = v4->memoryManager->m_persistentValues->allocate(); + + if (const QString *string = QJSValuePrivate::asQString(&value)) + *d = v4->newString(*string); + else + *d = QJSValuePrivate::asReturnedValue(&value); +} + +/*! + * Creates a QJSManagedValue from \a value using the heap of \a engine. + */ +QJSManagedValue::QJSManagedValue(const QJSPrimitiveValue &value, QJSEngine *engine) : + QJSManagedValue(engine->handle()) +{ + switch (value.type()) { + case QJSPrimitiveValue::Undefined: + *d = QV4::Encode::undefined(); + return; + case QJSPrimitiveValue::Null: + *d = QV4::Encode::null(); + return; + case QJSPrimitiveValue::Boolean: + *d = QV4::Encode(value.asBoolean()); + return; + case QJSPrimitiveValue::Integer: + *d = QV4::Encode(value.asInteger()); + return; + case QJSPrimitiveValue::Double: + *d = QV4::Encode(value.asDouble()); + return; + case QJSPrimitiveValue::String: + *d = engine->handle()->newString(value.asString()); + return; + } + + Q_UNREACHABLE(); +} + +/*! + * Creates a QJSManagedValue from \a variant using the heap of \a engine. + */ +QJSManagedValue::QJSManagedValue(const QVariant &variant, QJSEngine *engine) : + QJSManagedValue(engine->handle()) +{ + *d = engine->handle()->fromVariant(variant); +} + +/*! + * Creates a QJSManagedValue from \a string using the heap of \a engine. + */ +QJSManagedValue::QJSManagedValue(const QString &string, QJSEngine *engine) : + QJSManagedValue(engine->handle()) +{ + *d = engine->handle()->newString(string); +} + +/*! + * Destroys the QJSManagedValue. + * + * \note This frees the memory slot it holds on the JavaScript heap. You must + * not destroy a QJSManagedValue from a different thread than the one + * where the QJSEngine it belongs to lives. + */ +QJSManagedValue::~QJSManagedValue() +{ + QV4::PersistentValueStorage::free(d); +} + +/*! + * Move-constructs a QJSManagedValue from \a other. This leaves \a other in + * the default-constructed state where it represents undefined and does not + * belong to any engine. + */ +QJSManagedValue::QJSManagedValue(QJSManagedValue &&other) +{ + qSwap(d, other.d); +} + +/*! + * Move-assigns a QJSManagedValue from \a other. This leaves \a other in + * the default-constructed state where it represents undefined and does not + * belong to any engine. + * + * \note This frees the memory slot this QJSManagedValue holds on the + * JavaScript heap. You must not move-assign a QJSManagedValue on a + * different thread than the one where the QJSEngine it belongs to lives. + */ +QJSManagedValue &QJSManagedValue::operator=(QJSManagedValue &&other) +{ + if (this != &other) { + QV4::PersistentValueStorage::free(d); + d = nullptr; + qSwap(d, other.d); + } + return *this; +} + +/*! + * Invokes the JavaScript '==' operator on this QJSManagedValue and \a other, + * and returns the result. + * + * \sa strictlyEquals + */ +bool QJSManagedValue::equals(const QJSManagedValue &other) const +{ + if (!d) + return !other.d || other.d->isNullOrUndefined(); + if (!other.d) + return d->isNullOrUndefined(); + + return QV4::Runtime::CompareEqual::call(*d, *other.d); +} + +/*! + * Invokes the JavaScript '===' operator on this QJSManagedValue and \a other, + * and returns the result. + * + * \sa equals + */ +bool QJSManagedValue::strictlyEquals(const QJSManagedValue &other) const +{ + if (!d) + return !other.d || other.d->isUndefined(); + if (!other.d) + return d->isUndefined(); + + return QV4::RuntimeHelpers::strictEqual(*d, *other.d); +} + +/*! + * Returns the QJSEngine this QJSManagedValue belongs to. Mind that the engine + * is always valid, unless the QJSManagedValue is default-constructed or moved + * from. In the latter case a nullptr is returned. + */ +QJSEngine *QJSManagedValue::engine() const +{ + if (!d) + return nullptr; + if (QV4::ExecutionEngine *v4 = QV4::PersistentValueStorage::getEngine(d)) + return v4->jsEngine(); + return nullptr; +} + +/*! + * Returns the prototype for this QJSManagedValue. This works on any value. You + * can, for example retrieve the JavaScript \c boolean prototype from a \c boolean + * value. + */ +QJSManagedValue QJSManagedValue::prototype() const +{ + if (!d) + return QJSManagedValue(); + + QV4::ExecutionEngine *v4 = v4Engine(d); + QJSManagedValue result(v4); + + if (auto object = d->as()) + *result.d = object->getPrototypeOf(); + else if (auto managed = d->as()) + *result.d = managed->internalClass()->prototype; + else if (d->isBoolean()) + *result.d = v4->booleanPrototype(); + else if (d->isNumber()) + *result.d = v4->numberPrototype(); + + // If the prototype appears to be undefined, then it's actually null in JS terms. + if (result.d->isUndefined()) + *result.d = QV4::Encode::null(); + + return result; +} + +/*! + * Sets the prototype of this QJSManagedValue to \a prototype. A precondition + * is that \a prototype belongs to the same QJSEngine as this QJSManagedValue + * and is an object (including null). Furthermore, this QJSManagedValue has to + * be an object (excluding null), too, and you cannot create prototype cycles. + */ +void QJSManagedValue::setPrototype(const QJSManagedValue &prototype) +{ + auto object = d ? d->as() : nullptr; + if (!object) { + qWarning("QJSManagedValue::setPrototype() failed: " + "Can only set a prototype on an object (excluding null)."); + return; + } + + // Object includes null ... + if (prototype.type() != QJSManagedValue::Object) { + qWarning("QJSManagedValue::setPrototype() failed: " + "Can only set objects (including null) as prototypes."); + return; + } + + if (Q_UNLIKELY(object->engine() != v4Engine(prototype.d))) { + qWarning("QJSManagedValue::setPrototype() failed: " + "Prototype was created in differen engine."); + return; + } + + // ... Null becomes nullptr here. That is why it appears as undefined later. + if (!object->setPrototypeOf(prototype.d->as())) { + qWarning("QJSManagedValue::setPrototype() failed: " + "Prototype cycle detected."); + } +} + +/*! + * Returns the JavaScript type of this QJSManagedValue. + */ +QJSManagedValue::Type QJSManagedValue::type() const +{ + if (!d || d->isUndefined()) + return Undefined; + if (d->isBoolean()) + return Boolean; + if (d->isNumber()) + return Number; + if (d->isString()) + return String; + if (d->isSymbol()) + return Symbol; + return Object; +} + +/*! + * \fn QJSManagedValue::isUndefined() const + * + * Returns \c true if the type of this QJSManagedValue is \c undefined, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isBoolean() const + * + * Returns \c true if the type of this QJSManagedValue is \c boolean, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isNumber() const + * + * Returns \c true if the type of this QJSManagedValue is \c number, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isString() const + * + * Returns \c true if the type of this QJSManagedValue is \c string, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isSymbol() const + * + * Returns \c true if the type of this QJSManagedValue is \c symbol, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isObject() const + * + * Returns \c true if the type of this QJSManagedValue is \c object, + * or \c false otherwise. + */ + +/*! + * Returns \c true if this QJSManagedValue holds the JavaScript \c null value, + * or \c false otherwise. + */ +bool QJSManagedValue::isNull() const +{ + return d && d->isNull(); +} + +/*! + * Returns \c true if this QJSManagedValue holds an integer value, or \c false + * otherwise. The storage format of a number does not affect the result of any + * operations performed on it, but if an integer is stored, many operations are + * faster. + */ +bool QJSManagedValue::isInteger() const +{ + return d && d->isInteger(); +} + +/*! + * Returns \c true if this value represents a JavaScript regular expression + * object, or \c false otherwise. + */ +bool QJSManagedValue::isRegularExpression() const +{ + return d && d->as(); +} + +/*! + * Returns \c true if this value represents a JavaScript Array + * object, or \c false otherwise. + */ +bool QJSManagedValue::isArray() const +{ + return d && d->as(); +} + +/*! + * Returns \c true if this value represents a JavaScript Url + * object, or \c false otherwise. + */ +bool QJSManagedValue::isUrl() const +{ + return d && d->as(); +} + +/*! + * Returns \c true if this value represents a QVariant managed on the JavaScript + * heap, or \c false otherwise. + */ +bool QJSManagedValue::isVariant() const +{ + return d && d->as(); +} + +/*! + * Returns \c true if this value represents a QObject pointer managed on the + * JavaScript heap, or \c false otherwise. + */ +bool QJSManagedValue::isQObject() const +{ + return d && d->as(); +} + +/*! + * Returns \c true if this value represents a QMetaObject pointer managed on the + * JavaScript heap, or \c false otherwise. + */ +bool QJSManagedValue::isQMetaObject() const +{ + return d && d->as(); +} + +/*! + * Returns \c true if this value represents a JavaScript Date object, or + * \c false otherwise. + */ +bool QJSManagedValue::isDate() const +{ + return d && d->as(); +} + +/*! + * Returns \c true if this value represents a JavaScript Error object, or + * \c false otherwise. + */ +bool QJSManagedValue::isError() const +{ + return d && d->as(); +} + +/*! + * Converts the manged value to a string. If the managed value holds a string, + * that one is returned. Otherwise a string coercion by JavaScript rules is + * performed. + * + * \note Conversion of a managed value to a string can throw an exception. In + * particular, symbols cannot be coerced into strings, or a custom + * toString() method may throw. In this case the result is an empty + * string and the engine carries an error after the conversion. + */ +QString QJSManagedValue::toString() const +{ + return d ? d->toQString() : QStringLiteral("undefined"); +} + +/*! + * Converts the manged value to a number. If the managed value holds a number, + * that one is returned. Otherwise a number coercion by JavaScript rules is + * performed. + * + * \note Conversion of a managed value to a number can throw an exception. In + * particular, symbols cannot be coerced into numbers, or a custom + * valueOf() method may throw. In this case the result is 0 and the + * engine carries an error after the conversion. + */ +double QJSManagedValue::toNumber() const +{ + return d ? d->toNumber() : 0; +} + +/*! + * Converts the manged value to a boolean. If the managed value holds a boolean, + * that one is returned. Otherwise a boolean coercion by JavaScript rules is + * performed. + */ +bool QJSManagedValue::toBoolean() const +{ + return d ? d->toBoolean() : false; +} + +/*! + * Converts the manged value to an integer. This first converts the value to a + * number by the rules of toNumber(), and then clamps it into the integer range + * by the rules given for coercing the arguments to JavaScript bit shift + * operators into 32bit integers. + * + * Internally, the value may already be stored as an integer, in which case a + * fast path is taken. + * + * \note Conversion of a managed value to a number can throw an exception. In + * particular, symbols cannot be coerced into numbers, or a custom + * valueOf() method may throw. In this case the result is 0 and the + * engine carries an error after the conversion. + * + * \note The JavaScript rules for coercing numbers into 32bit integers are + * unintuitive. + */ +int QJSManagedValue::toInteger() const +{ + return d ? d->toInt32() : 0; +} + +/*! + * Converts the manged value to a QJSPrimitiveValue. If the managed value holds + * a type supported by QJSPrimitiveValue, the value is copied. Otherwise the + * value is converted to a string, and the string is stored in + * QJSPrimitiveValue. + * + * \note Conversion of a managed value to a string can throw an exception. In + * particular, symbols cannot be coerced into strings, or a custom + * toString() method may throw. In this case the result is the undefined + * value and the engine carries an error after the conversion. + */ +QJSPrimitiveValue QJSManagedValue::toPrimitive() const +{ + if (!d || d->isUndefined()) + return QJSPrimitiveUndefined(); + if (d->isNull()) + return QJSPrimitiveNull(); + if (d->isBoolean()) + return d->booleanValue(); + if (d->isInteger()) + return d->integerValue(); + if (d->isNumber()) + return d->doubleValue(); + + bool ok; + const QString result = d->toQString(&ok); + return ok ? QJSPrimitiveValue(result) : QJSPrimitiveValue(QJSPrimitiveUndefined()); +} + +/*! + * Copies this QJSManagedValue into a new QJSValue. This is less efficient than + * move-constructing a QJSValue from a QJSManagedValue, but retains the + * QJSManagedValue. + */ +QJSValue QJSManagedValue::toJSValue() const +{ + return d ? QJSValuePrivate::fromReturnedValue(d->asReturnedValue()) : QJSValue(); +} + +/*! + * Copies this QJSManagedValue into a new QVariant. This also creates a useful + * QVariant if QJSManagedValue::isVariant() returns false. QVariant can hold all + * types supported by QJSManagedValue. + */ +QVariant QJSManagedValue::toVariant() const +{ + if (!d || d->isUndefined()) + return QVariant(); + if (d->isNull()) + return QVariant(QMetaType::fromType(), nullptr); + if (d->isBoolean()) + return QVariant(d->booleanValue()); + if (d->isInteger()) + return QVariant(d->integerValue()); + if (d->isNumber()) + return QVariant(d->doubleValue()); + if (d->isString()) + return QVariant(d->toQString()); + if (QV4::Managed *m = d->as()) + return m->engine()->toVariant(*d, -1, true); + + Q_UNREACHABLE(); + return QVariant(); +} + +/*! + * If this QJSManagedValue holds a JavaScript regular expression object, returns + * an equivalent QRegularExpression. Otherwise returns an invalid one. + */ +QRegularExpression QJSManagedValue::toRegularExpression() const +{ + if (const auto *r = d ? d->as() : nullptr) + return r->toQRegularExpression(); + return {}; +} + +/*! + * If this QJSManagedValue holds a JavaScript Url object, returns + * an equivalent QUrl. Otherwise returns an invalid one. + */ +QUrl QJSManagedValue::toUrl() const +{ + if (const auto *u = d ? d->as() : nullptr) + return u->toQUrl(); + return {}; +} + +/*! + * If this QJSManagedValue holds a QObject pointer, returns it. Otherwise + * returns nullptr. + */ +QObject *QJSManagedValue::toQObject() const +{ + if (const auto *o = d ? d->as() : nullptr) + return o->object(); + return {}; +} + +/*! + * If this QJSManagedValue holds a QMetaObject pointer, returns it. + * Otherwise returns nullptr. + */ +const QMetaObject *QJSManagedValue::toQMetaObject() const +{ + if (const auto *m = d ? d->as() : nullptr) + return m->metaObject(); + return {}; +} + +/*! + * If this QJSManagedValue holds a JavaScript Date object, returns an equivalent + * QDateTime. Otherwise returns an invalid one. + */ +QDateTime QJSManagedValue::toDateTime() const +{ + if (const auto *t = d ? d->as() : nullptr) + return t->toQDateTime(); + return {}; +} + +/*! + * Returns \c true if this QJSManagedValue has a property \a name, otherwise + * returns \c false. The properties of the prototype chain are considered. + */ +bool QJSManagedValue::hasProperty(const QString &name) const +{ + if (!d || d->isNullOrUndefined()) + return false; + + if (d->isString() && name == QStringLiteral("length")) + return true; + + if (QV4::Object *obj = d->as()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + return obj->hasProperty(str->toPropertyKey()); + } + + return prototype().hasProperty(name); +} + +/*! + * Returns \c true if this QJSManagedValue has a property \a name, otherwise + * returns \c false. The properties of the prototype chain are not considered. + */ +bool QJSManagedValue::hasOwnProperty(const QString &name) const +{ + if (!d || d->isNullOrUndefined()) + return false; + + if (d->isString() && name == QStringLiteral("length")) + return true; + + if (QV4::Object *obj = d->as()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + return obj->getOwnProperty(str->toPropertyKey()) != QV4::Attr_Invalid; + } + + return false; +} + +/*! + * Returns the property \a name of this QJSManagedValue. The prototype chain + * is searched if the property is not found on the actual object. + */ +QJSValue QJSManagedValue::property(const QString &name) const +{ + if (!d || d->isNullOrUndefined()) + return QJSValue(); + + if (QV4::String *string = d->as()) { + if (name == QStringLiteral("length")) + return QJSValue(string->d()->length()); + } + + if (QV4::Object *obj = d->as()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + return QJSValuePrivate::fromReturnedValue(obj->get(str->toPropertyKey())); + } + + return prototype().property(name); +} + +/*! + * Sets the property \a name to \a value on this QJSManagedValue. This can only + * be done on JavaScript values of type \c object. Furhermore, \a value has to be + * either a primitive or belong to the same engine as this value. + */ +void QJSManagedValue::setProperty(const QString &name, const QJSValue &value) +{ + if (!d) + return; + + if (QV4::Object *obj = d->as()) { + QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(&value); + if (Q_UNLIKELY(v4 && v4 != obj->engine())) { + qWarning("QJSManagedValue::setProperty() failed: " + "Value was created in different engine."); + return; + } + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + obj->put(str->toPropertyKey(), QJSValuePrivate::convertToReturnedValue(v4, value)); + } +} + +/*! + * Deletes the property \a name from this QJSManagedValue. Returns \c true if + * the deletion succeeded, or \a false otherwise. + */ +bool QJSManagedValue::deleteProperty(const QString &name) +{ + if (!d) + return false; + + if (QV4::Object *obj = d->as()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + return obj->deleteProperty(str->toPropertyKey()); + } + + return false; +} + +/*! + * Returns \c true if this QJSManagedValue has an array index \a arrayIndex, + * otherwise returns \c false. The properties of the prototype chain are + * considered. + */ +bool QJSManagedValue::hasProperty(quint32 arrayIndex) const +{ + if (!d || d->isNullOrUndefined()) + return false; + + if (QV4::Object *obj = d->as()) { + bool hasProperty = false; + if (arrayIndex == std::numeric_limits::max()) + obj->get(obj->engine()->id_uintMax(), &hasProperty); + else + obj->get(arrayIndex, &hasProperty); + return hasProperty; + } + + return prototype().hasProperty(arrayIndex); +} + +/*! + * Returns \c true if this QJSManagedValue has an array index \a arrayIndex, + * otherwise returns \c false. The properties of the prototype chain are not + * considered. + */ +bool QJSManagedValue::hasOwnProperty(quint32 arrayIndex) const +{ + if (!d || d->isNullOrUndefined()) + return false; + + if (QV4::Object *obj = d->as()) { + if (arrayIndex == std::numeric_limits::max()) { + return obj->getOwnProperty(obj->engine()->id_uintMax()->toPropertyKey()) + != QV4::Attr_Invalid; + } else { + return obj->getOwnProperty(QV4::PropertyKey::fromArrayIndex(arrayIndex)) + != QV4::Attr_Invalid; + } + } + + return false; +} + +/*! + * Returns the property stored at \a arrayIndex of this QJSManagedValue. The + * prototype chain is searched if the property is not found on the actual + * object. + */ +QJSValue QJSManagedValue::property(quint32 arrayIndex) const +{ + if (!d || d->isNullOrUndefined()) + return QJSValue(); + + if (QV4::Object *obj = d->as()) { + if (arrayIndex == std::numeric_limits::max()) + return QJSValuePrivate::fromReturnedValue(obj->get(obj->engine()->id_uintMax())); + else + return QJSValuePrivate::fromReturnedValue(obj->get(arrayIndex)); + } + + return prototype().property(arrayIndex); +} + +/*! + * Stores the \a value at \a arrayIndex in this QJSManagedValue. This can only + * be done on JavaScript values of type \c object, and it's not recommended if the + * value is not an array. Furhermore, \a value has to be either a primitive or + * belong to the same engine as this value. + */ +void QJSManagedValue::setProperty(quint32 arrayIndex, const QJSValue &value) +{ + if (!d) + return; + + if (QV4::Object *obj = d->as()) { + QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(&value); + if (Q_UNLIKELY(v4 && v4 != obj->engine())) { + qWarning("QJSManagedValue::setProperty() failed: " + "Value was created in different engine."); + return; + } + obj->put(arrayIndex, QJSValuePrivate::convertToReturnedValue(v4, value)); + } +} + +/*! + * Deletes the value stored at \a arrayIndex from this QJSManagedValue. Returns + * \c true if the deletion succeeded, or \a false otherwise. + */ +bool QJSManagedValue::deleteProperty(quint32 arrayIndex) +{ + if (!d) + return false; + + if (QV4::Object *obj = d->as()) + return obj->deleteProperty(QV4::PropertyKey::fromArrayIndex(arrayIndex)); + + return false; +} + +/*! + * Returns \c true if this QJSManagedValue is a JavaScript FunctionObject, or + * \c false otherwise. + */ +bool QJSManagedValue::isCallable() const +{ + return d && d->isFunctionObject(); +} + +/*! + * If this QJSManagedValue represents a JavaScript FunctionObject, calls it with + * the given \a arguments, and returns the result. Otherwise returns a + * JavaScript \c undefined value. + * + * The \a arguments have to be either primitive values or belong to the same + * QJSEngine as this QJSManagedValue. Otherwise the call is not carried + * out and a JavaScript \c undefined value is returned. + */ +QJSValue QJSManagedValue::call(const QJSValueList &arguments) const +{ + const QV4::FunctionObject *f = d ? d->as() : nullptr; + if (!f) + return QJSValue(); + + QV4::ExecutionEngine *engine = f->engine(); + + QV4::Scope scope(engine); + QV4::JSCallData jsCallData(scope, arguments.length()); + *jsCallData->thisObject = engine->globalObject; + int i = 0; + for (const QJSValue &arg : arguments) { + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { + qWarning("QJSManagedValue::call() failed: Argument was created in different engine."); + return QJSValue(); + } + jsCallData->args[i++] = QJSValuePrivate::convertToReturnedValue(engine, arg); + } + + return QJSValuePrivate::fromReturnedValue(f->call(jsCallData)); +} + +/*! + * If this QJSManagedValue represents a JavaScript FunctionObject, calls it on + * \a instance with the given \a arguments, and returns the result. Otherwise + * returns a JavaScript \c undefined value. + * + * The \a arguments and the \a instance have to be either primitive values or + * belong to the same QJSEngine as this QJSManagedValue. Otherwise the call is + * not carried out and a JavaScript \c undefined value is returned. + */ +QJSValue QJSManagedValue::callWithInstance(const QJSValue &instance, + const QJSValueList &arguments) const +{ + const QV4::FunctionObject *f = d ? d->as() : nullptr; + if (!f) + return QJSValue(); + + QV4::ExecutionEngine *engine = f->engine(); + + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, instance))) { + qWarning("QJSManagedValue::callWithInstance() failed: " + "Instance was created in different engine."); + return QJSValue(); + } + + QV4::Scope scope(engine); + QV4::JSCallData jsCallData(scope, arguments.length()); + *jsCallData->thisObject = QJSValuePrivate::convertToReturnedValue(engine, instance); + int i = 0; + for (const QJSValue &arg : arguments) { + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { + qWarning("QJSManagedValue::callWithInstance() failed: " + "Argument was created in different engine."); + return QJSValue(); + } + jsCallData->args[i++] = QJSValuePrivate::convertToReturnedValue(engine, arg); + } + + return QJSValuePrivate::fromReturnedValue(f->call(jsCallData)); +} + +/*! + * If this QJSManagedValue represents a JavaScript FunctionObject, calls it as + * constructor with the given \a arguments, and returns the result. Otherwise + * returns a JavaScript \c undefined value. + * + * The \a arguments have to be either primitive values or belong to the same + * QJSEngine as this QJSManagedValue. Otherwise the call is not carried + * out and a JavaScript \c undefined value is returned. + */ +QJSValue QJSManagedValue::callAsConstructor(const QJSValueList &arguments) const +{ + const QV4::FunctionObject *f = d ? d->as() : nullptr; + if (!f) + return QJSValue(); + + QV4::ExecutionEngine *engine = f->engine(); + + QV4::Scope scope(engine); + QV4::JSCallData jsCallData(scope, arguments.length()); + int i = 0; + for (const QJSValue &arg : arguments) { + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { + qWarning("QJSManagedValue::callAsConstructor() failed: " + "Argument was created in different engine."); + return QJSValue(); + } + jsCallData->args[i++] = QJSValuePrivate::convertToReturnedValue(engine, arg); + } + + return QJSValuePrivate::fromReturnedValue(f->callAsConstructor(jsCallData)); +} + +QJSManagedValue::QJSManagedValue(QV4::ExecutionEngine *engine) : + d(engine ? engine->memoryManager->m_persistentValues->allocate() : nullptr) +{ +} + +QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjsmanagedvalue.h b/src/qml/jsapi/qjsmanagedvalue.h new file mode 100644 index 0000000000..67a3f3758a --- /dev/null +++ b/src/qml/jsapi/qjsmanagedvalue.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QJSMANAGEDVALUE_H +#define QJSMANAGEDVALUE_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QV4 { +struct Value; +struct ExecutionEngine; +} + +class QJSEngine; +class Q_QML_EXPORT QJSManagedValue +{ + Q_DISABLE_COPY(QJSManagedValue) +public: + enum Type { + Undefined, + Boolean, + Number, + String, + Object, + Symbol + }; + + QJSManagedValue() = default; + QJSManagedValue(QJSValue value, QJSEngine *engine); + QJSManagedValue(const QJSPrimitiveValue &value, QJSEngine *engine); + QJSManagedValue(const QVariant &variant, QJSEngine *engine); + QJSManagedValue(const QString &string, QJSEngine *engine); + + ~QJSManagedValue(); + QJSManagedValue(QJSManagedValue &&other); + QJSManagedValue &operator=(QJSManagedValue &&other); + + bool equals(const QJSManagedValue &other) const; + bool strictlyEquals(const QJSManagedValue &other) const; + + QJSEngine *engine() const; + + QJSManagedValue prototype() const; + void setPrototype(const QJSManagedValue &prototype); + + Type type() const; + + // Compatibility with QJSValue + bool isUndefined() const { return type() == Undefined; } + bool isBoolean() const { return type() == Boolean; } + bool isNumber() const { return type() == Number; } + bool isString() const { return type() == String; } + bool isObject() const { return type() == Object; } + bool isSymbol() const { return type() == Symbol; } + + // Special case of Number + bool isInteger() const; + + // Selected special cases of Object + bool isNull() const; + bool isRegularExpression() const; + bool isArray() const; + bool isUrl() const; + bool isVariant() const; + bool isQObject() const; + bool isQMetaObject() const; + bool isDate() const; + bool isError() const; + bool isCallable() const; + + // Native type transformations + QString toString() const; + double toNumber() const; + bool toBoolean() const; + + // Variant-like type transformations + QJSPrimitiveValue toPrimitive() const; + QJSValue toJSValue() const; + QVariant toVariant() const; + + // Special cases + int toInteger() const; + QRegularExpression toRegularExpression() const; + QUrl toUrl() const; + QObject *toQObject() const; + const QMetaObject *toQMetaObject() const; + QDateTime toDateTime() const; + + // Properties of objects + bool hasProperty(const QString &name) const; + bool hasOwnProperty(const QString &name) const; + QJSValue property(const QString &name) const; + void setProperty(const QString &name, const QJSValue &value); + bool deleteProperty(const QString &name); + + // Array indexing + bool hasProperty(quint32 arrayIndex) const; + bool hasOwnProperty(quint32 arrayIndex) const; + QJSValue property(quint32 arrayIndex) const; + void setProperty(quint32 arrayIndex, const QJSValue &value); + bool deleteProperty(quint32 arrayIndex); + + // Calling functions + QJSValue call(const QJSValueList &arguments = {}) const; + QJSValue callWithInstance(const QJSValue &instance, const QJSValueList &arguments = {}) const; + QJSValue callAsConstructor(const QJSValueList &arguments = {}) const; + +private: + friend class QJSValue; + friend class QJSEngine; + + QJSManagedValue(QV4::ExecutionEngine *engine); + QV4::Value *d = nullptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/jsapi/qjsprimitivevalue.h b/src/qml/jsapi/qjsprimitivevalue.h index 1a12104a61..27f38aec93 100644 --- a/src/qml/jsapi/qjsprimitivevalue.h +++ b/src/qml/jsapi/qjsprimitivevalue.h @@ -439,6 +439,7 @@ public: } private: + friend class QJSManagedValue; friend class QJSValue; constexpr bool asBoolean() const { return std::get(d); } diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index ce3ab0076b..5525aee595 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -43,6 +43,7 @@ #include "qjsengine.h" #include "qjsvalue.h" #include "qjsprimitivevalue.h" +#include "qjsmanagedvalue.h" #include "qjsvalue_p.h" #include "qv4value_p.h" #include "qv4object_p.h" @@ -884,6 +885,20 @@ QJSValue::QJSValue(QJSPrimitiveValue &&value) Q_UNREACHABLE(); } +QJSValue::QJSValue(QJSManagedValue &&value) +{ + if (!value.d) { + d = QV4::Encode::undefined(); + } else if (value.d->isManaged()) { + QJSValuePrivate::setRawValue(this, value.d); + value.d = nullptr; + } else { + d = value.d->asReturnedValue(); + QV4::PersistentValueStorage::free(value.d); + value.d = nullptr; + } +} + static bool js_equal(const QString &string, const QV4::Value &value) { if (String *s = value.stringValue()) diff --git a/src/qml/jsapi/qjsvalue.h b/src/qml/jsapi/qjsvalue.h index 393f1c960e..3f8a16a149 100644 --- a/src/qml/jsapi/qjsvalue.h +++ b/src/qml/jsapi/qjsvalue.h @@ -61,6 +61,9 @@ namespace QV4 { struct Value; } +class QJSPrimitiveValue; +class QJSManagedValue; + class Q_QML_EXPORT QJSValue { public: @@ -109,6 +112,7 @@ public: QJSValue &operator=(const QJSValue &other); explicit QJSValue(QJSPrimitiveValue &&value); + explicit QJSValue(QJSManagedValue &&value); bool isBool() const; bool isNumber() const; diff --git a/src/qml/jsapi/qjsvalue_p.h b/src/qml/jsapi/qjsvalue_p.h index 9e371118cc..cea48fd9e2 100644 --- a/src/qml/jsapi/qjsvalue_p.h +++ b/src/qml/jsapi/qjsvalue_p.h @@ -83,6 +83,12 @@ class Q_AUTOTEST_EXPORT QJSValuePrivate return (m & IsString) ? nullptr : reinterpret_cast(m); } + static QV4::Value *managedValue(QV4::Value &v) + { + quintptr m = quintptr(v.m()); + return (m & IsString) ? nullptr : reinterpret_cast(m); + } + static const QString *qstring(const QV4::Value &v) { const quintptr m = quintptr(v.m()); @@ -108,14 +114,13 @@ class Q_AUTOTEST_EXPORT QJSValuePrivate return QV4::Value::fromHeapObject(reinterpret_cast(m)).asReturnedValue(); } -protected: - // Only for the test. You're not supposed to subclass QJSValuePrivate otherwise. +public: + static void setRawValue(QJSValue *jsval, QV4::Value *m) { jsval->d = encodeRawValue(quintptr(m)); } -public: static QJSValue fromReturnedValue(QV4::ReturnedValue d) { QJSValue result; @@ -134,6 +139,19 @@ public: return nullptr; } + // This is a move operation and transfers ownership. + static QV4::Value *takeManagedValue(QJSValue *jsval) + { + QV4::Value v = QV4::Value::fromReturnedValue(jsval->d); + if (!v.isManaged()) + return nullptr; + if (QV4::Value *value = managedValue(v)) { + setValue(jsval, QV4::Encode::undefined()); + return value; + } + return nullptr; + } + static QV4::ReturnedValue asPrimitiveType(const QJSValue *jsval) { const QV4::Value v = QV4::Value::fromReturnedValue(jsval->d); @@ -170,6 +188,11 @@ public: jsval->d = v.isManaged() ? encode(v) : v.asReturnedValue(); } + static void adoptValue(QJSValue *jsval, QV4::Value *v) + { + jsval->d = v->isManaged() ? encodeRawValue(quintptr(v)) : v->asReturnedValue(); + } + // Moves any QString onto the V4 heap, changing the value to reflect that. static void manageStringOnV4Heap(QV4::ExecutionEngine *e, QJSValue *jsval) { diff --git a/src/qml/jsruntime/qv4value.cpp b/src/qml/jsruntime/qv4value.cpp index 3449603a8a..6913a49f9c 100644 --- a/src/qml/jsruntime/qv4value.cpp +++ b/src/qml/jsruntime/qv4value.cpp @@ -122,100 +122,125 @@ double Value::toNumberImpl(Value val) } } -QString Value::toQStringNoThrow() const +static QString primitiveToQString(const Value *value) { - switch (type()) { + switch (value->type()) { case Value::Empty_Type: Q_ASSERT(!"empty Value encountered"); Q_UNREACHABLE(); + return QString(); case Value::Undefined_Type: return QStringLiteral("undefined"); case Value::Null_Type: return QStringLiteral("null"); case Value::Boolean_Type: - if (booleanValue()) + if (value->booleanValue()) return QStringLiteral("true"); else return QStringLiteral("false"); case Value::Managed_Type: + Q_UNREACHABLE(); + return QString(); + case Value::Integer_Type: { + QString str; + RuntimeHelpers::numberToString(&str, (double)value->int_32(), 10); + return str; + } + case Value::Double_Type: { + QString str; + RuntimeHelpers::numberToString(&str, value->doubleValue(), 10); + return str; + } + } // switch + + Q_UNREACHABLE(); + return QString(); +} + + +QString Value::toQStringNoThrow() const +{ + if (isManaged()) { if (String *s = stringValue()) return s->toQString(); if (Symbol *s = symbolValue()) return s->descriptiveString(); - { - Q_ASSERT(isObject()); - Scope scope(objectValue()->engine()); - ScopedValue ex(scope); - bool caughtException = false; - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + + Q_ASSERT(isObject()); + Scope scope(objectValue()->engine()); + ScopedValue ex(scope); + bool caughtException = false; + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + if (scope.hasException()) { + ex = scope.engine->catchException(); + caughtException = true; + } else if (prim->isPrimitive()) { + return prim->toQStringNoThrow(); + } + + // Can't nest try/catch due to CXX ABI limitations for foreign exception nesting. + if (caughtException) { + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(ex, STRING_HINT)); if (scope.hasException()) { ex = scope.engine->catchException(); - caughtException = true; } else if (prim->isPrimitive()) { - return prim->toQStringNoThrow(); - } - // Can't nest try/catch due to CXX ABI limitations for foreign exception nesting. - if (caughtException) { - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(ex, STRING_HINT)); - if (scope.hasException()) { - ex = scope.engine->catchException(); - } else if (prim->isPrimitive()) { - return prim->toQStringNoThrow(); - } + return prim->toQStringNoThrow(); } - return QString(); } - case Value::Integer_Type: { - QString str; - RuntimeHelpers::numberToString(&str, (double)int_32(), 10); - return str; - } - default: { // double - QString str; - RuntimeHelpers::numberToString(&str, doubleValue(), 10); - return str; + + return QString(); } - } // switch + + return primitiveToQString(this); } QString Value::toQString() const { - switch (type()) { - case Value::Empty_Type: - Q_ASSERT(!"empty Value encountered"); - Q_UNREACHABLE(); - case Value::Undefined_Type: - return QStringLiteral("undefined"); - case Value::Null_Type: - return QStringLiteral("null"); - case Value::Boolean_Type: - if (booleanValue()) - return QStringLiteral("true"); - else - return QStringLiteral("false"); - case Value::Managed_Type: - if (String *s = stringValue()) { + if (isManaged()) { + if (String *s = stringValue()) return s->toQString(); - } else if (isSymbol()) { + + if (isSymbol()) { static_cast(this)->engine()->throwTypeError(); return QString(); - } else { - Q_ASSERT(isObject()); - Scope scope(objectValue()->engine()); - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); - return prim->toQString(); } - case Value::Integer_Type: { - QString str; - RuntimeHelpers::numberToString(&str, (double)int_32(), 10); - return str; + + Q_ASSERT(isObject()); + Scope scope(objectValue()->engine()); + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + return prim->toQString(); } - default: { // double - QString str; - RuntimeHelpers::numberToString(&str, doubleValue(), 10); - return str; + + return primitiveToQString(this); +} + +QString Value::toQString(bool *ok) const +{ + if (isManaged()) { + if (String *s = stringValue()) { + *ok = true; + return s->toQString(); + } + + if (isSymbol()) { + static_cast(this)->engine()->throwTypeError(); + *ok = false; + return QString(); + } + + Q_ASSERT(isObject()); + Scope scope(objectValue()->engine()); + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + + if (scope.hasException()) { + *ok = false; + return QString(); + } + + return prim->toQString(ok); } - } // switch + + return primitiveToQString(this); } QV4::PropertyKey Value::toPropertyKey(ExecutionEngine *e) const diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index 44383689b4..6941c11f44 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -190,8 +190,11 @@ struct Q_QML_PRIVATE_EXPORT Value : public StaticValue inline double toNumber() const; static double toNumberImpl(Value v); double toNumberImpl() const { return toNumberImpl(*this); } + QString toQStringNoThrow() const; QString toQString() const; + QString toQString(bool *ok) const; + Heap::String *toString(ExecutionEngine *e) const { if (isString()) return reinterpret_cast(m()); -- cgit v1.2.3