diff options
Diffstat (limited to 'src/qml/jsapi')
-rw-r--r-- | src/qml/jsapi/jsapi.pri | 12 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.cpp | 765 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.h | 346 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine_p.h | 151 | ||||
-rw-r--r-- | src/qml/jsapi/qjslist.cpp | 18 | ||||
-rw-r--r-- | src/qml/jsapi/qjslist.h | 368 | ||||
-rw-r--r-- | src/qml/jsapi/qjsmanagedvalue.cpp | 1145 | ||||
-rw-r--r-- | src/qml/jsapi/qjsmanagedvalue.h | 130 | ||||
-rw-r--r-- | src/qml/jsapi/qjsprimitivevalue.cpp | 340 | ||||
-rw-r--r-- | src/qml/jsapi/qjsprimitivevalue.h | 966 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalue.cpp | 786 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalue.h | 84 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalue_p.h | 425 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalueiterator.cpp | 46 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalueiterator.h | 40 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalueiterator_p.h | 40 |
16 files changed, 4532 insertions, 1130 deletions
diff --git a/src/qml/jsapi/jsapi.pri b/src/qml/jsapi/jsapi.pri deleted file mode 100644 index f70588ec7b..0000000000 --- a/src/qml/jsapi/jsapi.pri +++ /dev/null @@ -1,12 +0,0 @@ -SOURCES += \ - $$PWD/qjsengine.cpp \ - $$PWD/qjsvalue.cpp \ - $$PWD/qjsvalueiterator.cpp \ - -HEADERS += \ - $$PWD/qjsengine.h \ - $$PWD/qjsengine_p.h \ - $$PWD/qjsvalue.h \ - $$PWD/qjsvalue_p.h \ - $$PWD/qjsvalueiterator.h \ - $$PWD/qjsvalueiterator_p.h diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index 065fbc1c0a..8346ef5d84 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qjsengine.h" #include "qjsengine_p.h" @@ -48,11 +12,14 @@ #include "private/qv4globalobject_p.h" #include "private/qv4script_p.h" #include "private/qv4runtime_p.h" +#include <private/qv4dateobject_p.h> #include <private/qqmlbuiltinfunctions_p.h> #include <private/qqmldebugconnector_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qv4qmetaobjectwrapper_p.h> #include <private/qv4stackframe_p.h> #include <private/qv4module_p.h> +#include <private/qv4symbol_p.h> #include <QtCore/qdatetime.h> #include <QtCore/qmetaobject.h> @@ -71,11 +38,6 @@ #include <private/qqmlglobal_p.h> #include <qqmlengine.h> -#undef Q_D -#undef Q_Q -#define Q_D(blah) -#define Q_Q(blah) - Q_DECLARE_METATYPE(QList<int>) /*! @@ -154,6 +116,46 @@ Q_DECLARE_METATYPE(QList<int>) } \endcode + Modules don't have to be files. They can be values registered with + QJSEngine::registerModule(): + + \code + import version from "version"; + + export function getVersion() + { + return version; + } + \endcode + + \code + QJSValue version(610); + myEngine.registerModule("version", version); + QJSValue module = myEngine.importModule("./myprint.mjs"); + QJSValue getVersion = module.property("getVersion"); + QJSValue result = getVersion.call(); + \endcode + + Named exports are supported, but because they are treated as members of an + object, the default export must be an ECMAScript object. Most of the newXYZ + functions in QJSValue will return an object. + + \code + QJSValue name("Qt6"); + QJSValue obj = myEngine.newObject(); + obj.setProperty("name", name); + myEngine.registerModule("info", obj); + \endcode + + \code + import { name } from "info"; + + export function getName() + { + return name; + } + \endcode + \section1 Engine Configuration The globalObject() function returns the \b {Global Object} @@ -242,7 +244,7 @@ Q_DECLARE_METATYPE(QList<int>) \section1 Extensions QJSEngine provides a compliant ECMAScript implementation. By default, - familiar utilities like logging are not available, but they can can be + familiar utilities like logging are not available, but they can be installed via the \l installExtensions() function. \sa QJSValue, {Making Applications Scriptable}, @@ -257,7 +259,7 @@ Q_DECLARE_METATYPE(QList<int>) \l installExtensions(). \value TranslationExtension Indicates that translation functions (\c qsTr(), - for example) should be installed. + for example) should be installed. This also installs the Qt.uiLanguage property. \value ConsoleExtension Indicates that console functions (\c console.log(), for example) should be installed. @@ -344,11 +346,8 @@ QJSEngine::QJSEngine() */ QJSEngine::QJSEngine(QObject *parent) - : QObject(*new QJSEnginePrivate, parent) - , m_v4Engine(new QV4::ExecutionEngine(this)) + : QJSEngine(*new QJSEnginePrivate, parent) { - checkForApplicationInstance(); - QJSEnginePrivate::addToDebugServer(this); } @@ -366,11 +365,12 @@ QJSEngine::QJSEngine(QJSEnginePrivate &dd, QObject *parent) Destroys this QJSEngine. Garbage is not collected from the persistent JS heap during QJSEngine - destruction. If you need all memory freed, call collectGarbage manually + destruction. If you need all memory freed, call collectGarbage() manually right before destroying the QJSEngine. */ QJSEngine::~QJSEngine() { + m_v4Engine->inShutdown = true; QJSEnginePrivate::removeFromDebugServer(this); delete m_v4Engine; } @@ -390,46 +390,16 @@ QJSEngine::~QJSEngine() when the QJSEngine decides that it's wise to do so (i.e. when a certain number of new objects have been created). However, you can call this function to explicitly request that garbage collection should be performed as soon as possible. -*/ -void QJSEngine::collectGarbage() -{ - m_v4Engine->memoryManager->runGC(); -} - -#if QT_DEPRECATED_SINCE(5, 6) - -/*! - \since 5.4 - \obsolete - - Installs translator functions on the given \a object, or on the Global - Object if no object is specified. - - The relation between script translator functions and C++ translator - functions is described in the following table: - - \table - \header \li Script Function \li Corresponding C++ Function - \row \li qsTr() \li QObject::tr() - \row \li QT_TR_NOOP() \li QT_TR_NOOP() - \row \li qsTranslate() \li QCoreApplication::translate() - \row \li QT_TRANSLATE_NOOP() \li QT_TRANSLATE_NOOP() - \row \li qsTrId() \li qtTrId() - \row \li QT_TRID_NOOP() \li QT_TRID_NOOP() - \endtable - It also adds an arg() method to the string prototype. - \sa {Internationalization with Qt} -*/ -void QJSEngine::installTranslatorFunctions(const QJSValue &object) + \sa {Garbage Collection} + \sa {Qt::}{gc()} + */ +void QJSEngine::collectGarbage() { - installExtensions(TranslationExtension, object); + m_v4Engine->memoryManager->runGC(); } -#endif // QT_DEPRECATED_SINCE(5, 6) - - /*! \since 5.6 @@ -456,10 +426,7 @@ void QJSEngine::installExtensions(QJSEngine::Extensions extensions, const QJSVal } QV4::Scope scope(m_v4Engine); - QV4::ScopedObject obj(scope); - QV4::Value *val = QJSValuePrivate::getValue(&object); - if (val) - obj = val; + QV4::ScopedObject obj(scope, QJSValuePrivate::asReturnedValue(&object)); if (!obj) obj = scope.engine->globalObject; @@ -479,7 +446,7 @@ void QJSEngine::installExtensions(QJSEngine::Extensions extensions, const QJSVal */ void QJSEngine::setInterrupted(bool interrupted) { - m_v4Engine->isInterrupted = interrupted; + m_v4Engine->isInterrupted.storeRelaxed(interrupted); } /*! @@ -490,7 +457,7 @@ void QJSEngine::setInterrupted(bool interrupted) */ bool QJSEngine::isInterrupted() const { - return m_v4Engine->isInterrupted.loadAcquire(); + return m_v4Engine->isInterrupted.loadRelaxed(); } static QUrl urlForFileName(const QString &fileName) @@ -510,6 +477,9 @@ static QUrl urlForFileName(const QString &fileName) The script code will be evaluated in the context of the global object. + \note If you need to evaluate inside a QML context, use \l QQmlExpression + instead. + The evaluation of \a program can cause an \l{Script Exceptions}{exception} in the engine; in this case the return value will be the exception that was thrown (typically an \c{Error} object; see @@ -527,12 +497,23 @@ static QUrl urlForFileName(const QString &fileName) the file name is accessible through the "fileName" property if it is provided with this function. + \a exceptionStackTrace is used to report whether an uncaught exception was + thrown. If you pass a non-null pointer to a QStringList to it, it will set + it to list of "stackframe messages" if the script threw an unhandled + exception, or an empty list otherwise. A stackframe message has the format + function name:line number:column:file name + \note In some cases, e.g. for native functions, function name and file name + can be empty and line number and column can be -1. + \note If an exception was thrown and the exception value is not an Error instance (i.e., QJSValue::isError() returns \c false), the - exception value will still be returned, but there is currently no - API for detecting that an exception did occur in this case. + exception value will still be returned. Use \c exceptionStackTrace->isEmpty() + to distinguish whether the value was a normal or an exceptional return + value. + + \sa QQmlExpression::evaluate */ -QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) +QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, int lineNumber, QStringList *exceptionStackTrace) { QV4::ExecutionEngine *v4 = m_v4Engine; QV4::Scope scope(v4); @@ -546,16 +527,27 @@ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, in script.strictMode = v4->globalCode->isStrict(); script.inheritContext = true; script.parse(); - if (!scope.engine->hasException) + if (!scope.hasException()) result = script.run(); - if (scope.engine->hasException) - result = v4->catchException(); - if (v4->isInterrupted.loadAcquire()) + if (exceptionStackTrace) + exceptionStackTrace->clear(); + if (scope.hasException()) { + QV4::StackTrace trace; + result = v4->catchException(&trace); + if (exceptionStackTrace) { + for (auto &&frame: trace) + exceptionStackTrace->push_back(QLatin1StringView("%1:%2:%3:%4").arg( + frame.function, + QString::number(qAbs(frame.line)), + QString::number(frame.column), + frame.source) + ); + } + } + if (v4->isInterrupted.loadRelaxed()) result = v4->newErrorObject(QStringLiteral("Interrupted")); - QJSValue retval(v4, result->asReturnedValue()); - - return retval; + return QJSValuePrivate::fromReturnedValue(result->asReturnedValue()); } /*! @@ -576,26 +568,68 @@ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, in \note If an exception is thrown during the loading of the module, the return value will be the exception (typically an \c{Error} object; see QJSValue::isError()). + \sa registerModule() + \since 5.12 */ QJSValue QJSEngine::importModule(const QString &fileName) { const QUrl url = urlForFileName(QFileInfo(fileName).canonicalFilePath()); - auto moduleUnit = m_v4Engine->loadModule(url); + const auto module = m_v4Engine->loadModule(url); if (m_v4Engine->hasException) - return QJSValue(m_v4Engine, m_v4Engine->catchException()); + return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException()); + + QV4::Scope scope(m_v4Engine); + if (const auto compiled = module.compiled) { + QV4::Scoped<QV4::Module> moduleNamespace(scope, compiled->instantiate()); + if (m_v4Engine->hasException) + return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException()); + compiled->evaluate(); + if (!m_v4Engine->isInterrupted.loadRelaxed()) + return QJSValuePrivate::fromReturnedValue(moduleNamespace->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue( + m_v4Engine->newErrorObject(QStringLiteral("Interrupted"))->asReturnedValue()); + } + + // If there is neither a native nor a compiled module, we should have seen an exception + Q_ASSERT(module.native); + + return QJSValuePrivate::fromReturnedValue(module.native->asReturnedValue()); +} + +/*! + Registers a QJSValue to serve as a module. After this function is called, + all modules that import \a moduleName will import the value of \a value + instead of loading \a moduleName from the filesystem. + Any valid QJSValue can be registered, but named exports (i.e. + \c {import { name } from "info"} are treated as members of an object, so + the default export must be created with one of the newXYZ methods of + QJSEngine. + + Because this allows modules that do not exist on the filesystem to be imported, + scripting applications can use this to provide built-in modules, similar to + Node.js. + + Returns \c true on success, \c false otherwise. + + \note The QJSValue \a value is not called or read until it is used by another module. + This means that there is no code to evaluate, so no errors will be seen until + another module throws an exception while trying to load this module. + + \warning Attempting to access a named export from a QJSValue that is not an + object will trigger a \l{Script Exceptions}{exception}. + + \sa importModule() + */ +bool QJSEngine::registerModule(const QString &moduleName, const QJSValue &value) +{ QV4::Scope scope(m_v4Engine); - QV4::Scoped<QV4::Module> moduleNamespace(scope, moduleUnit->instantiate(m_v4Engine)); + QV4::ScopedValue v4Value(scope, QJSValuePrivate::asReturnedValue(&value)); + m_v4Engine->registerNativeModule(QUrl(moduleName), v4Value); if (m_v4Engine->hasException) - return QJSValue(m_v4Engine, m_v4Engine->catchException()); - moduleUnit->evaluate(); - if (!m_v4Engine->isInterrupted.loadAcquire()) - return QJSValue(m_v4Engine, moduleNamespace->asReturnedValue()); - - return QJSValue( - m_v4Engine, - m_v4Engine->newErrorObject(QStringLiteral("Interrupted"))->asReturnedValue()); + return false; + return true; } /*! @@ -610,7 +644,23 @@ QJSValue QJSEngine::newObject() { QV4::Scope scope(m_v4Engine); QV4::ScopedValue v(scope, m_v4Engine->newObject()); - return QJSValue(m_v4Engine, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); +} + +/*! + \since 6.2 + + Creates a JavaScript object of class Symbol, with value \a name. + + The prototype of the created object will be the Symbol prototype object. + + \sa newObject() +*/ +QJSValue QJSEngine::newSymbol(const QString &name) +{ + QV4::Scope scope(m_v4Engine); + QV4::ScopedValue v(scope, QV4::Symbol::create(m_v4Engine, u'@' + name)); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } /*! @@ -652,7 +702,7 @@ QJSValue QJSEngine::newErrorObject(QJSValue::ErrorType errorType, const QString case QJSValue::NoError: return QJSValue::UndefinedValue; } - return QJSValue(m_v4Engine, error->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(error->asReturnedValue()); } /*! @@ -667,7 +717,7 @@ QJSValue QJSEngine::newArray(uint length) if (length < 0x1000) array->arrayReserve(length); array->setArrayLengthUnchecked(length); - return QJSValue(m_v4Engine, array.asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(array.asReturnedValue()); } /*! @@ -692,7 +742,6 @@ QJSValue QJSEngine::newArray(uint length) */ QJSValue QJSEngine::newQObject(QObject *object) { - Q_D(QJSEngine); QV4::ExecutionEngine *v4 = m_v4Engine; QV4::Scope scope(v4); if (object) { @@ -701,7 +750,7 @@ QJSValue QJSEngine::newQObject(QObject *object) QQmlEngine::setObjectOwnership(object, QQmlEngine::JavaScriptOwnership); } QV4::ScopedValue v(scope, QV4::QObjectWrapper::wrap(v4, object)); - return QJSValue(v4, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } /*! @@ -719,11 +768,10 @@ QJSValue QJSEngine::newQObject(QObject *object) */ QJSValue QJSEngine::newQMetaObject(const QMetaObject* metaObject) { - Q_D(QJSEngine); QV4::ExecutionEngine *v4 = m_v4Engine; QV4::Scope scope(v4); QV4::ScopedValue v(scope, QV4::QMetaObjectWrapper::create(v4, metaObject)); - return QJSValue(v4, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } /*! \fn template <typename T> QJSValue QJSEngine::newQMetaObject() @@ -750,152 +798,252 @@ QJSValue QJSEngine::globalObject() const { QV4::Scope scope(m_v4Engine); QV4::ScopedValue v(scope, m_v4Engine->globalObject); - return QJSValue(m_v4Engine, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); +} + +QJSPrimitiveValue QJSEngine::createPrimitive(QMetaType type, const void *ptr) +{ + QV4::Scope scope(m_v4Engine); + QV4::ScopedValue v(scope, m_v4Engine->metaTypeToJS(type, ptr)); + return QV4::ExecutionEngine::createPrimitive(v); +} + +QJSManagedValue QJSEngine::createManaged(QMetaType type, const void *ptr) +{ + QJSManagedValue result(m_v4Engine); + *result.d = m_v4Engine->metaTypeToJS(type, ptr); + return result; } /*! * \internal * used by QJSEngine::toScriptValue */ -QJSValue QJSEngine::create(int type, const void *ptr) +QJSValue QJSEngine::create(QMetaType type, const void *ptr) { QV4::Scope scope(m_v4Engine); QV4::ScopedValue v(scope, scope.engine->metaTypeToJS(type, ptr)); - return QJSValue(m_v4Engine, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); +} + +bool QJSEngine::convertPrimitive(const QJSPrimitiveValue &value, QMetaType type, void *ptr) +{ + switch (value.type()) { + case QJSPrimitiveValue::Undefined: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::undefinedValue(), type, ptr); + case QJSPrimitiveValue::Null: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::nullValue(), type, ptr); + case QJSPrimitiveValue::Boolean: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::fromBoolean(value.toBoolean()), type, ptr); + case QJSPrimitiveValue::Integer: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::fromInt32(value.toInteger()), type, ptr); + case QJSPrimitiveValue::Double: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::fromDouble(value.toDouble()), type, ptr); + case QJSPrimitiveValue::String: + return convertString(value.toString(), type, ptr); + } + + Q_UNREACHABLE_RETURN(false); +} + +bool QJSEngine::convertManaged(const QJSManagedValue &value, int type, void *ptr) +{ + return convertManaged(value, QMetaType(type), ptr); +} + +bool QJSEngine::convertManaged(const QJSManagedValue &value, QMetaType type, void *ptr) +{ + return QV4::ExecutionEngine::metaTypeFromJS(*value.d, type, ptr); +} + +bool QJSEngine::convertString(const QString &string, QMetaType metaType, void *ptr) +{ + // have a string based value without engine. Do conversion manually + if (metaType == QMetaType::fromType<bool>()) { + *reinterpret_cast<bool*>(ptr) = string.size() != 0; + return true; + } + if (metaType == QMetaType::fromType<QString>()) { + *reinterpret_cast<QString*>(ptr) = string; + return true; + } + if (metaType == QMetaType::fromType<QUrl>()) { + *reinterpret_cast<QUrl *>(ptr) = QUrl(string); + return true; + } + + double d = QV4::RuntimeHelpers::stringToNumber(string); + switch (metaType.id()) { + case QMetaType::Int: + *reinterpret_cast<int*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UInt: + *reinterpret_cast<uint*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::Long: + *reinterpret_cast<long*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::ULong: + *reinterpret_cast<ulong*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::LongLong: + *reinterpret_cast<qlonglong*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::ULongLong: + *reinterpret_cast<qulonglong*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::Double: + *reinterpret_cast<double*>(ptr) = d; + return true; + case QMetaType::Float: + *reinterpret_cast<float*>(ptr) = d; + return true; + case QMetaType::Short: + *reinterpret_cast<short*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UShort: + *reinterpret_cast<unsigned short*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::Char: + *reinterpret_cast<char*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UChar: + *reinterpret_cast<unsigned char*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::QChar: + *reinterpret_cast<QChar*>(ptr) = QChar(QV4::Value::toUInt32(d)); + return true; + case QMetaType::Char16: + *reinterpret_cast<char16_t *>(ptr) = QV4::Value::toUInt32(d); + return true; + default: + return false; + } } /*! \internal convert \a value to \a type, store the result in \a ptr */ -bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr) +bool QJSEngine::convertV2(const QJSValue &value, QMetaType metaType, void *ptr) { - QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(&value); - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(&value, &scratch); - if (v4) { - QV4::Scope scope(v4); - QV4::ScopedValue v(scope, *val); - return scope.engine->metaTypeFromJS(v, type, ptr); - } + if (const QString *string = QJSValuePrivate::asQString(&value)) + return convertString(*string, metaType, ptr); - if (!val) { - QVariant *variant = QJSValuePrivate::getVariant(&value); - Q_ASSERT(variant); - - if (variant->userType() == QMetaType::QString) { - QString string = variant->toString(); - // have a string based value without engine. Do conversion manually - if (type == QMetaType::Bool) { - *reinterpret_cast<bool*>(ptr) = string.length() != 0; - return true; - } - if (type == QMetaType::QString) { - *reinterpret_cast<QString*>(ptr) = string; - return true; - } - double d = QV4::RuntimeHelpers::stringToNumber(string); - switch (type) { - case QMetaType::Int: - *reinterpret_cast<int*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UInt: - *reinterpret_cast<uint*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::LongLong: - *reinterpret_cast<qlonglong*>(ptr) = QV4::Value::toInteger(d); - return true; - case QMetaType::ULongLong: - *reinterpret_cast<qulonglong*>(ptr) = QV4::Value::toInteger(d); - return true; - case QMetaType::Double: - *reinterpret_cast<double*>(ptr) = d; - return true; - case QMetaType::Float: - *reinterpret_cast<float*>(ptr) = d; - return true; - case QMetaType::Short: - *reinterpret_cast<short*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UShort: - *reinterpret_cast<unsigned short*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::Char: - *reinterpret_cast<char*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UChar: - *reinterpret_cast<unsigned char*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::QChar: - *reinterpret_cast<QChar*>(ptr) = QV4::Value::toUInt32(d); - return true; - default: - return false; - } - } else { - return QMetaType::convert(&variant->data_ptr(), variant->userType(), ptr, type); - } - } + // Does not need scoping since QJSValue still holds on to the value. + return QV4::ExecutionEngine::metaTypeFromJS(QJSValuePrivate::asReturnedValue(&value), metaType, ptr); +} - Q_ASSERT(val); - - switch (type) { - case QMetaType::Bool: - *reinterpret_cast<bool*>(ptr) = val->toBoolean(); - return true; - case QMetaType::Int: - *reinterpret_cast<int*>(ptr) = val->toInt32(); - return true; - case QMetaType::UInt: - *reinterpret_cast<uint*>(ptr) = val->toUInt32(); - return true; - case QMetaType::LongLong: - *reinterpret_cast<qlonglong*>(ptr) = val->toInteger(); - return true; - case QMetaType::ULongLong: - *reinterpret_cast<qulonglong*>(ptr) = val->toInteger(); - return true; - case QMetaType::Double: - *reinterpret_cast<double*>(ptr) = val->toNumber(); - return true; - case QMetaType::QString: - *reinterpret_cast<QString*>(ptr) = val->toQStringNoThrow(); - return true; - case QMetaType::Float: - *reinterpret_cast<float*>(ptr) = val->toNumber(); - return true; - case QMetaType::Short: - *reinterpret_cast<short*>(ptr) = val->toInt32(); - return true; - case QMetaType::UShort: - *reinterpret_cast<unsigned short*>(ptr) = val->toUInt16(); - return true; - case QMetaType::Char: - *reinterpret_cast<char*>(ptr) = val->toInt32(); - return true; - case QMetaType::UChar: - *reinterpret_cast<unsigned char*>(ptr) = val->toUInt16(); - return true; - case QMetaType::QChar: - *reinterpret_cast<QChar*>(ptr) = val->toUInt16(); - return true; - default: - return false; - } +bool QJSEngine::convertVariant(const QVariant &value, QMetaType metaType, void *ptr) +{ + // TODO: We could probably avoid creating a QV4::Value in many cases, but we'd have to + // duplicate much of metaTypeFromJS and some methods of QV4::Value itself here. + QV4::Scope scope(handle()); + QV4::ScopedValue scoped(scope, scope.engine->fromVariant(value)); + return QV4::ExecutionEngine::metaTypeFromJS(scoped, metaType, ptr); +} + +bool QJSEngine::convertMetaType(QMetaType fromType, const void *from, QMetaType toType, void *to) +{ + // TODO: We could probably avoid creating a QV4::Value in many cases, but we'd have to + // duplicate much of metaTypeFromJS and some methods of QV4::Value itself here. + QV4::Scope scope(handle()); + QV4::ScopedValue scoped(scope, scope.engine->fromData(fromType, from)); + return QV4::ExecutionEngine::metaTypeFromJS(scoped, toType, to); +} + +QString QJSEngine::convertQObjectToString(QObject *object) +{ + return QV4::QObjectWrapper::objectToString( + handle(), object ? object->metaObject() : nullptr, object); +} + +QString QJSEngine::convertDateTimeToString(const QDateTime &dateTime) +{ + return QV4::DateObject::dateTimeToString(dateTime, handle()); +} + +double QJSEngine::convertDateTimeToNumber(const QDateTime &dateTime) +{ + return QV4::DateObject::dateTimeToNumber(dateTime); +} + +QDate QJSEngine::convertDateTimeToDate(const QDateTime &dateTime) +{ + return QV4::DateObject::dateTimeToDate(dateTime); } /*! \fn template <typename T> QJSValue QJSEngine::toScriptValue(const T &value) Creates a QJSValue with the given \a value. - \sa fromScriptValue() + \sa fromScriptValue(), coerceValue() +*/ + +/*! \fn template <typename T> QJSManagedValue QJSEngine::toManagedValue(const T &value) + + Creates a QJSManagedValue with the given \a value. + + \sa fromManagedValue(), coerceValue() +*/ + +/*! \fn template <typename T> QJSPrimitiveValue QJSEngine::toPrimitiveValue(const T &value) + + Creates a QJSPrimitiveValue with the given \a value. + + Since QJSPrimitiveValue can only hold int, bool, double, QString, and the + equivalents of JavaScript \c null and \c undefined, the value will be + coerced aggressively if you pass any other type. + + \sa fromPrimitiveValue(), coerceValue() */ /*! \fn template <typename T> T QJSEngine::fromScriptValue(const QJSValue &value) Returns the given \a value converted to the template type \c{T}. - \sa toScriptValue() + \sa toScriptValue(), coerceValue() +*/ + +/*! \fn template <typename T> T QJSEngine::fromManagedValue(const QJSManagedValue &value) + + Returns the given \a value converted to the template type \c{T}. + + \sa toManagedValue(), coerceValue() +*/ + +/*! \fn template <typename T> T QJSEngine::fromPrimitiveValue(const QJSPrimitiveValue &value) + + Returns the given \a value converted to the template type \c{T}. + + Since QJSPrimitiveValue can only hold int, bool, double, QString, and the + equivalents of JavaScript \c null and \c undefined, the value will be + coerced aggressively if you request any other type. + + \sa toPrimitiveValue(), coerceValue() +*/ + +/*! \fn template <typename T> T QJSEngine::fromVariant(const QVariant &value) + + Returns the given \a value converted to the template type \c{T}. + The conversion is done in JavaScript semantics. Those differ from + qvariant_cast's semantics. There are a number of implicit + conversions between JavaScript-equivalent types that are not + performed by qvariant_cast by default. + + \sa coerceValue(), fromScriptValue(), {QVariant::}{qvariant_cast()} +*/ + +/*! \fn template <typename From, typename To> T QJSEngine::coerceValue(const From &from) + + Returns the given \a from converted to the template type \c{To}. + The conversion is done in JavaScript semantics. Those differ from + qvariant_cast's semantics. There are a number of implicit + conversions between JavaScript-equivalent types that are not + performed by qvariant_cast by default. This method is a generalization of + all the other conversion methods in this class. + + \sa fromVariant(), {QVariant::}{qvariant_cast()}, fromScriptValue(), toScriptValue() */ /*! @@ -907,7 +1055,7 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr) JavaScript function through QJSEngine. When returning from C++, the engine will interrupt the normal flow of - execution and call the the next pre-registered exception handler with + execution and call the next pre-registered exception handler with an error object that contains the given \a message. The error object will point to the location of the top-most context on the JavaScript caller stack; specifically, it will have properties \c lineNumber, @@ -998,12 +1146,78 @@ void QJSEngine::throwError(QJSValue::ErrorType errorType, const QString &message { QV4::Scope scope(m_v4Engine); QJSValue error = newErrorObject(errorType, message); - QV4::ScopedObject e(scope, QJSValuePrivate::getValue(&error)); + QV4::ScopedObject e(scope, QJSValuePrivate::asReturnedValue(&error)); if (!e) return; m_v4Engine->throwError(e); } +/*! + \overload throwError() + + Throws a pre-constructed run-time \a error (exception). This way you can + use \l newErrorObject() to create the error and customize it as necessary. + + \since 6.1 + \sa {Script Exceptions}, newErrorObject() +*/ +void QJSEngine::throwError(const QJSValue &error) +{ + m_v4Engine->throwError(QJSValuePrivate::asReturnedValue(&error)); +} + +/*! + * Returns \c true if the last JavaScript execution resulted in an exception or + * if throwError() was called. Otherwise returns \c false. Mind that evaluate() + * catches any exceptions thrown in the evaluated code. + * + * \since Qt 6.1 + */ +bool QJSEngine::hasError() const +{ + return m_v4Engine->hasException; +} + +/*! + * If an exception is currently pending, catches it and returns it as a + * QJSValue. Otherwise returns undefined as QJSValue. After calling this method + * hasError() returns \c false. + * + * \since Qt 6.1 + */ +QJSValue QJSEngine::catchError() +{ + if (m_v4Engine->hasException) + return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException()); + else + return QJSValue(); +} + +/*! + \property QJSEngine::uiLanguage + \brief the language to be used for translating user interface strings + \since 5.15 + + This property holds the name of the language to be used for user interface + string translations. It is exposed for reading and writing as \c{Qt.uiLanguage} when + the QJSEngine::TranslationExtension is installed on the engine. It is always exposed + in instances of QQmlEngine. + + You can set the value freely and use it in bindings. It is recommended to set it + after installing translators in your application. By convention, an empty string + means no translation from the language used in the source code is intended to occur. +*/ +void QJSEngine::setUiLanguage(const QString &language) { + Q_D(QJSEngine); + d->uiLanguage = language; // property takes care of signal emission if necessary +} + +QString QJSEngine::uiLanguage() const +{ + Q_D(const QJSEngine); + return d->uiLanguage; +} + QJSEnginePrivate *QJSEnginePrivate::get(QV4::ExecutionEngine *e) { return e->jsEngine()->d_func(); @@ -1046,12 +1260,87 @@ void QJSEnginePrivate::removeFromDebugServer(QJSEngine *q) */ QJSEngine *qjsEngine(const QObject *object) { - QQmlData *data = QQmlData::get(object, false); + QQmlData *data = QQmlData::get(object); if (!data || data->jsWrapper.isNullOrUndefined()) return nullptr; return data->jsWrapper.engine()->jsEngine(); } + +/*! + \enum QJSEngine::ObjectOwnership + + ObjectOwnership controls whether or not the JavaScript memory manager automatically destroys the + QObject when the corresponding JavaScript object is garbage collected by the + engine. The two ownership options are: + + \value CppOwnership The object is owned by C++ code and the JavaScript memory manager will never + delete it. The JavaScript destroy() method cannot be used on these objects. This + option is similar to QScriptEngine::QtOwnership. + + \value JavaScriptOwnership The object is owned by JavaScript. When the object + is returned to the JavaScript memory manager as the return value of a method call, the JavaScript + memory manager will track it and delete it if there are no remaining JavaScript references to it + and it has no QObject::parent(). An object tracked by one QJSEngine will be deleted during that + QJSEngine's destructor. Thus, JavaScript references between objects with JavaScriptOwnership from + two different engines will not be valid if one of these engines is deleted. This option is similar + to QScriptEngine::ScriptOwnership. + + Generally an application doesn't need to set an object's ownership explicitly. The JavaScript + memory manager uses a heuristic to set the default ownership. By default, an object that is + created by the JavaScript memory manager has JavaScriptOwnership. The exception to this are the + root objects created by calling QQmlComponent::create() or QQmlComponent::beginCreate(), which + have CppOwnership by default. The ownership of these root-level objects is considered to have been + transferred to the C++ caller. + + Objects not-created by the JavaScript memory manager have CppOwnership by default. The exception + to this are objects returned from C++ method calls; their ownership will be set to + JavaScriptOwnership. This applies only to explicit invocations of Q_INVOKABLE methods or slots, + but not to property getter invocations. + + Calling setObjectOwnership() overrides the default ownership. + + \sa {Data Ownership} +*/ + +/*! + Sets the \a ownership of \a object. + + An object with \c JavaScriptOwnership is not garbage collected as long + as it still has a parent, even if there are no references to it. + + \sa QJSEngine::ObjectOwnership +*/ +void QJSEngine::setObjectOwnership(QObject *object, ObjectOwnership ownership) +{ + if (!object) + return; + + QQmlData *ddata = QQmlData::get(object, true); + if (!ddata) + return; + + ddata->indestructible = (ownership == CppOwnership)?true:false; + ddata->explicitIndestructibleSet = true; +} + +/*! + Returns the ownership of \a object. + + \sa QJSEngine::ObjectOwnership +*/ +QJSEngine::ObjectOwnership QJSEngine::objectOwnership(QObject *object) +{ + if (!object) + return CppOwnership; + + QQmlData *ddata = QQmlData::get(object, false); + if (!ddata) + return CppOwnership; + else + return ddata->indestructible?CppOwnership:JavaScriptOwnership; +} + QT_END_NAMESPACE #include "moc_qjsengine.cpp" diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index 31a4d68baa..b1c99db220 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QJSENGINE_H #define QJSENGINE_H @@ -45,8 +9,9 @@ #include <QtCore/qvariant.h> #include <QtCore/qsharedpointer.h> #include <QtCore/qobject.h> +#include <QtCore/qtimezone.h> #include <QtQml/qjsvalue.h> - +#include <QtQml/qjsmanagedvalue.h> #include <QtQml/qqmldebug.h> QT_BEGIN_NAMESPACE @@ -60,6 +25,7 @@ class Q_QML_EXPORT QJSEngine : public QObject { Q_OBJECT + Q_PROPERTY(QString uiLanguage READ uiLanguage WRITE setUiLanguage NOTIFY uiLanguageChanged) public: QJSEngine(); explicit QJSEngine(QObject *parent); @@ -67,11 +33,13 @@ public: QJSValue globalObject() const; - QJSValue evaluate(const QString &program, const QString &fileName = QString(), int lineNumber = 1); + QJSValue evaluate(const QString &program, const QString &fileName = QString(), int lineNumber = 1, QStringList *exceptionStackTrace = nullptr); QJSValue importModule(const QString &fileName); + bool registerModule(const QString &moduleName, const QJSValue &value); QJSValue newObject(); + QJSValue newSymbol(const QString &name); QJSValue newArray(uint length = 0); QJSValue newQObject(QObject *object); @@ -89,19 +57,230 @@ public: template <typename T> inline QJSValue toScriptValue(const T &value) { - return create(qMetaTypeId<T>(), &value); + return create(QMetaType::fromType<T>(), &value); + } + + template <typename T> + inline QJSManagedValue toManagedValue(const T &value) + { + return createManaged(QMetaType::fromType<T>(), &value); + } + + template <typename T> + inline QJSPrimitiveValue toPrimitiveValue(const T &value) + { + // In the common case that the argument fits into QJSPrimitiveValue, use it. + if constexpr (std::disjunction_v< + std::is_same<T, int>, + std::is_same<T, bool>, + std::is_same<T, double>, + std::is_same<T, QString>>) { + return QJSPrimitiveValue(value); + } else { + return createPrimitive(QMetaType::fromType<T>(), &value); + } } + template <typename T> inline T fromScriptValue(const QJSValue &value) { return qjsvalue_cast<T>(value); } + template <typename T> + inline T fromManagedValue(const QJSManagedValue &value) + { + return qjsvalue_cast<T>(value); + } + + template <typename T> + inline T fromPrimitiveValue(const QJSPrimitiveValue &value) + { + if constexpr (std::is_same_v<T, int>) + return value.toInteger(); + if constexpr (std::is_same_v<T, bool>) + return value.toBoolean(); + if constexpr (std::is_same_v<T, double>) + return value.toDouble(); + if constexpr (std::is_same_v<T, QString>) + return value.toString(); + if constexpr (std::is_same_v<T, QVariant>) + return value.toVariant(); + if constexpr (std::is_pointer_v<T>) + return nullptr; + return qjsvalue_cast<T>(value); + } + + template <typename T> + inline T fromVariant(const QVariant &value) + { + if constexpr (std::is_same_v<T, QVariant>) + return value; + + const QMetaType sourceType = value.metaType(); + const QMetaType targetType = QMetaType::fromType<T>(); + if (sourceType == targetType) + return *reinterpret_cast<const T *>(value.constData()); + + if constexpr (std::is_same_v<T,std::remove_const_t<std::remove_pointer_t<T>> const *>) { + using nonConstT = std::remove_const_t<std::remove_pointer_t<T>> *; + const QMetaType nonConstTargetType = QMetaType::fromType<nonConstT>(); + if (value.metaType() == nonConstTargetType) + return *reinterpret_cast<const nonConstT *>(value.constData()); + } + + if constexpr (std::is_same_v<T, QJSValue>) + return toScriptValue(value); + + if constexpr (std::is_same_v<T, QJSManagedValue>) + return toManagedValue(value); + + if constexpr (std::is_same_v<T, QJSPrimitiveValue>) + return toPrimitiveValue(value); + + if constexpr (std::is_same_v<T, QString>) { + if (sourceType.flags() & QMetaType::PointerToQObject) { + return convertQObjectToString( + *reinterpret_cast<QObject *const *>(value.constData())); + } + } + + if constexpr (std::is_same_v<QObject, std::remove_const_t<std::remove_pointer_t<T>>>) { + if (sourceType.flags() & QMetaType::PointerToQObject) { + return *static_cast<QObject *const *>(value.constData()); + + // We should not access source->metaObject() here since that may trigger some + // rather involved code. convertVariant() can do this using property caches. + } + } + + if (sourceType == QMetaType::fromType<QJSValue>()) + return fromScriptValue<T>(*reinterpret_cast<const QJSValue *>(value.constData())); + + if (sourceType == QMetaType::fromType<QJSManagedValue>()) { + return fromManagedValue<T>( + *reinterpret_cast<const QJSManagedValue *>(value.constData())); + } + + if (sourceType == QMetaType::fromType<QJSPrimitiveValue>()) { + return fromPrimitiveValue<T>( + *reinterpret_cast<const QJSPrimitiveValue *>(value.constData())); + } + + { + T t{}; + if (value.metaType() == QMetaType::fromType<QString>()) { + if (convertString(value.toString(), targetType, &t)) + return t; + } else if (convertVariant(value, targetType, &t)) { + return t; + } + + QMetaType::convert(value.metaType(), value.constData(), targetType, &t); + return t; + } + } + + template<typename From, typename To> + inline To coerceValue(const From &from) + { + if constexpr (std::is_base_of_v<To, From>) + return from; + + if constexpr (std::is_same_v<To, QJSValue>) + return toScriptValue(from); + + if constexpr (std::is_same_v<From, QJSValue>) + return fromScriptValue<To>(from); + + if constexpr (std::is_same_v<To, QJSManagedValue>) + return toManagedValue(from); + + if constexpr (std::is_same_v<From, QJSManagedValue>) + return fromManagedValue<To>(from); + + if constexpr (std::is_same_v<To, QJSPrimitiveValue>) + return toPrimitiveValue(from); + + if constexpr (std::is_same_v<From, QJSPrimitiveValue>) + return fromPrimitiveValue<To>(from); + + if constexpr (std::is_same_v<From, QVariant>) + return fromVariant<To>(from); + + if constexpr (std::is_same_v<To, QVariant>) + return QVariant::fromValue(from); + + if constexpr (std::is_same_v<To, QString>) { + if constexpr (std::is_base_of_v<QObject, std::remove_const_t<std::remove_pointer_t<From>>>) + return convertQObjectToString(from); + } + + if constexpr (std::is_same_v<From, QDateTime>) { + if constexpr (std::is_same_v<To, QDate>) + return convertDateTimeToDate(from.toLocalTime()); + if constexpr (std::is_same_v<To, QTime>) + return from.toLocalTime().time(); + if constexpr (std::is_same_v<To, QString>) + return convertDateTimeToString(from.toLocalTime()); + if constexpr (std::is_same_v<To, double>) + return convertDateTimeToNumber(from.toLocalTime()); + } + + if constexpr (std::is_same_v<From, QDate>) { + if constexpr (std::is_same_v<To, QDateTime>) + return from.startOfDay(QTimeZone::UTC); + if constexpr (std::is_same_v<To, QTime>) { + // This is the current time zone offset, for better or worse + return coerceValue<QDateTime, QTime>(coerceValue<QDate, QDateTime>(from)); + } + if constexpr (std::is_same_v<To, QString>) + return convertDateTimeToString(coerceValue<QDate, QDateTime>(from)); + if constexpr (std::is_same_v<To, double>) + return convertDateTimeToNumber(coerceValue<QDate, QDateTime>(from)); + } + + if constexpr (std::is_same_v<From, QTime>) { + if constexpr (std::is_same_v<To, QDate>) { + // Yes. April Fools' 1971. See qv4dateobject.cpp. + return from.isValid() ? QDate(1971, 4, 1) : QDate(); + } + + if constexpr (std::is_same_v<To, QDateTime>) + return QDateTime(coerceValue<QTime, QDate>(from), from, QTimeZone::LocalTime); + if constexpr (std::is_same_v<To, QString>) + return convertDateTimeToString(coerceValue<QTime, QDateTime>(from)); + if constexpr (std::is_same_v<To, double>) + return convertDateTimeToNumber(coerceValue<QTime, QDateTime>(from)); + } + + if constexpr (std::is_same_v<To, std::remove_const_t<std::remove_pointer_t<To>> const *>) { + using nonConstTo = std::remove_const_t<std::remove_pointer_t<To>> *; + if constexpr (std::is_same_v<From, nonConstTo>) + return from; + } + + { + const QMetaType sourceType = QMetaType::fromType<From>(); + const QMetaType targetType = QMetaType::fromType<To>(); + To to{}; + if constexpr (std::is_same_v<From, QString>) { + if (convertString(from, targetType, &to)) + return to; + } else if (convertMetaType(sourceType, &from, targetType, &to)) { + return to; + } + + QMetaType::convert(sourceType, &from, targetType, &to); + return to; + } + } + void collectGarbage(); -#if QT_DEPRECATED_SINCE(5, 6) - QT_DEPRECATED void installTranslatorFunctions(const QJSValue &object = QJSValue()); -#endif + enum ObjectOwnership { CppOwnership, JavaScriptOwnership }; + static void setObjectOwnership(QObject *, ObjectOwnership); + static ObjectOwnership objectOwnership(QObject *); enum Extension { TranslationExtension = 0x1, @@ -120,13 +299,49 @@ public: void throwError(const QString &message); void throwError(QJSValue::ErrorType errorType, const QString &message = QString()); + void throwError(const QJSValue &error); + bool hasError() const; + QJSValue catchError(); + + QString uiLanguage() const; + void setUiLanguage(const QString &language); + +Q_SIGNALS: + void uiLanguageChanged(); private: - QJSValue create(int type, const void *ptr); + QJSPrimitiveValue createPrimitive(QMetaType type, const void *ptr); + QJSManagedValue createManaged(QMetaType type, const void *ptr); + QJSValue create(QMetaType type, const void *ptr); +#if QT_QML_REMOVED_SINCE(6, 5) + QJSValue create(int id, const void *ptr); // only there for BC reasons +#endif + + static bool convertPrimitive(const QJSPrimitiveValue &value, QMetaType type, void *ptr); + static bool convertManaged(const QJSManagedValue &value, int type, void *ptr); + static bool convertManaged(const QJSManagedValue &value, QMetaType type, void *ptr); +#if QT_QML_REMOVED_SINCE(6, 5) + static bool convertV2(const QJSValue &value, int type, void *ptr); // only there for BC reasons +#endif + static bool convertV2(const QJSValue &value, QMetaType metaType, void *ptr); + static bool convertString(const QString &string, QMetaType metaType, void *ptr); + + bool convertVariant(const QVariant &value, QMetaType metaType, void *ptr); + bool convertMetaType(QMetaType fromType, const void *from, QMetaType toType, void *to); + + QString convertQObjectToString(QObject *object); + QString convertDateTimeToString(const QDateTime &dateTime); + double convertDateTimeToNumber(const QDateTime &dateTime); + static QDate convertDateTimeToDate(const QDateTime &dateTime); - static bool convertV2(const QJSValue &value, int type, void *ptr); + template<typename T> + friend inline T qjsvalue_cast(const QJSValue &); - friend inline bool qjsvalue_cast_helper(const QJSValue &, int, void *); + template<typename T> + friend inline T qjsvalue_cast(const QJSManagedValue &); + + template<typename T> + friend inline T qjsvalue_cast(const QJSPrimitiveValue &); protected: QJSEngine(QJSEnginePrivate &dd, QObject *parent = nullptr); @@ -139,23 +354,30 @@ private: Q_DECLARE_OPERATORS_FOR_FLAGS(QJSEngine::Extensions) -inline bool qjsvalue_cast_helper(const QJSValue &value, int type, void *ptr) +template<typename T> +T qjsvalue_cast(const QJSValue &value) { - return QJSEngine::convertV2(value, type, ptr); + if (T t; QJSEngine::convertV2(value, QMetaType::fromType<T>(), &t)) + return t; + return qvariant_cast<T>(value.toVariant()); } template<typename T> -T qjsvalue_cast(const QJSValue &value) +T qjsvalue_cast(const QJSManagedValue &value) { - T t; - const int id = qMetaTypeId<T>(); + if (T t; QJSEngine::convertManaged(value, QMetaType::fromType<T>(), &t)) + return t; - if (qjsvalue_cast_helper(value, id, &t)) + return qvariant_cast<T>(value.toVariant()); +} + +template<typename T> +T qjsvalue_cast(const QJSPrimitiveValue &value) +{ + if (T t; QJSEngine::convertPrimitive(value, QMetaType::fromType<T>(), &t)) return t; - else if (value.isVariant()) - return qvariant_cast<T>(value.toVariant()); - return T(); + return qvariant_cast<T>(value.toVariant()); } template <> @@ -164,6 +386,18 @@ inline QVariant qjsvalue_cast<QVariant>(const QJSValue &value) return value.toVariant(); } +template <> +inline QVariant qjsvalue_cast<QVariant>(const QJSManagedValue &value) +{ + return value.toVariant(); +} + +template <> +inline QVariant qjsvalue_cast<QVariant>(const QJSPrimitiveValue &value) +{ + return value.toVariant(); +} + Q_QML_EXPORT QJSEngine *qjsEngine(const QObject *); QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjsengine_p.h b/src/qml/jsapi/qjsengine_p.h index 164a70d000..735dae8d81 100644 --- a/src/qml/jsapi/qjsengine_p.h +++ b/src/qml/jsapi/qjsengine_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QJSENGINE_P_H #define QJSENGINE_P_H @@ -53,6 +17,7 @@ #include <QtCore/private/qobject_p.h> #include <QtCore/qmutex.h> +#include <QtCore/qproperty.h> #include "qjsengine.h" #include "private/qtqmlglobal_p.h" #include <private/qqmlmetatype_p.h> @@ -65,7 +30,7 @@ namespace QV4 { struct ExecutionEngine; } -class Q_QML_PRIVATE_EXPORT QJSEnginePrivate : public QObjectPrivate +class Q_QML_EXPORT QJSEnginePrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QJSEngine) @@ -80,114 +45,10 @@ public: static void addToDebugServer(QJSEngine *q); static void removeFromDebugServer(QJSEngine *q); - // Locker locks the QQmlEnginePrivate data structures for read and write, if necessary. - // Currently, locking is only necessary if the threaded loader is running concurrently. If it is - // either idle, or is running with the main thread blocked, no locking is necessary. This way - // we only pay for locking when we have to. - // Consequently, this class should only be used to protect simple accesses or modifications of the - // QQmlEnginePrivate structures or operations that can be guaranteed not to start activity - // on the loader thread. - // The Locker API is identical to QMutexLocker. Locker reuses the QQmlEnginePrivate::mutex - // QMutex instance and multiple Lockers are recursive in the same thread. - class Locker - { - public: - inline Locker(const QJSEngine *); - inline Locker(const QJSEnginePrivate *); - inline ~Locker(); - - inline void unlock(); - inline void relock(); - - private: - const QJSEnginePrivate *m_ep; - quint32 m_locked:1; - }; - - // Shared by QQmlEngine - mutable QRecursiveMutex mutex; - - - // These methods may be called from the QML loader thread - inline QQmlPropertyCache *cache(QObject *obj, int minorVersion = -1); - inline QQmlPropertyCache *cache(const QMetaObject *, int minorVersion = -1); + void uiLanguageChanged() { Q_Q(QJSEngine); if (q) q->uiLanguageChanged(); } + Q_OBJECT_BINDABLE_PROPERTY(QJSEnginePrivate, QString, uiLanguage, &QJSEnginePrivate::uiLanguageChanged); }; -QJSEnginePrivate::Locker::Locker(const QJSEngine *e) -: m_ep(QJSEnginePrivate::get(e)) -{ - relock(); -} - -QJSEnginePrivate::Locker::Locker(const QJSEnginePrivate *e) -: m_ep(e), m_locked(false) -{ - relock(); -} - -QJSEnginePrivate::Locker::~Locker() -{ - unlock(); -} - -void QJSEnginePrivate::Locker::unlock() -{ - if (m_locked) { - m_ep->mutex.unlock(); - m_locked = false; - } -} - -void QJSEnginePrivate::Locker::relock() -{ - Q_ASSERT(!m_locked); - m_ep->mutex.lock(); - m_locked = true; -} - -/*! -Returns a QQmlPropertyCache for \a obj if one is available. - -If \a obj is null, being deleted or contains a dynamic meta object 0 -is returned. - -The returned cache is not referenced, so if it is to be stored, call addref(). - -XXX thread There is a potential future race condition in this and all the cache() -functions. As the QQmlPropertyCache is returned unreferenced, when called -from the loader thread, it is possible that the cache will have been dereferenced -and deleted before the loader thread has a chance to use or reference it. This -can't currently happen as the cache holds a reference to the -QQmlPropertyCache until the QQmlEngine is destroyed. -*/ -QQmlPropertyCache *QJSEnginePrivate::cache(QObject *obj, int minorVersion) -{ - if (!obj || QObjectPrivate::get(obj)->metaObject || QObjectPrivate::get(obj)->wasDeleted) - return nullptr; - - Locker locker(this); - const QMetaObject *mo = obj->metaObject(); - return QQmlMetaType::propertyCache(mo, minorVersion); -} - -/*! -Returns a QQmlPropertyCache for \a metaObject. - -As the cache is persisted for the life of the engine, \a metaObject must be -a static "compile time" meta-object, or a meta-object that is otherwise known to -exist for the lifetime of the QQmlEngine. - -The returned cache is not referenced, so if it is to be stored, call addref(). -*/ -QQmlPropertyCache *QJSEnginePrivate::cache(const QMetaObject *metaObject, int minorVersion) -{ - Q_ASSERT(metaObject); - - Locker locker(this); - return QQmlMetaType::propertyCache(metaObject, minorVersion); -} - - QT_END_NAMESPACE #endif // QJSENGINE_P_H diff --git a/src/qml/jsapi/qjslist.cpp b/src/qml/jsapi/qjslist.cpp new file mode 100644 index 0000000000..b3a4eed3c0 --- /dev/null +++ b/src/qml/jsapi/qjslist.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtQml/qjslist.h> + +QT_BEGIN_NAMESPACE + +/*! + * \class QJSListIndexClamp + * \internal + */ + +/*! + * \class QJSList + * \internal + */ + +QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjslist.h b/src/qml/jsapi/qjslist.h new file mode 100644 index 0000000000..d604e266f2 --- /dev/null +++ b/src/qml/jsapi/qjslist.h @@ -0,0 +1,368 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QJSLIST_H +#define QJSLIST_H + +#include <QtQml/qtqmlglobal.h> +#include <QtQml/qqmllist.h> +#include <QtQml/qjsengine.h> +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> + +#include <algorithm> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version. It will be kept compatible with the intended usage by +// code generated using qmlcachegen. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +struct QJSListIndexClamp +{ + static qsizetype clamp(qsizetype start, qsizetype max, qsizetype min = 0) + { + Q_ASSERT(min >= 0); + Q_ASSERT(min <= max); + return std::clamp(start < 0 ? max + qsizetype(start) : qsizetype(start), min, max); + } +}; + +template<typename List, typename Value = typename List::value_type> +struct QJSList : private QJSListIndexClamp +{ + Q_DISABLE_COPY_MOVE(QJSList) + + QJSList(List *list, QJSEngine *engine) : m_list(list), m_engine(engine) {} + + Value at(qsizetype index) const + { + Q_ASSERT(index >= 0 && index < size()); + return *(m_list->cbegin() + index); + } + + qsizetype size() const { return m_list->size(); } + + void resize(qsizetype size) + { + m_list->resize(size); + } + + bool includes(const Value &value) const + { + return std::find(m_list->cbegin(), m_list->cend(), value) != m_list->cend(); + } + + bool includes(const Value &value, qsizetype start) const + { + return std::find(m_list->cbegin() + clamp(start, m_list->size()), m_list->cend(), value) + != m_list->cend(); + } + + QString join(const QString &separator = QStringLiteral(",")) const + { + QString result; + bool atBegin = true; + std::for_each(m_list->cbegin(), m_list->cend(), [&](const Value &value) { + if (atBegin) + atBegin = false; + else + result += separator; + result += m_engine->coerceValue<Value, QString>(value); + }); + return result; + } + + List slice() const + { + return *m_list; + } + List slice(qsizetype start) const + { + List result; + std::copy(m_list->cbegin() + clamp(start, m_list->size()), m_list->cend(), + std::back_inserter(result)); + return result; + } + List slice(qsizetype start, qsizetype end) const + { + const qsizetype size = m_list->size(); + const qsizetype clampedStart = clamp(start, size); + const qsizetype clampedEnd = clamp(end, size, clampedStart); + + List result; + std::copy(m_list->cbegin() + clampedStart, m_list->cbegin() + clampedEnd, + std::back_inserter(result)); + return result; + } + + qsizetype indexOf(const Value &value) const + { + const auto begin = m_list->cbegin(); + const auto end = m_list->cend(); + const auto it = std::find(begin, end, value); + if (it == end) + return -1; + const qsizetype result = it - begin; + Q_ASSERT(result >= 0); + return result; + } + qsizetype indexOf(const Value &value, qsizetype start) const + { + const auto begin = m_list->cbegin(); + const auto end = m_list->cend(); + const auto it = std::find(begin + clamp(start, m_list->size()), end, value); + if (it == end) + return -1; + const qsizetype result = it - begin; + Q_ASSERT(result >= 0); + return result; + } + + qsizetype lastIndexOf(const Value &value) const + { + const auto begin = std::make_reverse_iterator(m_list->cend()); + const auto end = std::make_reverse_iterator(m_list->cbegin()); + const auto it = std::find(begin, end, value); + return (end - it) - 1; + } + qsizetype lastIndexOf(const Value &value, qsizetype start) const + { + const qsizetype size = m_list->size(); + if (size == 0) + return -1; + + // Construct a one-past-end iterator as input. + const qsizetype clampedStart = std::min(clamp(start, size), size - 1); + const auto begin = std::make_reverse_iterator(m_list->cbegin() + clampedStart + 1); + + const auto end = std::make_reverse_iterator(m_list->cbegin()); + const auto it = std::find(begin, end, value); + return (end - it) - 1; + } + + QString toString() const { return join(); } + +private: + List *m_list = nullptr; + QJSEngine *m_engine = nullptr; +}; + +template<> +struct QJSList<QQmlListProperty<QObject>, QObject *> : private QJSListIndexClamp +{ + Q_DISABLE_COPY_MOVE(QJSList) + + QJSList(QQmlListProperty<QObject> *list, QJSEngine *engine) : m_list(list), m_engine(engine) {} + + QObject *at(qsizetype index) const + { + Q_ASSERT(index >= 0 && index < size()); + return m_list->at(m_list, index); + } + + qsizetype size() const + { + return m_list->count(m_list); + } + + void resize(qsizetype size) + { + qsizetype current = m_list->count(m_list); + if (current < size && m_list->append) { + do { + m_list->append(m_list, nullptr); + } while (++current < size); + } else if (current > size && m_list->removeLast) { + do { + m_list->removeLast(m_list); + } while (--current > size); + } + } + + bool includes(const QObject *value) const + { + if (!m_list->count || !m_list->at) + return false; + + const qsizetype size = m_list->count(m_list); + for (qsizetype i = 0; i < size; ++i) { + if (m_list->at(m_list, i) == value) + return true; + } + + return false; + } + bool includes(const QObject *value, qsizetype start) const + { + if (!m_list->count || !m_list->at) + return false; + + const qsizetype size = m_list->count(m_list); + for (qsizetype i = clamp(start, size); i < size; ++i) { + if (m_list->at(m_list, i) == value) + return true; + } + + return false; + } + + QString join(const QString &separator = QStringLiteral(",")) const + { + if (!m_list->count || !m_list->at) + return QString(); + + QString result; + for (qsizetype i = 0, end = m_list->count(m_list); i < end; ++i) { + if (i != 0) + result += separator; + result += m_engine->coerceValue<QObject *, QString>(m_list->at(m_list, i)); + } + + return result; + } + + QObjectList slice() const + { + return m_list->toList<QObjectList>(); + } + QObjectList slice(qsizetype start) const + { + if (!m_list->count || !m_list->at) + return QObjectList(); + + const qsizetype size = m_list->count(m_list); + const qsizetype clampedStart = clamp(start, size); + QObjectList result; + result.reserve(size - clampedStart); + for (qsizetype i = clampedStart; i < size; ++i) + result.append(m_list->at(m_list, i)); + return result; + } + QObjectList slice(qsizetype start, qsizetype end) const + { + if (!m_list->count || !m_list->at) + return QObjectList(); + + const qsizetype size = m_list->count(m_list); + const qsizetype clampedStart = clamp(start, size); + const qsizetype clampedEnd = clamp(end, size, clampedStart); + QObjectList result; + result.reserve(clampedEnd - clampedStart); + for (qsizetype i = clampedStart; i < clampedEnd; ++i) + result.append(m_list->at(m_list, i)); + return result; + } + + qsizetype indexOf(const QObject *value) const + { + if (!m_list->count || !m_list->at) + return -1; + + const qsizetype end = m_list->count(m_list); + for (qsizetype i = 0; i < end; ++i) { + if (m_list->at(m_list, i) == value) + return i; + } + return -1; + } + qsizetype indexOf(const QObject *value, qsizetype start) const + { + if (!m_list->count || !m_list->at) + return -1; + + const qsizetype size = m_list->count(m_list); + for (qsizetype i = clamp(start, size); i < size; ++i) { + if (m_list->at(m_list, i) == value) + return i; + } + return -1; + } + + qsizetype lastIndexOf(const QObject *value) const + { + if (!m_list->count || !m_list->at) + return -1; + + for (qsizetype i = m_list->count(m_list) - 1; i >= 0; --i) { + if (m_list->at(m_list, i) == value) + return i; + } + return -1; + } + qsizetype lastIndexOf(const QObject *value, qsizetype start) const + { + if (!m_list->count || !m_list->at) + return -1; + + const qsizetype size = m_list->count(m_list); + if (size == 0) + return -1; + + qsizetype clampedStart = std::min(clamp(start, size), size - 1); + for (qsizetype i = clampedStart; i >= 0; --i) { + if (m_list->at(m_list, i) == value) + return i; + } + return -1; + } + + QString toString() const { return join(); } + +private: + QQmlListProperty<QObject> *m_list = nullptr; + QJSEngine *m_engine = nullptr; +}; + +struct QJSListForInIterator +{ +public: + using Ptr = QJSListForInIterator *; + template<typename List, typename Value> + void init(const QJSList<List, Value> &list) + { + m_index = 0; + m_size = list.size(); + } + + bool hasNext() const { return m_index < m_size; } + qsizetype next() { return m_index++; } + +private: + qsizetype m_index; + qsizetype m_size; +}; + +// QJSListForInIterator must not require initialization so that we can jump over it with goto. +static_assert(std::is_trivial_v<QJSListForInIterator>); + +struct QJSListForOfIterator +{ +public: + using Ptr = QJSListForOfIterator *; + void init() { m_index = 0; } + + template<typename List, typename Value> + bool hasNext(const QJSList<List, Value> &list) const { return m_index < list.size(); } + + template<typename List, typename Value> + Value next(const QJSList<List, Value> &list) { return list.at(m_index++); } + +private: + qsizetype m_index; +}; + +// QJSListForOfIterator must not require initialization so that we can jump over it with goto. +static_assert(std::is_trivial_v<QJSListForOfIterator>); + +QT_END_NAMESPACE + +#endif // QJSLIST_H diff --git a/src/qml/jsapi/qjsmanagedvalue.cpp b/src/qml/jsapi/qjsmanagedvalue.cpp new file mode 100644 index 0000000000..452f991a26 --- /dev/null +++ b/src/qml/jsapi/qjsmanagedvalue.cpp @@ -0,0 +1,1145 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtQml/qjsmanagedvalue.h> +#include <QtQml/qjsengine.h> +#include <QtQml/private/qv4persistent_p.h> +#include <QtQml/private/qv4engine_p.h> +#include <QtQml/private/qv4mm_p.h> +#include <QtQml/private/qjsvalue_p.h> +#include <QtQml/private/qv4runtime_p.h> +#include <QtQml/private/qv4functionobject_p.h> +#include <QtQml/private/qv4jscall_p.h> +#include <QtQml/private/qv4urlobject_p.h> +#include <QtQml/private/qv4variantobject_p.h> +#include <QtQml/private/qv4qobjectwrapper_p.h> +#include <QtQml/private/qv4qmetaobjectwrapper_p.h> +#include <QtQml/private/qv4regexpobject_p.h> +#include <QtQml/private/qv4dateobject_p.h> +#include <QtQml/private/qv4errorobject_p.h> +#include <QtQml/private/qv4identifiertable_p.h> + +#include <QtCore/qregularexpression.h> +#include <QtCore/qurl.h> +#include <QtCore/qdatetime.h> + +QT_BEGIN_NAMESPACE + +/*! + * \class QJSManagedValue + * \inmodule QtQml + * \since 6.1 + * + * \inmodule QtQml + * + * \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-262}. + * + * \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 + * \value Function The \c function 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) +{ + if (!d) + return nullptr; + + QV4::ExecutionEngine *v4 = QV4::PersistentValueStorage::getEngine(d); + Q_ASSERT(v4); + return v4; +} + +/*! + * 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<QV4::Object>()) + *result.d = object->getPrototypeOf(); + else if (auto managed = d->as<QV4::Managed>()) + *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<QV4::Object>() : 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<QV4::Object>())) { + 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; + if (d->isFunctionObject()) + return Function; + 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. + */ + +/*! + * \fn QJSManagedValue::isFunction() const + * + * Returns \c true if the type of this QJSManagedValue is \c function, + * \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<QV4::RegExpObject>(); +} + +/*! + * Returns \c true if this value represents a JavaScript Array + * object, or \c false otherwise. + */ +bool QJSManagedValue::isArray() const +{ + return d && d->as<QV4::ArrayObject>(); +} + +/*! + * Returns \c true if this value represents a JavaScript Url + * object, or \c false otherwise. + */ +bool QJSManagedValue::isUrl() const +{ + return d && d->as<QV4::UrlObject>(); +} + +/*! + * 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<QV4::VariantObject>(); +} + +/*! + * 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<QV4::QObjectWrapper>(); +} + +/*! + * 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<QV4::QMetaObjectWrapper>(); +} + +/*! + * Returns \c true if this value represents a JavaScript Date object, or + * \c false otherwise. + */ +bool QJSManagedValue::isDate() const +{ + return d && d->as<QV4::DateObject>(); +} + +/*! + * Returns \c true if this value represents a JavaScript Error object, or + * \c false otherwise. + */ +bool QJSManagedValue::isError() const +{ + return d && d->as<QV4::ErrorObject>(); +} + +/*! + * \internal + * + * Returns \c true if this value represents a JavaScript meta type, or \c false + * otherwise. + */ +bool QJSManagedValue::isJsMetaType() const +{ + return d && d->as<QV4::InternalClass>(); +} + +/*! + * 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<std::nullptr_t>(), 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 (d->as<QV4::Managed>()) + return QV4::ExecutionEngine::toVariant(*d, QMetaType{}, 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<QV4::RegExpObject>() : 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<QV4::UrlObject>() : 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<QV4::QObjectWrapper>() : 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<QV4::QMetaObjectWrapper>() : 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<QV4::DateObject>() : 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::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); + return obj->hasProperty(key); + } + + 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::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); + return obj->getOwnProperty(key) != 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) + return QJSValue(); + + if (d->isNullOrUndefined()) { + QV4::ExecutionEngine *e = v4Engine(d); + e->throwTypeError(QStringLiteral("Cannot read property '%1' of null").arg(name)); + return QJSValue(); + } + + if (QV4::String *string = d->as<QV4::String>()) { + if (name == QStringLiteral("length")) + return QJSValue(string->d()->length()); + } + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); + return QJSValuePrivate::fromReturnedValue(obj->get(key)); + } + + 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 (d->isNullOrUndefined()) { + v4Engine(d)->throwTypeError( + QStringLiteral("Value is null and could not be converted to an object")); + } + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(&value); + if (Q_UNLIKELY(v4 && v4 != scope.engine)) { + qWarning("QJSManagedValue::setProperty() failed: " + "Value was created in different engine."); + return; + } + QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); + obj->put(key, QJSValuePrivate::convertToReturnedValue(scope.engine, value)); + } +} + +/*! + * Deletes the property \a name from this QJSManagedValue. Returns \c true if + * the deletion succeeded, or \c false otherwise. + */ +bool QJSManagedValue::deleteProperty(const QString &name) +{ + if (!d) + return false; + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); + return obj->deleteProperty(key); + } + + 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::String *string = d->as<QV4::String>()) + return arrayIndex < quint32(string->d()->length()); + + if (QV4::Object *obj = d->as<QV4::Object>()) { + bool hasProperty = false; + if (arrayIndex == std::numeric_limits<quint32>::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::String *string = d->as<QV4::String>()) + return arrayIndex < quint32(string->d()->length()); + + if (QV4::Object *obj = d->as<QV4::Object>()) { + if (arrayIndex == std::numeric_limits<quint32>::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::String *string = d->as<QV4::String>()) { + const QString qString = string->toQString(); + if (arrayIndex < quint32(qString.size())) + return qString.sliced(arrayIndex, 1); + return QJSValue(); + } + + if (QV4::Object *obj = d->as<QV4::Object>()) { + if (arrayIndex == std::numeric_limits<quint32>::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::Object>()) { + 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 \c false otherwise. + */ +bool QJSManagedValue::deleteProperty(quint32 arrayIndex) +{ + if (!d) + return false; + + if (QV4::Object *obj = d->as<QV4::Object>()) + return obj->deleteProperty(QV4::PropertyKey::fromArrayIndex(arrayIndex)); + + return false; +} + +static const QV4::FunctionObject *functionObjectForCall(QV4::Value *d) +{ + if (Q_UNLIKELY(!d)) { + qWarning("QJSManagedValue: Calling a default-constructed or moved-from managed value" + "should throw an exception, but there is no engine to receive it."); + return nullptr; + } + + if (const QV4::FunctionObject *f = d->as<QV4::FunctionObject>()) + return f; + + v4Engine(d)->throwTypeError(QStringLiteral("Value is not a function")); + return nullptr; +} + +/*! + * 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 = functionObjectForCall(d); + if (!f) + return QJSValue(); + + QV4::ExecutionEngine *engine = f->engine(); + + QV4::Scope scope(engine); + QV4::JSCallArguments jsCallData(scope, arguments.size()); + *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 = functionObjectForCall(d); + 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::JSCallArguments jsCallData(scope, arguments.size()); + *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 = functionObjectForCall(d); + if (!f) + return QJSValue(); + + QV4::ExecutionEngine *engine = f->engine(); + + QV4::Scope scope(engine); + QV4::JSCallArguments jsCallData(scope, arguments.size()); + 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)); +} + +/*! + * \internal + * + * Retrieves the JavaScript meta type of this value. The JavaScript meta type + * represents the layout of members in an object. Instantiating a meta type is + * faster than re-constructing the same object using a sequence of setProperty() + * calls on a new object. + * + * \sa members(), instantiate() + */ +QJSManagedValue QJSManagedValue::jsMetaType() const +{ + if (!d) + return QJSManagedValue(); + + QJSManagedValue result(v4Engine(d)); + if (QV4::Managed *m = d->as<QV4::Managed>()) + *result.d = m->internalClass(); + + return result; +} + +/*! + * \internal + * + * If this value is a JavaScript meta type, retrieves the names of its members + * The ordering of the names corresponds to the ordering of the values to be + * passed to instantiate(). + * + * If the value is not a meta type, an empty list is returned. + * + * \sa isMetaType(), metaType(), instantiate() + */ +QStringList QJSManagedValue::jsMetaMembers() const +{ + if (!d) + return {}; + + if (QV4::InternalClass *c = d->as<QV4::InternalClass>()) { + const auto heapClass = c->d(); + const int size = heapClass->size; + QStringList result; + result.reserve(size); + QV4::Scope scope(c->engine()); + for (int i = 0; i < size; ++i) { + QV4::ScopedValue key(scope, heapClass->keyAt(i)); + result.append(key->toQString()); + } + return result; + } + + return {}; +} + +/*! + * \internal + * + * If this value is a JavaScript meta type, instantiates it using the + * \a values, and returns the result. Otherwise returns undefined. + * + * The values are expected in the same order as the keys in the return value of + * members(), and that is the order in which properties were added to the object + * this meta type originally belongs to. + * + * \sa members(), metaType(), isMetaType(). + */ +QJSManagedValue QJSManagedValue::jsMetaInstantiate(const QJSValueList &values) const +{ + if (!d) + return {}; + + if (QV4::InternalClass *c = d->as<QV4::InternalClass>()) { + QV4::ExecutionEngine *engine = c->engine(); + QJSManagedValue result(engine); + *result.d = c->engine()->newObject(c->d()); + QV4::Object *o = result.d->as<QV4::Object>(); + + for (uint i = 0, end = qMin(qsizetype(c->d()->size), values.size()); i < end; ++i) { + const QJSValue &arg = values[i]; + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { + qWarning("QJSManagedValue::instantiate() failed: " + "Argument was created in different engine."); + return QJSManagedValue(); + } + o->setProperty(i, QJSValuePrivate::convertToReturnedValue(engine, arg)); + } + + return result; + } + + return {}; +} + +QJSManagedValue::QJSManagedValue(QV4::ExecutionEngine *engine) : + d(engine->memoryManager->m_persistentValues->allocate()) +{ +} + +QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjsmanagedvalue.h b/src/qml/jsapi/qjsmanagedvalue.h new file mode 100644 index 0000000000..a6f67b0cc0 --- /dev/null +++ b/src/qml/jsapi/qjsmanagedvalue.h @@ -0,0 +1,130 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QJSMANAGEDVALUE_H +#define QJSMANAGEDVALUE_H + +#include <QtQml/qtqmlglobal.h> +#include <QtQml/qjsprimitivevalue.h> +#include <QtQml/qjsvalue.h> + +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, + Function + }; + + 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; } + bool isFunction() const { return type() == Function; } + + // 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 isJsMetaType() 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); + + // ### Qt 7 use qsizetype instead. + // 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; + + // JavaScript metatypes + QJSManagedValue jsMetaType() const; + QStringList jsMetaMembers() const; + QJSManagedValue jsMetaInstantiate(const QJSValueList &values = {}) 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.cpp b/src/qml/jsapi/qjsprimitivevalue.cpp new file mode 100644 index 0000000000..4bd418e082 --- /dev/null +++ b/src/qml/jsapi/qjsprimitivevalue.cpp @@ -0,0 +1,340 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qjsprimitivevalue.h" + +#include <QtQml/private/qv4runtime_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \since 6.1 + \class QJSPrimitiveUndefined + + \inmodule QtQml + + \brief An empty marker type to signify the JavaScript Undefined type and its single value. + \inmodule QtQml + */ + +/*! + \since 6.1 + \class QJSPrimitiveNull + + \inmodule QtQml + + \brief An empty marker type to signify the JavaScript null value. + \inmodule QtQml + */ + +/*! + \since 6.1 + \class QJSPrimitiveValue + + \brief The QJSPrimitiveValue class operates on primitive types in JavaScript semantics. + + \ingroup qtjavascript + \inmodule QtQml + + QJSPrimitiveValue supports most of the primitive types defined in the + \l{ECMA-262} standard, in particular Undefined, Boolean, Number, and String. + Additionally, you can store a JavaScript null in a QJSPrimitiveValue and as a + special case of Number, you can store an integer value. + + All those values are stored immediately, without interacting with the + JavaScript heap. Therefore, you can pass QJSPrimitiveValues between different + JavaScript engines. In contrast to QJSManagedValue, there is also no danger + in destroying a QJSPrimitiveValue from a different thread than it was created + in. On the flip side, QJSPrimitiveValue does not hold a reference to any + JavaScript engine. + + QJSPrimitiveValue implements the JavaScript arithmetic and comparison + operators on the supported types in JavaScript semantics. Types are coerced + like the JavaScript engine would coerce them if the operators were written + in a JavaScript expression. + + The JavaScript Symbol type is not supported as it is of very limited utility + regarding arithmetic and comparison operators, the main purpose of + QJSPrimitiveValue. In particular, it causes an exception whenever you try to + coerce it to a number or a string, and we cannot throw exceptions without a + JavaScript Engine. + */ + +/*! + \enum QJSPrimitiveValue::Type + + This enum speicifies the types a QJSPrimitiveValue might contain. + + \value Undefined The JavaScript Undefined value. + \value Null The JavaScript null value. This is in fact not a separate + JavaScript type but a special value of the Object type. As it is + very common and storable without JavaScript engine, it is still + supported. + \value Boolean A JavaScript Boolean value. + \value Integer An integer. This is a special case of the JavaScript Number + type. JavaScript does not have an actual integer type, but + the \l{ECMA-262} standard contains rules on how to transform a + Number in order to prepare it for certain operators that only + make sense on integers, in particular the bit shift operators. + QJSPrimitiveValue's Integer type represents the result of such + a transformation. + \value Double A JavaScript Number value. + \value String A JavaScript String value. + */ + +/*! + \fn Type QJSPrimitiveValue::type() const + + Returns the type of the QJSPrimitiveValue. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue() + + Creates a QJSPrimitiveValue of type Undefined. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(QJSPrimitiveUndefined undefined) + + Creates a QJSPrimitiveValue of value \a undefined and type Undefined. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(QJSPrimitiveNull null) + + Creates a QJSPrimitiveValue of value \a null and type Null. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(bool value) + + Creates a QJSPrimitiveValue of value \a value and type Boolean. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(int value) + + Creates a QJSPrimitiveValue of value \a value and type Integer. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(double value) + + Creates a QJSPrimitiveValue of value \a value and type Double. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(QString value) + + Creates a QJSPrimitiveValue of value \a value and type String. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(QMetaType type, const void *value) + \since 6.4 + + Creates a QJSPrimitiveValue of type \a type, and initializes with + \a value if \a type can be stored in QJSPrimtiveValue. \a value must not + be nullptr in that case. If \a type cannot be stored this results in a + QJSPrimitiveValue of type Undefined. + + Note that you have to pass the address of the variable you want stored. + + Usually, you never have to use this constructor, use the one taking QVariant + instead. + */ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(QMetaType type) + \since 6.6 + \internal + + Creates a QJSPrimitiveValue of type \a type, and initializes with a + default-constructed value if \a type can be stored in QJSPrimtiveValue. + If \a type cannot be stored this results in a QJSPrimitiveValue of type + Undefined. +*/ + +/*! + \fn QJSPrimitiveValue::QJSPrimitiveValue(const QVariant &value) + + Creates a QJSPrimitiveValue from the contents of \a value if those contents + can be stored in QJSPrimtiveValue. Otherwise this results in a + QJSPrimitiveValue of type Undefined. + */ + +/*! + \fn bool QJSPrimitiveValue::toBoolean() const + + Returns the value coerced a boolean by JavaScript rules. + */ + +/*! + \fn int QJSPrimitiveValue::toInteger() const + + Returns the value coerced to an integral 32bit number by the rules JavaScript + would apply when preparing it for a bit shift operation. + */ + +/*! + \fn double QJSPrimitiveValue::toDouble() const + + Returns the value coerced to a JavaScript Number by JavaScript rules. + */ + +/*! + \fn QString QJSPrimitiveValue::toString() const + + Returns the value coerced to a JavaScript String by JavaScript rules. + */ + +/*! + \fn QJSPrimitiveValue QJSPrimitiveValue::operator+(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + + \since 6.1 + + Perfoms the JavaScript '+' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn QJSPrimitiveValue QJSPrimitiveValue::operator-(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '-' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn QJSPrimitiveValue QJSPrimitiveValue::operator*(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '*' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn QJSPrimitiveValue QJSPrimitiveValue::operator/(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '/' operation between \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn bool QJSPrimitiveValue::strictlyEquals(const QJSPrimitiveValue &other) const + + Performs the JavaScript '===' operation on this QJSPrimitiveValue and + \a other, and returns the result. + */ + +/*! + \fn bool QJSPrimitiveValue::equals(const QJSPrimitiveValue &other) const + + Performs the JavaScript '==' operation on this QJSPrimitiveValue and + \a other, and returns the result. + */ + +/*! + \fn bool QJSPrimitiveValue::operator==(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '===' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn bool QJSPrimitiveValue::operator!=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '!==' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn bool QJSPrimitiveValue::operator<(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '<' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn bool QJSPrimitiveValue::operator>(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '>' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn bool QJSPrimitiveValue::operator<=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '<=' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn bool QJSPrimitiveValue::operator>=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + \since 6.1 + + Performs the JavaScript '>=' operation on \a lhs and \a rhs, and returns the + result. + */ + +/*! + \fn QMetaType QJSPrimitiveValue::metaType() const + \since 6.6 + + Returns the QMetaType of the value stored in the QJSPrimitiveValue. + */ + +/*! + \fn const void *QJSPrimitiveValue::constData() const + \fn const void *QJSPrimitiveValue::data() const + \since 6.6 + + Returns a pointer to the contained value as a generic void* that cannot be + written to. + */ + +/*! + \fn const void *QJSPrimitiveValue::data() + \since 6.6 + + Returns a pointer to the contained data as a generic void* that can be + written to. +*/ + +/*! + \fn template<QJSPrimitiveValue::Type type> QJSPrimitiveValue QJSPrimitiveValue::to() const + \since 6.6 + + Coerces the value to the specified \e type and returns the result as a new + QJSPrimitiveValue. + + \sa toBoolean(), toInteger(), toDouble(), toString() +*/ + +QString QJSPrimitiveValue::toString(double d) +{ + QString result; + QV4::RuntimeHelpers::numberToString(&result, d); + return result; +} + +/*! + \fn double QQmlPrivate::jsExponentiate(double base, double exponent) + \internal + \since 6.4 + + Performs JavaScript's Number::exponentiate operation on \a base and + \a exponent, and returns the result. + + See https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate + */ + +QT_END_NAMESPACE + diff --git a/src/qml/jsapi/qjsprimitivevalue.h b/src/qml/jsapi/qjsprimitivevalue.h new file mode 100644 index 0000000000..4ba3fd7dc3 --- /dev/null +++ b/src/qml/jsapi/qjsprimitivevalue.h @@ -0,0 +1,966 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QJSPRIMITIVEVALUE_H +#define QJSPRIMITIVEVALUE_H + +#include <QtQml/qtqmlglobal.h> +#include <QtQml/qjsnumbercoercion.h> + +#include <QtCore/qstring.h> +#include <QtCore/qnumeric.h> +#include <QtCore/qvariant.h> + +#include <variant> +#include <cmath> + +QT_BEGIN_NAMESPACE + +namespace QV4 { struct ExecutionEngine; } + +struct QJSPrimitiveUndefined {}; +struct QJSPrimitiveNull {}; + +class QJSPrimitiveValue +{ + template<typename Concrete> + struct StringNaNOperators + { + static constexpr double op(const QString &, QJSPrimitiveUndefined) + { + return std::numeric_limits<double>::quiet_NaN(); + } + + static constexpr double op(QJSPrimitiveUndefined, const QString &) + { + return std::numeric_limits<double>::quiet_NaN(); + } + + static double op(const QString &lhs, QJSPrimitiveNull) { return op(lhs, 0); } + static double op(QJSPrimitiveNull, const QString &rhs) { return op(0, rhs); } + + template<typename T> + static double op(const QString &lhs, T rhs) + { + return Concrete::op(fromString(lhs).toDouble(), rhs); + } + + template<typename T> + static double op(T lhs, const QString &rhs) + { + return Concrete::op(lhs, fromString(rhs).toDouble()); + } + + static double op(const QString &lhs, const QString &rhs) + { + return Concrete::op(fromString(lhs).toDouble(), fromString(rhs).toDouble()); + } + }; + + struct AddOperators { + static constexpr double op(double lhs, double rhs) { return lhs + rhs; } + static bool opOverflow(int lhs, int rhs, int *result) + { + return qAddOverflow(lhs, rhs, result); + } + + template<typename T> + static QString op(const QString &lhs, T rhs) + { + return lhs + QJSPrimitiveValue(rhs).toString(); + } + + template<typename T> + static QString op(T lhs, const QString &rhs) + { + return QJSPrimitiveValue(lhs).toString() + rhs; + } + + static QString op(const QString &lhs, const QString &rhs) { return lhs + rhs; } + }; + + struct SubOperators : private StringNaNOperators<SubOperators> { + static constexpr double op(double lhs, double rhs) { return lhs - rhs; } + static bool opOverflow(int lhs, int rhs, int *result) + { + return qSubOverflow(lhs, rhs, result); + } + + using StringNaNOperators::op; + }; + + struct MulOperators : private StringNaNOperators<MulOperators> { + static constexpr double op(double lhs, double rhs) { return lhs * rhs; } + static bool opOverflow(int lhs, int rhs, int *result) + { + // compare mul_int32 in qv4math_p.h + auto hadOverflow = qMulOverflow(lhs, rhs, result); + if (((lhs < 0) ^ (rhs < 0)) && (*result == 0)) + return true; // result must be negative 0, does not fit into int + return hadOverflow; + } + + using StringNaNOperators::op; + }; + + struct DivOperators : private StringNaNOperators<DivOperators> { + static constexpr double op(double lhs, double rhs) { return lhs / rhs; } + static constexpr bool opOverflow(int, int, int *) + { + return true; + } + + using StringNaNOperators::op; + }; + +public: + enum Type : quint8 { + Undefined, + Null, + Boolean, + Integer, + Double, + String + }; + + constexpr Type type() const { return Type(d.type()); } + + // Prevent casting from Type to int + QJSPrimitiveValue(Type) = delete; + + Q_IMPLICIT constexpr QJSPrimitiveValue() noexcept = default; + Q_IMPLICIT constexpr QJSPrimitiveValue(QJSPrimitiveUndefined undefined) noexcept : d(undefined) {} + Q_IMPLICIT constexpr QJSPrimitiveValue(QJSPrimitiveNull null) noexcept : d(null) {} + Q_IMPLICIT constexpr QJSPrimitiveValue(bool value) noexcept : d(value) {} + Q_IMPLICIT constexpr QJSPrimitiveValue(int value) noexcept : d(value) {} + Q_IMPLICIT constexpr QJSPrimitiveValue(double value) noexcept : d(value) {} + Q_IMPLICIT QJSPrimitiveValue(QString string) noexcept : d(std::move(string)) {} + + explicit QJSPrimitiveValue(const QMetaType type, const void *value) noexcept + { + switch (type.id()) { + case QMetaType::UnknownType: + d = QJSPrimitiveUndefined(); + break; + case QMetaType::Nullptr: + d = QJSPrimitiveNull(); + break; + case QMetaType::Bool: + d = *static_cast<const bool *>(value); + break; + case QMetaType::Int: + d = *static_cast<const int *>(value); + break; + case QMetaType::Double: + d = *static_cast<const double *>(value); + break; + case QMetaType::QString: + d = *static_cast<const QString *>(value); + break; + default: + // Unsupported. Remains undefined. + break; + } + } + + explicit QJSPrimitiveValue(QMetaType type) noexcept + { + switch (type.id()) { + case QMetaType::UnknownType: + d = QJSPrimitiveUndefined(); + break; + case QMetaType::Nullptr: + d = QJSPrimitiveNull(); + break; + case QMetaType::Bool: + d = false; + break; + case QMetaType::Int: + d = 0; + break; + case QMetaType::Double: + d = 0.0; + break; + case QMetaType::QString: + d = QString(); + break; + default: + // Unsupported. Remains undefined. + break; + } + } + + explicit QJSPrimitiveValue(const QVariant &variant) noexcept + : QJSPrimitiveValue(variant.metaType(), variant.data()) + { + } + + constexpr QMetaType metaType() const { return d.metaType(); } + constexpr void *data() { return d.data(); } + constexpr const void *data() const { return d.data(); } + constexpr const void *constData() const { return d.data(); } + + template<Type type> + QJSPrimitiveValue to() const { + if constexpr (type == Undefined) + return QJSPrimitiveUndefined(); + if constexpr (type == Null) + return QJSPrimitiveNull(); + if constexpr (type == Boolean) + return toBoolean(); + if constexpr (type == Integer) + return toInteger(); + if constexpr (type == Double) + return toDouble(); + if constexpr (type == String) + return toString(); + + Q_UNREACHABLE_RETURN(QJSPrimitiveUndefined()); + } + + constexpr bool toBoolean() const + { + switch (type()) { + case Undefined: return false; + case Null: return false; + case Boolean: return asBoolean(); + case Integer: return asInteger() != 0; + case Double: { + const double v = asDouble(); + return !QJSNumberCoercion::equals(v, 0) && !std::isnan(v); + } + case String: return !asString().isEmpty(); + } + + // GCC 8.x does not treat __builtin_unreachable() as constexpr + #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE_RETURN(false); + #else + return false; + #endif + } + + constexpr int toInteger() const + { + switch (type()) { + case Undefined: return 0; + case Null: return 0; + case Boolean: return asBoolean(); + case Integer: return asInteger(); + case Double: return QJSNumberCoercion::toInteger(asDouble()); + case String: return fromString(asString()).toInteger(); + } + + // GCC 8.x does not treat __builtin_unreachable() as constexpr + #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE_RETURN(0); + #else + return 0; + #endif + } + + constexpr double toDouble() const + { + switch (type()) { + case Undefined: return std::numeric_limits<double>::quiet_NaN(); + case Null: return 0; + case Boolean: return asBoolean(); + case Integer: return asInteger(); + case Double: return asDouble(); + case String: return fromString(asString()).toDouble(); + } + + // GCC 8.x does not treat __builtin_unreachable() as constexpr + #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE_RETURN({}); + #else + return {}; + #endif + } + + QString toString() const + { + switch (type()) { + case Undefined: return QStringLiteral("undefined"); + case Null: return QStringLiteral("null"); + case Boolean: return asBoolean() ? QStringLiteral("true") : QStringLiteral("false"); + case Integer: return QString::number(asInteger()); + case Double: { + const double result = asDouble(); + if (std::isnan(result)) + return QStringLiteral("NaN"); + if (std::isfinite(result)) + return toString(result); + if (result > 0) + return QStringLiteral("Infinity"); + return QStringLiteral("-Infinity"); + } + case String: return asString(); + } + + Q_UNREACHABLE_RETURN(QString()); + } + + QVariant toVariant() const + { + switch (type()) { + case Undefined: return QVariant(); + case Null: return QVariant::fromValue<std::nullptr_t>(nullptr); + case Boolean: return QVariant(asBoolean()); + case Integer: return QVariant(asInteger()); + case Double: return QVariant(asDouble()); + case String: return QVariant(asString()); + } + + Q_UNREACHABLE_RETURN(QVariant()); + } + + friend inline QJSPrimitiveValue operator+(const QJSPrimitiveValue &lhs, + const QJSPrimitiveValue &rhs) + { + return operate<AddOperators>(lhs, rhs); + } + + friend inline QJSPrimitiveValue operator-(const QJSPrimitiveValue &lhs, + const QJSPrimitiveValue &rhs) + { + return operate<SubOperators>(lhs, rhs); + } + + friend inline QJSPrimitiveValue operator*(const QJSPrimitiveValue &lhs, + const QJSPrimitiveValue &rhs) + { + return operate<MulOperators>(lhs, rhs); + } + + friend inline QJSPrimitiveValue operator/(const QJSPrimitiveValue &lhs, + const QJSPrimitiveValue &rhs) + { + return operate<DivOperators>(lhs, rhs); + } + + friend inline QJSPrimitiveValue operator%(const QJSPrimitiveValue &lhs, + const QJSPrimitiveValue &rhs) + { + switch (lhs.type()) { + case Null: + case Boolean: + case Integer: + switch (rhs.type()) { + case Boolean: + case Integer: { + const int leftInt = lhs.toInteger(); + const int rightInt = rhs.toInteger(); + if (leftInt >= 0 && rightInt > 0) + return leftInt % rightInt; + Q_FALLTHROUGH(); + } + default: + break; + } + Q_FALLTHROUGH(); + default: + break; + } + + return std::fmod(lhs.toDouble(), rhs.toDouble()); + } + + QJSPrimitiveValue &operator++() + { + // ++a is modeled as a -= (-1) to avoid the potential string concatenation + return (*this = operate<SubOperators>(*this, -1)); + } + + QJSPrimitiveValue operator++(int) + { + // a++ is modeled as a -= (-1) to avoid the potential string concatenation + QJSPrimitiveValue other = operate<SubOperators>(*this, -1); + std::swap(other, *this); + return +other; // We still need to coerce the original value. + } + + QJSPrimitiveValue &operator--() + { + return (*this = operate<SubOperators>(*this, 1)); + } + + QJSPrimitiveValue operator--(int) + { + QJSPrimitiveValue other = operate<SubOperators>(*this, 1); + std::swap(other, *this); + return +other; // We still need to coerce the original value. + } + + QJSPrimitiveValue operator+() + { + // +a is modeled as a -= 0. That should force it to number. + return (*this = operate<SubOperators>(*this, 0)); + } + + QJSPrimitiveValue operator-() + { + return (*this = operate<MulOperators>(*this, -1)); + } + + constexpr bool strictlyEquals(const QJSPrimitiveValue &other) const + { + const Type myType = type(); + const Type otherType = other.type(); + + if (myType != otherType) { + // int -> double promotion is OK in strict mode + if (myType == Double && otherType == Integer) + return strictlyEquals(double(other.asInteger())); + if (myType == Integer && otherType == Double) + return QJSPrimitiveValue(double(asInteger())).strictlyEquals(other); + return false; + } + + switch (myType) { + case Undefined: + case Null: + return true; + case Boolean: + return asBoolean() == other.asBoolean(); + case Integer: + return asInteger() == other.asInteger(); + case Double: { + const double l = asDouble(); + const double r = other.asDouble(); + if (std::isnan(l) || std::isnan(r)) + return false; + if (qIsNull(l) && qIsNull(r)) + return true; + return QJSNumberCoercion::equals(l, r); + } + case String: + return asString() == other.asString(); + } + + return false; + } + + // Loose operator==, in contrast to strict === + constexpr bool equals(const QJSPrimitiveValue &other) const + { + const Type myType = type(); + const Type otherType = other.type(); + + if (myType == otherType) + return strictlyEquals(other); + + switch (myType) { + case Undefined: + return otherType == Null; + case Null: + return otherType == Undefined; + case Boolean: + return QJSPrimitiveValue(int(asBoolean())).equals(other); + case Integer: + // prefer rhs bool -> int promotion over promoting both to double + return otherType == Boolean + ? QJSPrimitiveValue(asInteger()).equals(int(other.asBoolean())) + : QJSPrimitiveValue(double(asInteger())).equals(other); + case Double: + // Promote the other side to double (or recognize lhs as undefined/null) + return other.equals(*this); + case String: + return fromString(asString()).parsedEquals(other); + } + + return false; + } + + friend constexpr inline bool operator==(const QJSPrimitiveValue &lhs, const + QJSPrimitiveValue &rhs) + { + return lhs.strictlyEquals(rhs); + } + + friend constexpr inline bool operator!=(const QJSPrimitiveValue &lhs, + const QJSPrimitiveValue &rhs) + { + return !lhs.strictlyEquals(rhs); + } + + friend constexpr inline bool operator<(const QJSPrimitiveValue &lhs, + const QJSPrimitiveValue &rhs) + { + switch (lhs.type()) { + case Undefined: + return false; + case Null: { + switch (rhs.type()) { + case Undefined: return false; + case Null: return false; + case Boolean: return 0 < int(rhs.asBoolean()); + case Integer: return 0 < rhs.asInteger(); + case Double: return double(0) < rhs.asDouble(); + case String: return double(0) < rhs.toDouble(); + } + break; + } + case Boolean: { + switch (rhs.type()) { + case Undefined: return false; + case Null: return int(lhs.asBoolean()) < 0; + case Boolean: return lhs.asBoolean() < rhs.asBoolean(); + case Integer: return int(lhs.asBoolean()) < rhs.asInteger(); + case Double: return double(lhs.asBoolean()) < rhs.asDouble(); + case String: return double(lhs.asBoolean()) < rhs.toDouble(); + } + break; + } + case Integer: { + switch (rhs.type()) { + case Undefined: return false; + case Null: return lhs.asInteger() < 0; + case Boolean: return lhs.asInteger() < int(rhs.asBoolean()); + case Integer: return lhs.asInteger() < rhs.asInteger(); + case Double: return double(lhs.asInteger()) < rhs.asDouble(); + case String: return double(lhs.asInteger()) < rhs.toDouble(); + } + break; + } + case Double: { + switch (rhs.type()) { + case Undefined: return false; + case Null: return lhs.asDouble() < double(0); + case Boolean: return lhs.asDouble() < double(rhs.asBoolean()); + case Integer: return lhs.asDouble() < double(rhs.asInteger()); + case Double: return lhs.asDouble() < rhs.asDouble(); + case String: return lhs.asDouble() < rhs.toDouble(); + } + break; + } + case String: { + switch (rhs.type()) { + case Undefined: return false; + case Null: return lhs.toDouble() < double(0); + case Boolean: return lhs.toDouble() < double(rhs.asBoolean()); + case Integer: return lhs.toDouble() < double(rhs.asInteger()); + case Double: return lhs.toDouble() < rhs.asDouble(); + case String: return lhs.asString() < rhs.asString(); + } + break; + } + } + + return false; + } + + friend constexpr inline bool operator>(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + { + return rhs < lhs; + } + + friend constexpr inline bool operator<=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + { + if (lhs.type() == String) { + if (rhs.type() == String) + return lhs.asString() <= rhs.asString(); + else + return fromString(lhs.asString()) <= rhs; + } + if (rhs.type() == String) + return lhs <= fromString(rhs.asString()); + + if (lhs.isNanOrUndefined() || rhs.isNanOrUndefined()) + return false; + return !(lhs > rhs); + } + + friend constexpr inline bool operator>=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + { + if (lhs.type() == String) { + if (rhs.type() == String) + return lhs.asString() >= rhs.asString(); + else + return fromString(lhs.asString()) >= rhs; + } + if (rhs.type() == String) + return lhs >= fromString(rhs.asString()); + + if (lhs.isNanOrUndefined() || rhs.isNanOrUndefined()) + return false; + return !(lhs < rhs); + } + +private: + friend class QJSManagedValue; + friend class QJSValue; + friend struct QV4::ExecutionEngine; + + constexpr bool asBoolean() const { return d.getBool(); } + constexpr int asInteger() const { return d.getInt(); } + constexpr double asDouble() const { return d.getDouble(); } + QString asString() const { return d.getString(); } + + constexpr bool parsedEquals(const QJSPrimitiveValue &other) const + { + return type() != Undefined && equals(other); + } + + static QJSPrimitiveValue fromString(const QString &string) + { + bool ok; + const int intValue = string.toInt(&ok); + if (ok) + return intValue; + + const double doubleValue = string.toDouble(&ok); + if (ok) + return doubleValue; + if (string == QStringLiteral("Infinity")) + return std::numeric_limits<double>::infinity(); + if (string == QStringLiteral("-Infinity")) + return -std::numeric_limits<double>::infinity(); + if (string == QStringLiteral("NaN")) + return std::numeric_limits<double>::quiet_NaN(); + return QJSPrimitiveUndefined(); + } + + static Q_QML_EXPORT QString toString(double d); + + template<typename Operators, typename Lhs, typename Rhs> + static QJSPrimitiveValue operateOnIntegers(const QJSPrimitiveValue &lhs, + const QJSPrimitiveValue &rhs) + { + int result; + if (Operators::opOverflow(lhs.d.get<Lhs>(), rhs.d.get<Rhs>(), &result)) + return Operators::op(lhs.d.get<Lhs>(), rhs.d.get<Rhs>()); + return result; + } + + template<typename Operators> + static QJSPrimitiveValue operate(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs) + { + switch (lhs.type()) { + case Undefined: + switch (rhs.type()) { + case Undefined: return std::numeric_limits<double>::quiet_NaN(); + case Null: return std::numeric_limits<double>::quiet_NaN(); + case Boolean: return std::numeric_limits<double>::quiet_NaN(); + case Integer: return std::numeric_limits<double>::quiet_NaN(); + case Double: return std::numeric_limits<double>::quiet_NaN(); + case String: return Operators::op(QJSPrimitiveUndefined(), rhs.asString()); + } + break; + case Null: + switch (rhs.type()) { + case Undefined: return std::numeric_limits<double>::quiet_NaN(); + case Null: return operateOnIntegers<Operators, int, int>(0, 0); + case Boolean: return operateOnIntegers<Operators, int, bool>(0, rhs); + case Integer: return operateOnIntegers<Operators, int, int>(0, rhs); + case Double: return Operators::op(0, rhs.asDouble()); + case String: return Operators::op(QJSPrimitiveNull(), rhs.asString()); + } + break; + case Boolean: + switch (rhs.type()) { + case Undefined: return std::numeric_limits<double>::quiet_NaN(); + case Null: return operateOnIntegers<Operators, bool, int>(lhs, 0); + case Boolean: return operateOnIntegers<Operators, bool, bool>(lhs, rhs); + case Integer: return operateOnIntegers<Operators, bool, int>(lhs, rhs); + case Double: return Operators::op(lhs.asBoolean(), rhs.asDouble()); + case String: return Operators::op(lhs.asBoolean(), rhs.asString()); + } + break; + case Integer: + switch (rhs.type()) { + case Undefined: return std::numeric_limits<double>::quiet_NaN(); + case Null: return operateOnIntegers<Operators, int, int>(lhs, 0); + case Boolean: return operateOnIntegers<Operators, int, bool>(lhs, rhs); + case Integer: return operateOnIntegers<Operators, int, int>(lhs, rhs); + case Double: return Operators::op(lhs.asInteger(), rhs.asDouble()); + case String: return Operators::op(lhs.asInteger(), rhs.asString()); + } + break; + case Double: + switch (rhs.type()) { + case Undefined: return std::numeric_limits<double>::quiet_NaN(); + case Null: return Operators::op(lhs.asDouble(), 0); + case Boolean: return Operators::op(lhs.asDouble(), rhs.asBoolean()); + case Integer: return Operators::op(lhs.asDouble(), rhs.asInteger()); + case Double: return Operators::op(lhs.asDouble(), rhs.asDouble()); + case String: return Operators::op(lhs.asDouble(), rhs.asString()); + } + break; + case String: + switch (rhs.type()) { + case Undefined: return Operators::op(lhs.asString(), QJSPrimitiveUndefined()); + case Null: return Operators::op(lhs.asString(), QJSPrimitiveNull()); + case Boolean: return Operators::op(lhs.asString(), rhs.asBoolean()); + case Integer: return Operators::op(lhs.asString(), rhs.asInteger()); + case Double: return Operators::op(lhs.asString(), rhs.asDouble()); + case String: return Operators::op(lhs.asString(), rhs.asString()); + } + break; + } + + Q_UNREACHABLE_RETURN(QJSPrimitiveUndefined()); + } + + constexpr bool isNanOrUndefined() const + { + switch (type()) { + case Undefined: return true; + case Double: return std::isnan(asDouble()); + default: return false; + } + } + + struct QJSPrimitiveValuePrivate + { + // Can't be default because QString has a non-trivial ctor. + constexpr QJSPrimitiveValuePrivate() noexcept {} + + Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(QJSPrimitiveUndefined) noexcept {} + Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(QJSPrimitiveNull) noexcept + : m_type(Null) {} + Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(bool b) noexcept + : m_bool(b), m_type(Boolean) {} + Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(int i) noexcept + : m_int(i), m_type(Integer) {} + Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(double d) noexcept + : m_double(d), m_type(Double) {} + Q_IMPLICIT QJSPrimitiveValuePrivate(QString s) noexcept + : m_string(std::move(s)), m_type(String) {} + + constexpr QJSPrimitiveValuePrivate(const QJSPrimitiveValuePrivate &other) noexcept + : m_type(other.m_type) + { + // Not copy-and-swap since swap() would be much more complicated. + if (!assignSimple(other)) + new (&m_string) QString(other.m_string); + } + + constexpr QJSPrimitiveValuePrivate(QJSPrimitiveValuePrivate &&other) noexcept + : m_type(other.m_type) + { + // Not move-and-swap since swap() would be much more complicated. + if (!assignSimple(other)) + new (&m_string) QString(std::move(other.m_string)); + } + + constexpr QJSPrimitiveValuePrivate &operator=(const QJSPrimitiveValuePrivate &other) noexcept + { + if (this == &other) + return *this; + + if (m_type == String) { + if (other.m_type == String) { + m_type = other.m_type; + m_string = other.m_string; + return *this; + } + m_string.~QString(); + } + + m_type = other.m_type; + if (!assignSimple(other)) + new (&m_string) QString(other.m_string); + return *this; + } + + constexpr QJSPrimitiveValuePrivate &operator=(QJSPrimitiveValuePrivate &&other) noexcept + { + if (this == &other) + return *this; + + if (m_type == String) { + if (other.m_type == String) { + m_type = other.m_type; + m_string = std::move(other.m_string); + return *this; + } + m_string.~QString(); + } + + m_type = other.m_type; + if (!assignSimple(other)) + new (&m_string) QString(std::move(other.m_string)); + return *this; + } + + ~QJSPrimitiveValuePrivate() + { + if (m_type == String) + m_string.~QString(); + } + + constexpr Type type() const noexcept { return m_type; } + constexpr bool getBool() const noexcept { return m_bool; } + constexpr int getInt() const noexcept { return m_int; } + constexpr double getDouble() const noexcept { return m_double; } + QString getString() const noexcept { return m_string; } + + template<typename T> + constexpr T get() const noexcept { + if constexpr (std::is_same_v<T, QJSPrimitiveUndefined>) + return QJSPrimitiveUndefined(); + else if constexpr (std::is_same_v<T, QJSPrimitiveNull>) + return QJSPrimitiveNull(); + else if constexpr (std::is_same_v<T, bool>) + return getBool(); + else if constexpr (std::is_same_v<T, int>) + return getInt(); + else if constexpr (std::is_same_v<T, double>) + return getDouble(); + else if constexpr (std::is_same_v<T, QString>) + return getString(); + + // GCC 8.x does not treat __builtin_unreachable() as constexpr + #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE_RETURN(T()); + #else + return T(); + #endif + } + + constexpr QMetaType metaType() const noexcept { + switch (m_type) { + case Undefined: + return QMetaType(); + case Null: + return QMetaType::fromType<std::nullptr_t>(); + case Boolean: + return QMetaType::fromType<bool>(); + case Integer: + return QMetaType::fromType<int>(); + case Double: + return QMetaType::fromType<double>(); + case String: + return QMetaType::fromType<QString>(); + } + + // GCC 8.x does not treat __builtin_unreachable() as constexpr + #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE_RETURN(QMetaType()); + #else + return QMetaType(); + #endif + } + + constexpr void *data() noexcept { + switch (m_type) { + case Undefined: + case Null: + return nullptr; + case Boolean: + return &m_bool; + case Integer: + return &m_int; + case Double: + return &m_double; + case String: + return &m_string; + } + + // GCC 8.x does not treat __builtin_unreachable() as constexpr + #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE_RETURN(nullptr); + #else + return nullptr; + #endif + } + + constexpr const void *data() const noexcept { + switch (m_type) { + case Undefined: + case Null: + return nullptr; + case Boolean: + return &m_bool; + case Integer: + return &m_int; + case Double: + return &m_double; + case String: + return &m_string; + } + + // GCC 8.x does not treat __builtin_unreachable() as constexpr + #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE_RETURN(nullptr); + #else + return nullptr; + #endif + } + + private: + constexpr bool assignSimple(const QJSPrimitiveValuePrivate &other) noexcept + { + switch (other.m_type) { + case Undefined: + case Null: + return true; + case Boolean: + m_bool = other.m_bool; + return true; + case Integer: + m_int = other.m_int; + return true; + case Double: + m_double = other.m_double; + return true; + case String: + return false; + } + + // GCC 8.x does not treat __builtin_unreachable() as constexpr + #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE_RETURN(false); + #else + return false; + #endif + } + + union { + bool m_bool = false; + int m_int; + double m_double; + QString m_string; + }; + + Type m_type = Undefined; + }; + + QJSPrimitiveValuePrivate d; +}; + +namespace QQmlPrivate { + // TODO: Make this constexpr once std::isnan is constexpr. + inline double jsExponentiate(double base, double exponent) + { + constexpr double qNaN = std::numeric_limits<double>::quiet_NaN(); + constexpr double inf = std::numeric_limits<double>::infinity(); + + if (qIsNull(exponent)) + return 1.0; + + if (std::isnan(exponent)) + return qNaN; + + if (QJSNumberCoercion::equals(base, 1.0) || QJSNumberCoercion::equals(base, -1.0)) + return std::isinf(exponent) ? qNaN : std::pow(base, exponent); + + if (!qIsNull(base)) + return std::pow(base, exponent); + + if (std::copysign(1.0, base) > 0.0) + return exponent < 0.0 ? inf : std::pow(base, exponent); + + if (exponent < 0.0) + return QJSNumberCoercion::equals(std::fmod(-exponent, 2.0), 1.0) ? -inf : inf; + + return QJSNumberCoercion::equals(std::fmod(exponent, 2.0), 1.0) + ? std::copysign(0, -1.0) + : 0.0; + } +} + +QT_END_NAMESPACE + +#endif // QJSPRIMITIVEVALUE_H diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index c2957dd294..f6b97262c3 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -1,47 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <QtCore/qstring.h> #include <QtCore/qvarlengtharray.h> #include <QtCore/qdatetime.h> -#include "qjsengine.h" #include "qjsvalue.h" +#include "qjsprimitivevalue.h" +#include "qjsmanagedvalue.h" #include "qjsvalue_p.h" #include "qv4value_p.h" #include "qv4object_p.h" @@ -54,6 +19,9 @@ #include <private/qv4mm_p.h> #include <private/qv4jscall_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qv4qmetaobjectwrapper_p.h> +#include <private/qv4urlobject_p.h> +#include <private/qqmlbuiltins_p.h> /*! \since 5.0 @@ -139,6 +107,16 @@ integers.append(jsArray.property(i).toInt()); } \endcode + + \section2 Converting to JSON + + It's possible to convert a QJSValue to a JSON type. For example, + to convert to an array, use \l QJSEngine::fromScriptValue(): + + \code + const QJsonValue jsonValue = engine.fromScriptValue<QJsonValue>(jsValue); + const QJsonArray jsonArray = jsonValue.toArray(); + \endcode */ /*! @@ -184,6 +162,18 @@ provided is malformed. */ +/*! + \enum QJSValue::ObjectConversionBehavior + + This enum is used to specify how JavaScript objects and symbols without an equivalent + native Qt type should be treated when converting to QVariant. + + \value ConvertJSObjects A best-effort, possibly lossy, conversion is attempted. + Symbols are converted to QString. + + \value RetainJSObjects The value is retained as QJSValue wrapped in QVariant. +*/ + QT_BEGIN_NAMESPACE using namespace QV4; @@ -191,76 +181,59 @@ using namespace QV4; /*! Constructs a new QJSValue with a boolean \a value. */ -QJSValue::QJSValue(bool value) +QJSValue::QJSValue(bool value) : d(QJSValuePrivate::encode(value)) { - QJSValuePrivate::setVariant(this, QVariant(value)); -} - -/*! - \internal -*/ -QJSValue::QJSValue(ExecutionEngine *e, quint64 val) -{ - QJSValuePrivate::setValue(this, e, val); } /*! Constructs a new QJSValue with a number \a value. */ -QJSValue::QJSValue(int value) +QJSValue::QJSValue(int value) : d(QJSValuePrivate::encode(value)) { - QJSValuePrivate::setVariant(this, QVariant(value)); } /*! Constructs a new QJSValue with a number \a value. */ -QJSValue::QJSValue(uint value) +QJSValue::QJSValue(uint value) : d(QJSValuePrivate::encode(value)) { - QJSValuePrivate::setVariant(this, QVariant((double)value)); } /*! Constructs a new QJSValue with a number \a value. */ -QJSValue::QJSValue(double value) +QJSValue::QJSValue(double value) : d(QJSValuePrivate::encode(value)) { - QJSValuePrivate::setVariant(this, QVariant(value)); } /*! Constructs a new QJSValue with a string \a value. */ -QJSValue::QJSValue(const QString& value) +QJSValue::QJSValue(const QString &value) : d(QJSValuePrivate::encode(value)) { - QJSValuePrivate::setVariant(this, QVariant(value)); } /*! Constructs a new QJSValue with a special \a value. */ QJSValue::QJSValue(SpecialValue value) - : d(0) + : d(value == NullValue ? QJSValuePrivate::encodeNull() : QJSValuePrivate::encodeUndefined()) { - if (value == NullValue) - QJSValuePrivate::setVariant(this, QVariant::fromValue(nullptr)); } /*! Constructs a new QJSValue with a string \a value. */ -QJSValue::QJSValue(const QLatin1String &value) +QJSValue::QJSValue(const QLatin1String &value) : d(QJSValuePrivate::encode(value)) { - QJSValuePrivate::setVariant(this, QVariant(value)); } /*! Constructs a new QJSValue with a string \a value. */ #ifndef QT_NO_CAST_FROM_ASCII -QJSValue::QJSValue(const char *value) +QJSValue::QJSValue(const char *value) : d(QJSValuePrivate::encode(QString::fromUtf8(value))) { - QJSValuePrivate::setVariant(this, QVariant(QString::fromUtf8(value))); } #endif @@ -271,14 +244,23 @@ QJSValue::QJSValue(const char *value) true), then only a reference to the underlying object is copied into the new script value (i.e., the object itself is not copied). */ -QJSValue::QJSValue(const QJSValue& other) - : d(0) +QJSValue::QJSValue(const QJSValue &other) : d(other.d) { - QV4::Value *v = QJSValuePrivate::getValue(&other); - if (v) { - QJSValuePrivate::setValue(this, QJSValuePrivate::engine(&other), *v); - } else if (QVariant *v = QJSValuePrivate::getVariant(&other)) { - QJSValuePrivate::setVariant(this, *v); + switch (QJSValuePrivate::tag(d)) { + case QJSValuePrivate::Kind::Undefined: + case QJSValuePrivate::Kind::Null: + case QJSValuePrivate::Kind::IntValue: + case QJSValuePrivate::Kind::BoolValue: + return; + case QJSValuePrivate::Kind::DoublePtr: + d = QJSValuePrivate::encode(*QJSValuePrivate::doublePtr(d)); + return; + case QJSValuePrivate::Kind::QV4ValuePtr: + d = QJSValuePrivate::encode(*QJSValuePrivate::qv4ValuePtr(d)); + return; + case QJSValuePrivate::Kind::QStringPtr: + d = QJSValuePrivate::encode(*QJSValuePrivate::qStringPtr(d)); + break; } } @@ -310,11 +292,7 @@ QJSValue::~QJSValue() */ bool QJSValue::isBool() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (val) - return val->isBoolean(); - QVariant *variant = QJSValuePrivate::getVariant(this); - return variant && variant->type() == QVariant::Bool; + return QJSValuePrivate::tag(d) == QJSValuePrivate::Kind::BoolValue; } /*! @@ -325,27 +303,15 @@ bool QJSValue::isBool() const */ bool QJSValue::isNumber() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (val) - return val->isNumber(); - QVariant *variant = QJSValuePrivate::getVariant(this); - if (!variant) - return false; - - switch (variant->userType()) { - case QMetaType::Double: - case QMetaType::Int: - case QMetaType::UInt: - case QMetaType::Long: - case QMetaType::ULong: - case QMetaType::Short: - case QMetaType::UShort: - case QMetaType::LongLong: - case QMetaType::ULongLong: + switch (QJSValuePrivate::tag(d)) { + case QJSValuePrivate::Kind::IntValue: + case QJSValuePrivate::Kind::DoublePtr: return true; default: - return false; + break; } + + return false; } /*! @@ -354,14 +320,7 @@ bool QJSValue::isNumber() const */ bool QJSValue::isNull() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (val) - return val->isNull(); - QVariant *variant = QJSValuePrivate::getVariant(this); - if (!variant) - return false; - const int type = variant->userType(); - return type == QMetaType::Nullptr || type == QMetaType::VoidStar; + return QJSValuePrivate::tag(d) == QJSValuePrivate::Kind::Null; } /*! @@ -372,24 +331,35 @@ bool QJSValue::isNull() const */ bool QJSValue::isString() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (val) - return val->isString(); - QVariant *variant = QJSValuePrivate::getVariant(this); - return variant && variant->userType() == QMetaType::QString; + switch (QJSValuePrivate::tag(d)) { + case QJSValuePrivate::Kind::QStringPtr: + return true; + case QJSValuePrivate::Kind::QV4ValuePtr: { + return QJSValuePrivate::qv4ValuePtr(d)->isString(); + } + default: + break; + } + + return false; } /*! - Returns true if this QJSValue is of the primitive type Undefined; - otherwise returns false. + Returns true if this QJSValue is of the primitive type Undefined or if the managed value + has been cleared (by deleting the engine). Otherwise returns false. */ bool QJSValue::isUndefined() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (val) - return val->isUndefined(); - QVariant *variant = QJSValuePrivate::getVariant(this); - return !variant || variant->userType() == QMetaType::UnknownType || variant->userType() == QMetaType::Void; + switch (QJSValuePrivate::tag(d)) { + case QJSValuePrivate::Kind::Undefined: + return true; + case QJSValuePrivate::Kind::QV4ValuePtr: + return QJSValuePrivate::qv4ValuePtr(d)->isUndefined(); + default: + break; + } + + return false; } /*! @@ -400,10 +370,19 @@ bool QJSValue::isUndefined() const */ bool QJSValue::isError() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return false; - return val->as<ErrorObject>(); + return QJSValuePrivate::asManagedType<ErrorObject>(this); +} + +/*! + Returns true if this QJSValue is an object of the URL JavaScript class; + otherwise returns false. + + \note For a QJSValue that contains a QUrl, this function returns false. + However, \c{toVariant().value<QUrl>()} works in both cases. +*/ +bool QJSValue::isUrl() const +{ + return QJSValuePrivate::asManagedType<UrlObject>(this); } /*! @@ -415,10 +394,7 @@ bool QJSValue::isError() const */ QJSValue::ErrorType QJSValue::errorType() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return NoError; - QV4::ErrorObject *error = val->as<ErrorObject>(); + const QV4::ErrorObject *error = QJSValuePrivate::asManagedType<ErrorObject>(this); if (!error) return NoError; switch (error->d()->errorType) { @@ -437,8 +413,7 @@ QJSValue::ErrorType QJSValue::errorType() const case QV4::Heap::ErrorObject::URIError: return URIError; } - Q_UNREACHABLE(); - return NoError; + Q_UNREACHABLE_RETURN(NoError); } /*! @@ -449,10 +424,7 @@ QJSValue::ErrorType QJSValue::errorType() const */ bool QJSValue::isArray() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return false; - return val->as<ArrayObject>(); + return QJSValuePrivate::asManagedType<ArrayObject>(this); } /*! @@ -466,39 +438,46 @@ bool QJSValue::isArray() const */ bool QJSValue::isObject() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return false; - return val->as<QV4::Object>(); + return QJSValuePrivate::asManagedType<QV4::Object>(this); } /*! - Returns true if this QJSValue can be called a function, otherwise + Returns true if this QJSValue is a function, otherwise returns false. \sa call() */ bool QJSValue::isCallable() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return false; - return val->as<FunctionObject>(); + return QJSValuePrivate::asManagedType<FunctionObject>(this); } +#if QT_DEPRECATED_SINCE(6, 9) /*! + \deprecated [6.9] Returns true if this QJSValue is a variant value; otherwise returns false. + \warning This function is likely to give unexpected results. + A variant value is only constructed by the QJSEngine in a very + limited number of cases. This used to be different before Qt + 5.14, where \l{QJSEngine::toScriptValue} would have created + them for more types instead of corresponding ECMAScript types. + You can get a valid \l QVariant via \l toVariant for many values + for which \c{isVariant} returns false. + \sa toVariant() */ bool QJSValue::isVariant() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return false; - return val->as<QV4::VariantObject>(); + if (QJSValuePrivate::asManagedType<QV4::VariantObject>(this)) + return true; + if (auto vt = QJSValuePrivate::asManagedType<QV4::QQmlValueTypeWrapper>(this)) + if (vt->metaObject() == &QQmlVarForeign::staticMetaObject) + return true; + return false; } +#endif /*! Returns the string value of this QJSValue, as defined in @@ -514,27 +493,22 @@ bool QJSValue::isVariant() const */ QString QJSValue::toString() const { - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(this, &scratch); - - if (!val) { - QVariant *variant = QJSValuePrivate::getVariant(this); - Q_ASSERT(variant); - if (variant->type() == QVariant::Map) - return QStringLiteral("[object Object]"); - else if (variant->type() == QVariant::List) { - const QVariantList list = variant->toList(); - QString result; - for (int i = 0; i < list.count(); ++i) { - if (i > 0) - result.append(QLatin1Char(',')); - result.append(list.at(i).toString()); - } - return result; - } - return variant->toString(); + if (const QString *string = QJSValuePrivate::asQString(this)) + return *string; + + return QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(this)).toQStringNoThrow(); +} + +template<typename T> +T caughtResult(const QJSValue *v, T (QV4::Value::*convert)() const) +{ + const T result = (QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(v)).*convert)(); + QV4::ExecutionEngine *engine = QJSValuePrivate::engine(v); + if (engine && engine->hasException) { + engine->catchException(); + return T(); } - return val->toQStringNoThrow(); + return result; } /*! @@ -551,28 +525,10 @@ QString QJSValue::toString() const */ double QJSValue::toNumber() const { - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(this, &scratch); - - if (!val) { - QVariant *variant = QJSValuePrivate::getVariant(this); - Q_ASSERT(variant); - - if (variant->type() == QVariant::String) - return RuntimeHelpers::stringToNumber(variant->toString()); - else if (variant->canConvert<double>()) - return variant->value<double>(); - else - return std::numeric_limits<double>::quiet_NaN(); - } + if (const QString *string = QJSValuePrivate::asQString(this)) + return RuntimeHelpers::stringToNumber(*string); - double dbl = val->toNumber(); - QV4::ExecutionEngine *engine = QJSValuePrivate::engine(this); - if (engine && engine->hasException) { - engine->catchException(); - return 0; - } - return dbl; + return caughtResult<double>(this, &QV4::Value::toNumber); } /*! @@ -589,24 +545,10 @@ double QJSValue::toNumber() const */ bool QJSValue::toBool() const { - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(this, &scratch); - - if (!val) { - QVariant *variant = QJSValuePrivate::getVariant(this); - if (variant->userType() == QMetaType::QString) - return variant->toString().length() > 0; - else - return variant->toBool(); - } + if (const QString *string = QJSValuePrivate::asQString(this)) + return string->size() > 0; - bool b = val->toBoolean(); - QV4::ExecutionEngine *engine = QJSValuePrivate::engine(this); - if (engine && engine->hasException) { - engine->catchException(); - return false; - } - return b; + return caughtResult<bool>(this, &QV4::Value::toBoolean); } /*! @@ -623,24 +565,10 @@ bool QJSValue::toBool() const */ qint32 QJSValue::toInt() const { - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(this, &scratch); - - if (!val) { - QVariant *variant = QJSValuePrivate::getVariant(this); - if (variant->userType() == QMetaType::QString) - return QV4::Value::toInt32(RuntimeHelpers::stringToNumber(variant->toString())); - else - return variant->toInt(); - } + if (const QString *string = QJSValuePrivate::asQString(this)) + return QV4::Value::toInt32(RuntimeHelpers::stringToNumber(*string)); - qint32 i = val->toInt32(); - QV4::ExecutionEngine *engine = QJSValuePrivate::engine(this); - if (engine && engine->hasException) { - engine->catchException(); - return 0; - } - return i; + return caughtResult<qint32>(this, &QV4::Value::toInt32); } /*! @@ -657,30 +585,29 @@ qint32 QJSValue::toInt() const */ quint32 QJSValue::toUInt() const { - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(this, &scratch); + if (const QString *string = QJSValuePrivate::asQString(this)) + return QV4::Value::toUInt32(RuntimeHelpers::stringToNumber(*string)); - if (!val) { - QVariant *variant = QJSValuePrivate::getVariant(this); - if (variant->userType() == QMetaType::QString) - return QV4::Value::toUInt32(RuntimeHelpers::stringToNumber(variant->toString())); - else - return variant->toUInt(); - } + return caughtResult<quint32>(this, &QV4::Value::toUInt32); +} - quint32 u = val->toUInt32(); - QV4::ExecutionEngine *engine = QJSValuePrivate::engine(this); - if (engine && engine->hasException) { - engine->catchException(); - return 0; - } - return u; +/*! + \overload + + Returns toVariant(ConvertJSObjects). + + \sa isVariant() +*/ +QVariant QJSValue::toVariant() const +{ + return toVariant(ConvertJSObjects); } /*! - Returns the QVariant value of this QJSValue, if it can be - converted to a QVariant; otherwise returns an invalid QVariant. - The conversion is performed according to the following table: + Returns the QVariant value of this QJSValue, if it can be + converted to a QVariant; otherwise returns an invalid QVariant. + Some JavaScript types and objects have native expressions in Qt. + Those are converted to their native expressions. For example: \table \header \li Input Type \li Result @@ -692,42 +619,75 @@ quint32 QJSValue::toUInt() const \row \li QVariant Object \li The result is the QVariant value of the object (no conversion). \row \li QObject Object \li A QVariant containing a pointer to the QObject. \row \li Date Object \li A QVariant containing the date value (toDateTime()). - \row \li RegExp Object \li A QVariant containing the regular expression value. - \row \li Array Object \li The array is converted to a QVariantList. Each element is converted to a QVariant, recursively; cyclic references are not followed. - \row \li Object \li The object is converted to a QVariantMap. Each property is converted to a QVariant, recursively; cyclic references are not followed. + \row \li RegularExpression Object \li A QVariant containing the regular expression value. \endtable - \sa isVariant() + For other types the \a behavior parameter is relevant. If + \c ConvertJSObjects is given, a best effort but possibly lossy conversion is + attempted. Generic JavaScript objects are converted to QVariantMap. + JavaScript arrays are converted to QVariantList. Each property or element is + converted to a QVariant, recursively; cyclic references are not followed. + JavaScript function objects are dropped. If \c RetainJSObjects is given, the + QJSValue is wrapped into a QVariant via QVariant::fromValue(). The resulting + conversion is lossless but the internal structure of the objects is not + immediately accessible. + + \sa isVariant() */ -QVariant QJSValue::toVariant() const +QVariant QJSValue::toVariant(QJSValue::ObjectConversionBehavior behavior) const { - QVariant *variant = QJSValuePrivate::getVariant(this); - if (variant) - return *variant; - - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(this, &scratch); - Q_ASSERT(val); - - if (QV4::Object *o = val->as<QV4::Object>()) - return o->engine()->toVariant(*val, /*typeHint*/ -1, /*createJSValueForObjects*/ false); - - if (String *s = val->stringValue()) - return QVariant(s->toQString()); - if (val->isBoolean()) - return QVariant(val->booleanValue()); - if (val->isNumber()) { - if (val->isInt32()) - return QVariant(val->integerValue()); - return QVariant(val->asDouble()); + if (const QString *string = QJSValuePrivate::asQString(this)) + return QVariant(*string); + + QV4::Value val = QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(this)); + if (val.isUndefined()) + return QVariant(); + if (val.isNull()) + return QVariant(QMetaType::fromType<std::nullptr_t>(), nullptr); + if (val.isBoolean()) + return QVariant(val.booleanValue()); + if (val.isInt32()) // Includes doubles that can be losslessly casted to int + return QVariant(val.integerValue()); + if (val.isNumber()) + return QVariant(val.doubleValue()); + + Q_ASSERT(val.isManaged()); + + if (val.isString()) + return QVariant(val.toQString()); + if (val.as<QV4::Managed>()) { + if (behavior == RetainJSObjects) + return QV4::ExecutionEngine::toVariant( + val, /*typeHint*/ QMetaType{}, /*createJSValueForObjectsAndSymbols=*/ true); + else + return QV4::ExecutionEngine::toVariantLossy(val); } - if (val->isNull()) - return QVariant(QMetaType::Nullptr, nullptr); - Q_ASSERT(val->isUndefined()); + + Q_ASSERT(false); return QVariant(); } /*! + * Converts the value to a QJSPrimitiveValue. If the 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 QJSValue::toPrimitive() const +{ + if (const QString *string = QJSValuePrivate::asQString(this)) + return *string; + + const QV4::Value val = QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(this)); + return QV4::ExecutionEngine::createPrimitive(&val); +} + +/*! Calls this QJSValue as a function, passing \a args as arguments to the function, and using the globalObject() as the "this"-object. Returns the value returned from the function. @@ -742,13 +702,9 @@ QVariant QJSValue::toVariant() const \sa isCallable(), callWithInstance(), callAsConstructor() */ -QJSValue QJSValue::call(const QJSValueList &args) +QJSValue QJSValue::call(const QJSValueList &args) const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return QJSValue(); - - FunctionObject *f = val->as<FunctionObject>(); + const FunctionObject *f = QJSValuePrivate::asManagedType<FunctionObject>(this); if (!f) return QJSValue(); @@ -756,23 +712,23 @@ QJSValue QJSValue::call(const QJSValueList &args) Q_ASSERT(engine); Scope scope(engine); - JSCallData jsCallData(scope, args.length()); - *jsCallData->thisObject = engine->globalObject; + JSCallArguments jsCallData(scope, args.size()); + *jsCallData.thisObject = engine->globalObject; for (int i = 0; i < args.size(); ++i) { if (!QJSValuePrivate::checkEngine(engine, args.at(i))) { qWarning("QJSValue::call() failed: cannot call function with argument created in a different engine"); return QJSValue(); } - jsCallData->args[i] = QJSValuePrivate::convertedToValue(engine, args.at(i)); + jsCallData.args[i] = QJSValuePrivate::convertToReturnedValue(engine, args.at(i)); } ScopedValue result(scope, f->call(jsCallData)); if (engine->hasException) result = engine->catchException(); - if (engine->isInterrupted.loadAcquire()) + if (engine->isInterrupted.loadRelaxed()) result = engine->newErrorObject(QStringLiteral("Interrupted")); - return QJSValue(engine, result->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(result->asReturnedValue()); } /*! @@ -795,13 +751,9 @@ QJSValue QJSValue::call(const QJSValueList &args) \sa call() */ -QJSValue QJSValue::callWithInstance(const QJSValue &instance, const QJSValueList &args) +QJSValue QJSValue::callWithInstance(const QJSValue &instance, const QJSValueList &args) const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return QJSValue(); - - FunctionObject *f = val->as<FunctionObject>(); + const FunctionObject *f = QJSValuePrivate::asManagedType<FunctionObject>(this); if (!f) return QJSValue(); @@ -814,23 +766,23 @@ QJSValue QJSValue::callWithInstance(const QJSValue &instance, const QJSValueList return QJSValue(); } - JSCallData jsCallData(scope, args.size()); - *jsCallData->thisObject = QJSValuePrivate::convertedToValue(engine, instance); + JSCallArguments jsCallData(scope, args.size()); + *jsCallData.thisObject = QJSValuePrivate::convertToReturnedValue(engine, instance); for (int i = 0; i < args.size(); ++i) { if (!QJSValuePrivate::checkEngine(engine, args.at(i))) { qWarning("QJSValue::call() failed: cannot call function with argument created in a different engine"); return QJSValue(); } - jsCallData->args[i] = QJSValuePrivate::convertedToValue(engine, args.at(i)); + jsCallData.args[i] = QJSValuePrivate::convertToReturnedValue(engine, args.at(i)); } ScopedValue result(scope, f->call(jsCallData)); if (engine->hasException) result = engine->catchException(); - if (engine->isInterrupted.loadAcquire()) + if (engine->isInterrupted.loadRelaxed()) result = engine->newErrorObject(QStringLiteral("Interrupted")); - return QJSValue(engine, result->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(result->asReturnedValue()); } /*! @@ -851,13 +803,9 @@ QJSValue QJSValue::callWithInstance(const QJSValue &instance, const QJSValueList \sa call(), QJSEngine::newObject() */ -QJSValue QJSValue::callAsConstructor(const QJSValueList &args) +QJSValue QJSValue::callAsConstructor(const QJSValueList &args) const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (!val) - return QJSValue(); - - FunctionObject *f = val->as<FunctionObject>(); + const FunctionObject *f = QJSValuePrivate::asManagedType<FunctionObject>(this); if (!f) return QJSValue(); @@ -865,43 +813,24 @@ QJSValue QJSValue::callAsConstructor(const QJSValueList &args) Q_ASSERT(engine); Scope scope(engine); - JSCallData jsCallData(scope, args.size()); + JSCallArguments jsCallData(scope, args.size()); for (int i = 0; i < args.size(); ++i) { if (!QJSValuePrivate::checkEngine(engine, args.at(i))) { qWarning("QJSValue::callAsConstructor() failed: cannot construct function with argument created in a different engine"); return QJSValue(); } - jsCallData->args[i] = QJSValuePrivate::convertedToValue(engine, args.at(i)); + jsCallData.args[i] = QJSValuePrivate::convertToReturnedValue(engine, args.at(i)); } ScopedValue result(scope, f->callAsConstructor(jsCallData)); if (engine->hasException) result = engine->catchException(); - if (engine->isInterrupted.loadAcquire()) + if (engine->isInterrupted.loadRelaxed()) result = engine->newErrorObject(QStringLiteral("Interrupted")); - return QJSValue(engine, result->asReturnedValue()); -} - -#ifdef QT_DEPRECATED - -/*! - \obsolete - - Returns the QJSEngine that created this QJSValue, - or 0 if this QJSValue is invalid or the value is not - associated with a particular engine. -*/ -QJSEngine* QJSValue::engine() const -{ - QV4::ExecutionEngine *engine = QJSValuePrivate::engine(this); - if (engine) - return engine->jsEngine(); - return nullptr; + return QJSValuePrivate::fromReturnedValue(result->asReturnedValue()); } -#endif // QT_DEPRECATED - /*! If this QJSValue is an object, returns the internal prototype (\c{__proto__} property) of this object; otherwise returns an @@ -915,13 +844,13 @@ QJSValue QJSValue::prototype() const if (!engine) return QJSValue(); QV4::Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)->as<QV4::Object>()); + ScopedObject o(scope, QJSValuePrivate::asManagedType<QV4::Object>(this)); if (!o) return QJSValue(); ScopedObject p(scope, o->getPrototypeOf()); if (!p) return QJSValue(NullValue); - return QJSValue(o->internalClass()->engine, p.asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(p.asReturnedValue()); } /*! @@ -942,14 +871,11 @@ void QJSValue::setPrototype(const QJSValue& prototype) if (!engine) return; Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)); + ScopedObject o(scope, QJSValuePrivate::asReturnedValue(this)); if (!o) return; - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(&prototype, &scratch); - if (!val) - return; - if (val->isNull()) { + QV4::Value val = QV4::Value::fromReturnedValue(QJSValuePrivate::asReturnedValue(&prototype)); + if (val.isNull()) { o->setPrototypeOf(nullptr); return; } @@ -980,15 +906,55 @@ QJSValue& QJSValue::operator=(const QJSValue& other) QJSValuePrivate::free(this); d = 0; - QV4::Value *v = QJSValuePrivate::getValue(&other); - if (v) { - QJSValuePrivate::setValue(this, QJSValuePrivate::engine(&other), *v); - } else if (QVariant *v = QJSValuePrivate::getVariant(&other)) { - QJSValuePrivate::setVariant(this, *v); - } + if (const QString *string = QJSValuePrivate::asQString(&other)) + QJSValuePrivate::setString(this, *string); + else + QJSValuePrivate::setValue(this, QJSValuePrivate::asReturnedValue(&other)); + return *this; } +QJSValue::QJSValue(QJSPrimitiveValue &&value) +{ + switch (value.type()) { + case QJSPrimitiveValue::Undefined: + d = QJSValuePrivate::encodeUndefined(); + return; + case QJSPrimitiveValue::Null: + d = QJSValuePrivate::encodeNull(); + return; + case QJSPrimitiveValue::Boolean: + d = QJSValuePrivate::encode(value.asBoolean()); + return; + case QJSPrimitiveValue::Integer: + d = QJSValuePrivate::encode(value.asInteger()); + return; + case QJSPrimitiveValue::Double: + d = QJSValuePrivate::encode(value.asDouble()); + return; + case QJSPrimitiveValue::String: + d = QJSValuePrivate::encode(value.asString()); + return; + } + + Q_UNREACHABLE(); +} + +QJSValue::QJSValue(QJSManagedValue &&value) +{ + if (!value.d) { + d = QV4::Encode::undefined(); + } else if (value.d->isManaged()) { + // If it's managed, we can adopt the persistent value. + QJSValuePrivate::adoptPersistentValue(this, value.d); + value.d = nullptr; + } else { + d = QJSValuePrivate::encode(*value.d); + QV4::PersistentValueStorage::free(value.d); + value.d = nullptr; + } +} + static bool js_equal(const QString &string, const QV4::Value &value) { if (String *s = value.stringValue()) @@ -1031,23 +997,17 @@ static bool js_equal(const QString &string, const QV4::Value &value) */ bool QJSValue::equals(const QJSValue& other) const { - QV4::Value s1, s2; - QV4::Value *v = QJSValuePrivate::valueForData(this, &s1); - QV4::Value *ov = QJSValuePrivate::valueForData(&other, &s2); - - if (!v) { - QVariant *variant = QJSValuePrivate::getVariant(this); - Q_ASSERT(variant); - if (!ov) - return *variant == *QJSValuePrivate::getVariant(&other); - if (variant->type() == QVariant::Map || variant->type() == QVariant::List) - return false; - return js_equal(variant->toString(), *ov); - } - if (!ov) - return other.equals(*this); + if (const QString *string = QJSValuePrivate::asQString(this)) { + if (const QString *otherString = QJSValuePrivate::asQString(&other)) + return *string == *otherString; + return js_equal(*string, QJSValuePrivate::asReturnedValue(&other)); + } - return Runtime::CompareEqual::call(*v, *ov); + if (const QString *otherString = QJSValuePrivate::asQString(&other)) + return js_equal(*otherString, QJSValuePrivate::asReturnedValue(this)); + + return Runtime::CompareEqual::call(QJSValuePrivate::asReturnedValue(this), + QJSValuePrivate::asReturnedValue(&other)); } /*! @@ -1074,25 +1034,22 @@ bool QJSValue::equals(const QJSValue& other) const */ bool QJSValue::strictlyEquals(const QJSValue& other) const { - QV4::Value s1, s2; - QV4::Value *v = QJSValuePrivate::valueForData(this, &s1); - QV4::Value *ov = QJSValuePrivate::valueForData(&other, &s2); - - if (!v) { - QVariant *variant = QJSValuePrivate::getVariant(this); - Q_ASSERT(variant); - if (!ov) - return *variant == *QJSValuePrivate::getVariant(&other); - if (variant->type() == QVariant::Map || variant->type() == QVariant::List) - return false; - if (String *s = ov->stringValue()) - return variant->toString() == s->toQString(); + if (const QString *string = QJSValuePrivate::asQString(this)) { + if (const QString *otherString = QJSValuePrivate::asQString(&other)) + return *string == *otherString; + if (const String *s = QJSValuePrivate::asManagedType<String>(&other)) + return *string == s->toQString(); + return false; + } + + if (const QString *otherString = QJSValuePrivate::asQString(&other)) { + if (const String *s = QJSValuePrivate::asManagedType<String>(this)) + return *otherString == s->toQString(); return false; } - if (!ov) - return other.strictlyEquals(*this); - return RuntimeHelpers::strictEqual(*v, *ov); + return RuntimeHelpers::strictEqual(QJSValuePrivate::asReturnedValue(this), + QJSValuePrivate::asReturnedValue(&other)); } /*! @@ -1119,7 +1076,7 @@ QJSValue QJSValue::property(const QString& name) const return QJSValue(); QV4::Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)); + ScopedObject o(scope, QJSValuePrivate::asReturnedValue(this)); if (!o) return QJSValue(); @@ -1128,7 +1085,7 @@ QJSValue QJSValue::property(const QString& name) const if (engine->hasException) result = engine->catchException(); - return QJSValue(engine, result->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(result->asReturnedValue()); } /*! @@ -1167,14 +1124,14 @@ QJSValue QJSValue::property(quint32 arrayIndex) const return QJSValue(); QV4::Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)); + ScopedObject o(scope, QJSValuePrivate::asReturnedValue(this)); if (!o) return QJSValue(); QV4::ScopedValue result(scope, arrayIndex == UINT_MAX ? o->get(engine->id_uintMax()) : o->get(arrayIndex)); if (engine->hasException) engine->catchException(); - return QJSValue(engine, result->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(result->asReturnedValue()); } /*! @@ -1199,7 +1156,7 @@ void QJSValue::setProperty(const QString& name, const QJSValue& value) return; Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)); + ScopedObject o(scope, QJSValuePrivate::asReturnedValue(this)); if (!o) return; @@ -1209,7 +1166,7 @@ void QJSValue::setProperty(const QString& name, const QJSValue& value) } ScopedString s(scope, engine->newString(name)); - QV4::ScopedValue v(scope, QJSValuePrivate::convertedToValue(engine, value)); + QV4::ScopedValue v(scope, QJSValuePrivate::convertToReturnedValue(engine, value)); o->put(s->toPropertyKey(), v); if (engine->hasException) engine->catchException(); @@ -1253,7 +1210,7 @@ void QJSValue::setProperty(quint32 arrayIndex, const QJSValue& value) return; Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)); + ScopedObject o(scope, QJSValuePrivate::asReturnedValue(this)); if (!o) return; @@ -1262,7 +1219,7 @@ void QJSValue::setProperty(quint32 arrayIndex, const QJSValue& value) return; } - QV4::ScopedValue v(scope, QJSValuePrivate::convertedToValue(engine, value)); + QV4::ScopedValue v(scope, QJSValuePrivate::convertToReturnedValue(engine, value)); PropertyKey id = arrayIndex != UINT_MAX ? PropertyKey::fromArrayIndex(arrayIndex) : engine->id_uintMax()->propertyKey(); o->put(id, v); if (engine->hasException) @@ -1296,7 +1253,7 @@ bool QJSValue::deleteProperty(const QString &name) return false; Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)); + ScopedObject o(scope, QJSValuePrivate::asReturnedValue(this)); if (!o) return false; @@ -1317,7 +1274,7 @@ bool QJSValue::hasProperty(const QString &name) const return false; Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)); + ScopedObject o(scope, QJSValuePrivate::asReturnedValue(this)); if (!o) return false; @@ -1338,7 +1295,7 @@ bool QJSValue::hasOwnProperty(const QString &name) const return false; Scope scope(engine); - ScopedObject o(scope, QJSValuePrivate::getValue(this)); + ScopedObject o(scope, QJSValuePrivate::asReturnedValue(this)); if (!o) return false; @@ -1362,7 +1319,7 @@ QObject *QJSValue::toQObject() const if (!engine) return nullptr; QV4::Scope scope(engine); - QV4::Scoped<QV4::QObjectWrapper> wrapper(scope, QJSValuePrivate::getValue(this)); + QV4::Scoped<QV4::QObjectWrapper> wrapper(scope, QJSValuePrivate::asReturnedValue(this)); if (!wrapper) return nullptr; @@ -1383,7 +1340,7 @@ const QMetaObject *QJSValue::toQMetaObject() const if (!engine) return nullptr; QV4::Scope scope(engine); - QV4::Scoped<QV4::QMetaObjectWrapper> wrapper(scope, QJSValuePrivate::getValue(this)); + QV4::Scoped<QV4::QMetaObjectWrapper> wrapper(scope, QJSValuePrivate::asReturnedValue(this)); if (!wrapper) return nullptr; @@ -1400,12 +1357,8 @@ const QMetaObject *QJSValue::toQMetaObject() const */ QDateTime QJSValue::toDateTime() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - if (val) { - QV4::DateObject *date = val->as<DateObject>(); - if (date) - return date->toQDateTime(); - } + if (const QV4::DateObject *date = QJSValuePrivate::asManagedType<DateObject>(this)) + return date->toQDateTime(); return QDateTime(); } @@ -1415,8 +1368,7 @@ QDateTime QJSValue::toDateTime() const */ bool QJSValue::isDate() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - return val && val->as<DateObject>(); + return QJSValuePrivate::asManagedType<DateObject>(this); } /*! @@ -1425,8 +1377,7 @@ bool QJSValue::isDate() const */ bool QJSValue::isRegExp() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - return val && val->as<RegExpObject>(); + return QJSValuePrivate::asManagedType<RegExpObject>(this); } /*! @@ -1440,8 +1391,7 @@ bool QJSValue::isRegExp() const */ bool QJSValue::isQObject() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - return val && val->as<QV4::QObjectWrapper>() != nullptr; + return QJSValuePrivate::asManagedType<QV4::QObjectWrapper>(this); } /*! @@ -1454,8 +1404,72 @@ bool QJSValue::isQObject() const */ bool QJSValue::isQMetaObject() const { - QV4::Value *val = QJSValuePrivate::getValue(this); - return val && val->as<QV4::QMetaObjectWrapper>() != nullptr; + return QJSValuePrivate::asManagedType<QV4::QMetaObjectWrapper>(this); +} + +#ifndef QT_NO_DATASTREAM +QDataStream &operator<<(QDataStream &stream, const QJSValue &jsv) +{ + quint32 isNullOrUndefined = 0; + if (jsv.isNull()) + isNullOrUndefined |= 0x1; + if (jsv.isUndefined()) + isNullOrUndefined |= 0x2; + stream << isNullOrUndefined; + if (!isNullOrUndefined) { + const QVariant v = jsv.toVariant(); + switch (v.userType()) { + case QMetaType::Bool: + case QMetaType::Double: + case QMetaType::Int: + case QMetaType::QString: + v.save(stream); + break; + default: + qWarning() << "QDataStream::operator<< was to save a non-trivial QJSValue." + << "This is not supported anymore, please stream a QVariant instead."; + QVariant().save(stream); + break; + } + + } + return stream; +} + +QDataStream &operator>>(QDataStream &stream, QJSValue &jsv) +{ + quint32 isNullOrUndefined; + stream >> isNullOrUndefined; + + if (isNullOrUndefined & 0x1) { + jsv = QJSValue(QJSValue::NullValue); + } else if (isNullOrUndefined & 0x2) { + jsv = QJSValue(); + } else { + QVariant v; + v.load(stream); + + switch (v.userType()) { + case QMetaType::Bool: + jsv = QJSValue(v.toBool()); + break; + case QMetaType::Double: + jsv = QJSValue(v.toDouble()); + break; + case QMetaType::Int: + jsv = QJSValue(v.toInt()); + break; + case QMetaType::QString: + jsv = QJSValue(v.toString()); + break; + default: + qWarning() << "QDataStream::operator>> to restore a non-trivial QJSValue." + << "This is not supported anymore, please stream a QVariant instead."; + break; + } + } + return stream; } +#endif QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjsvalue.h b/src/qml/jsapi/qjsvalue.h index 2f95e0ff31..065f73f666 100644 --- a/src/qml/jsapi/qjsvalue.h +++ b/src/qml/jsapi/qjsvalue.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QJSVALUE_H #define QJSVALUE_H @@ -53,13 +17,16 @@ class QVariant; class QObject; struct QMetaObject; class QDateTime; +class QJSPrimitiveValue; typedef QList<QJSValue> QJSValueList; namespace QV4 { struct ExecutionEngine; - struct Value; } +class QJSPrimitiveValue; +class QJSManagedValue; + class Q_QML_EXPORT QJSValue { public: @@ -79,16 +46,19 @@ public: URIError }; + enum ObjectConversionBehavior { + ConvertJSObjects, + RetainJSObjects + }; + public: QJSValue(SpecialValue value = UndefinedValue); ~QJSValue(); QJSValue(const QJSValue &other); -#ifdef Q_COMPILER_RVALUE_REFS inline QJSValue(QJSValue && other) : d(other.d) { other.d = 0; } inline QJSValue &operator=(QJSValue &&other) - { qSwap(d, other.d); return *this; } -#endif + { std::swap(d, other.d); return *this; } QJSValue(bool value); QJSValue(int value); @@ -102,12 +72,18 @@ public: QJSValue &operator=(const QJSValue &other); + explicit QJSValue(QJSPrimitiveValue &&value); + explicit QJSValue(QJSManagedValue &&value); + bool isBool() const; bool isNumber() const; bool isNull() const; bool isString() const; bool isUndefined() const; +#if QT_DEPRECATED_SINCE(6, 9) + QT_DEPRECATED_VERSION_X_6_9("This might return unexpected results; consult documentation for more information") bool isVariant() const; +#endif bool isQObject() const; bool isQMetaObject() const; bool isObject() const; @@ -115,13 +91,18 @@ public: bool isRegExp() const; bool isArray() const; bool isError() const; + bool isUrl() const; QString toString() const; double toNumber() const; qint32 toInt() const; quint32 toUInt() const; bool toBool() const; + QVariant toVariant() const; + QVariant toVariant(ObjectConversionBehavior behavior) const; + QJSPrimitiveValue toPrimitive() const; + QObject *toQObject() const; const QMetaObject *toQMetaObject() const; QDateTime toDateTime() const; @@ -144,24 +125,25 @@ public: bool deleteProperty(const QString &name); bool isCallable() const; - QJSValue call(const QJSValueList &args = QJSValueList()); // ### Qt6: Make const - QJSValue callWithInstance(const QJSValue &instance, const QJSValueList &args = QJSValueList()); // ### Qt6: Make const - QJSValue callAsConstructor(const QJSValueList &args = QJSValueList()); // ### Qt6: Make const + QJSValue call(const QJSValueList &args = QJSValueList()) const; + QJSValue callWithInstance(const QJSValue &instance, const QJSValueList &args = QJSValueList()) const; + QJSValue callAsConstructor(const QJSValueList &args = QJSValueList()) const; ErrorType errorType() const; -#ifdef QT_DEPRECATED - QT_DEPRECATED QJSEngine *engine() const; -#endif - QJSValue(QV4::ExecutionEngine *e, quint64 val); private: friend class QJSValuePrivate; // force compile error, prevent QJSValue(bool) to be called - QJSValue(void *) Q_DECL_EQ_DELETE; + QJSValue(void *) = delete; - mutable quintptr d; + quint64 d; }; +#ifndef QT_NO_DATASTREAM +Q_QML_EXPORT QDataStream &operator<<(QDataStream &, const QJSValue &); +Q_QML_EXPORT QDataStream &operator>>(QDataStream &, QJSValue &); +#endif + QT_END_NAMESPACE Q_DECLARE_METATYPE(QJSValue) diff --git a/src/qml/jsapi/qjsvalue_p.h b/src/qml/jsapi/qjsvalue_p.h index 2faffffbae..4624652c93 100644 --- a/src/qml/jsapi/qjsvalue_p.h +++ b/src/qml/jsapi/qjsvalue_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // // W A R N I N G @@ -56,7 +20,6 @@ #include <private/qv4value_p.h> #include <private/qv4string_p.h> #include <private/qv4engine_p.h> -#include <private/qflagpointer_p.h> #include <private/qv4mm_p.h> #include <private/qv4persistent_p.h> @@ -64,132 +27,344 @@ QT_BEGIN_NAMESPACE -class Q_AUTOTEST_EXPORT QJSValuePrivate +class QJSValuePrivate { + static constexpr quint64 s_tagBits = 3; // 3 bits mask + static constexpr quint64 s_tagMask = (1 << s_tagBits) - 1; + + static constexpr quint64 s_pointerBit = 0x1; + public: - static inline QV4::Value *getValue(const QJSValue *jsval) + enum class Kind { + Undefined = 0x0, + Null = 0x2, + IntValue = 0x4, + BoolValue = 0x6, + DoublePtr = 0x0 | s_pointerBit, + QV4ValuePtr = 0x2 | s_pointerBit, + QStringPtr = 0x4 | s_pointerBit, + }; + + static_assert(quint64(Kind::Undefined) <= s_tagMask); + static_assert(quint64(Kind::Null) <= s_tagMask); + static_assert(quint64(Kind::IntValue) <= s_tagMask); + static_assert(quint64(Kind::BoolValue) <= s_tagMask); + static_assert(quint64(Kind::DoublePtr) <= s_tagMask); + static_assert(quint64(Kind::QV4ValuePtr) <= s_tagMask); + static_assert(quint64(Kind::QStringPtr) <= s_tagMask); + + static Kind tag(quint64 raw) { return Kind(raw & s_tagMask); } + +#if QT_POINTER_SIZE == 4 + static void *pointer(quint64 raw) { - if (jsval->d & 3) - return nullptr; - return reinterpret_cast<QV4::Value *>(jsval->d); + Q_ASSERT(quint64(tag(raw)) & s_pointerBit); + return reinterpret_cast<void *>(raw >> 32); } - static inline QVariant *getVariant(const QJSValue *jsval) + static quint64 encodePointer(void *pointer, Kind tag) { - if (jsval->d & 1) - return reinterpret_cast<QVariant *>(jsval->d & ~3); - return nullptr; + Q_ASSERT(quint64(tag) & s_pointerBit); + return (quint64(quintptr(pointer)) << 32) | quint64(tag); } +#else + static constexpr quint64 s_minAlignment = 1 << s_tagBits; + static_assert(alignof(double) >= s_minAlignment); + static_assert(alignof(QV4::Value) >= s_minAlignment); + static_assert(alignof(QString) >= s_minAlignment); - static inline void setRawValue(QJSValue *jsval, QV4::Value *v) + static void *pointer(quint64 raw) { - jsval->d = reinterpret_cast<quintptr>(v); + Q_ASSERT(quint64(tag(raw)) & s_pointerBit); + return reinterpret_cast<void *>(raw & ~s_tagMask); } - static inline void setVariant(QJSValue *jsval, const QVariant &v) { - QVariant *val = new QVariant(v); - jsval->d = reinterpret_cast<quintptr>(val) | 1; + static quint64 encodePointer(void *pointer, Kind tag) + { + Q_ASSERT(quint64(tag) & s_pointerBit); + return quintptr(pointer) | quint64(tag); } +#endif - static inline void setValue(QJSValue *jsval, QV4::ExecutionEngine *engine, const QV4::Value &v) { - QV4::Value *value = engine->memoryManager->m_persistentValues->allocate(); - *value = v; - jsval->d = reinterpret_cast<quintptr>(value); + static quint64 encodeUndefined() + { + return quint64(Kind::Undefined); } - static inline void setValue(QJSValue *jsval, QV4::ExecutionEngine *engine, QV4::ReturnedValue v) { - QV4::Value *value = engine->memoryManager->m_persistentValues->allocate(); - *value = v; - jsval->d = reinterpret_cast<quintptr>(value); + static quint64 encodeNull() + { + return quint64(Kind::Null); } - static QV4::ReturnedValue convertedToValue(QV4::ExecutionEngine *e, const QJSValue &jsval) + static int intValue(quint64 v) { - QV4::Value *v = getValue(&jsval); - if (!v) { - QVariant *variant = getVariant(&jsval); - v = e->memoryManager->m_persistentValues->allocate(); - *v = variant ? e->fromVariant(*variant) : QV4::Encode::undefined(); - jsval.d = reinterpret_cast<quintptr>(v); - delete variant; - } + Q_ASSERT(tag(v) == Kind::IntValue); + return v >> 32; + } - if (QV4::PersistentValueStorage::getEngine(v) != e) { - qWarning("JSValue can't be reassigned to another engine."); - return QV4::Encode::undefined(); - } + static quint64 encode(int intValue) + { + return (quint64(intValue) << 32) | quint64(Kind::IntValue); + } - return v->asReturnedValue(); + static quint64 encode(uint uintValue) + { + return (uintValue < uint(std::numeric_limits<int>::max())) + ? encode(int(uintValue)) + : encode(double(uintValue)); } - static QV4::Value *valueForData(const QJSValue *jsval, QV4::Value *scratch) + static bool boolValue(quint64 v) { - QV4::Value *v = getValue(jsval); - if (v) - return v; - v = scratch; - QVariant *variant = getVariant(jsval); - if (!variant) { - *v = QV4::Encode::undefined(); - return v; - } + Q_ASSERT(tag(v) == Kind::BoolValue); + return v >> 32; + } - switch (variant->userType()) { - case QMetaType::UnknownType: - case QMetaType::Void: - *v = QV4::Encode::undefined(); - break; - case QMetaType::Nullptr: - case QMetaType::VoidStar: - *v = QV4::Encode::null(); - break; - case QMetaType::Bool: - *v = QV4::Encode(variant->toBool()); + static quint64 encode(bool boolValue) + { + return (quint64(boolValue) << 32) | quint64(Kind::BoolValue); + } + + static double *doublePtr(quint64 v) + { + Q_ASSERT(tag(v) == Kind::DoublePtr); + return static_cast<double *>(pointer(v)); + } + + static quint64 encode(double doubleValue) + { + return encodePointer(new double(doubleValue), Kind::DoublePtr); + } + + static QV4::Value *qv4ValuePtr(quint64 v) + { + Q_ASSERT(tag(v) == Kind::QV4ValuePtr); + return static_cast<QV4::Value *>(pointer(v)); + } + + static quint64 encode(const QV4::Value &qv4Value) + { + switch (qv4Value.type()) { + case QV4::StaticValue::Boolean_Type: + return encode(qv4Value.booleanValue()); + case QV4::StaticValue::Integer_Type: + return encode(qv4Value.integerValue()); + case QV4::StaticValue::Managed_Type: { + auto managed = qv4Value.as<QV4::Managed>(); + auto engine = managed->engine(); + auto mm = engine->memoryManager; + QV4::Value *m = mm->m_persistentValues->allocate(); + Q_ASSERT(m); + // we create a new strong reference to the heap managed object + // to avoid having to rescan the persistent values, we mark it here + QV4::WriteBarrier::markCustom(engine, [&](QV4::MarkStack *stack){ + if constexpr (QV4::WriteBarrier::isInsertionBarrier) + managed->heapObject()->mark(stack); + }); + *m = qv4Value; + return encodePointer(m, Kind::QV4ValuePtr); + } + case QV4::StaticValue::Double_Type: + return encode(qv4Value.doubleValue()); + case QV4::StaticValue::Null_Type: + return encodeNull(); + case QV4::StaticValue::Empty_Type: + Q_UNREACHABLE(); break; - case QMetaType::Double: - *v = QV4::Encode(variant->toDouble()); + case QV4::StaticValue::Undefined_Type: break; - case QMetaType::Int: - case QMetaType::Short: - case QMetaType::UShort: - case QMetaType::Char: - case QMetaType::UChar: - *v = QV4::Encode(variant->toInt()); + } + + return encodeUndefined(); + } + + static QString *qStringPtr(quint64 v) + { + Q_ASSERT(tag(v) == Kind::QStringPtr); + return static_cast<QString *>(pointer(v)); + } + + static quint64 encode(QString stringValue) + { + return encodePointer(new QString(std::move(stringValue)), Kind::QStringPtr); + } + + static quint64 encode(QLatin1String stringValue) + { + return encodePointer(new QString(std::move(stringValue)), Kind::QStringPtr); + } + + static QJSValue fromReturnedValue(QV4::ReturnedValue d) + { + QJSValue result; + setValue(&result, d); + return result; + } + + template<typename T> + static const T *asManagedType(const QJSValue *jsval) + { + if (tag(jsval->d) == Kind::QV4ValuePtr) { + if (const QV4::Value *value = qv4ValuePtr(jsval->d)) + return value->as<T>(); + } + return nullptr; + } + + // This is a move operation and transfers ownership. + static QV4::Value *takeManagedValue(QJSValue *jsval) + { + if (tag(jsval->d) == Kind::QV4ValuePtr) { + if (QV4::Value *value = qv4ValuePtr(jsval->d)) { + jsval->d = encodeUndefined(); + return value; + } + } + return nullptr; + } + + static QV4::ReturnedValue asPrimitiveType(const QJSValue *jsval) + { + switch (tag(jsval->d)) { + case Kind::BoolValue: + return QV4::Encode(boolValue(jsval->d)); + case Kind::IntValue: + return QV4::Encode(intValue(jsval->d)); + case Kind::DoublePtr: + return QV4::Encode(*doublePtr(jsval->d)); + case Kind::Null: + return QV4::Encode::null(); + case Kind::Undefined: + case Kind::QV4ValuePtr: + case Kind::QStringPtr: break; - case QMetaType::UInt: - *v = QV4::Encode(variant->toUInt()); + } + + return QV4::Encode::undefined(); + } + + // Beware: This only returns a non-null string if the QJSValue actually holds one. + // QV4::Strings are kept as managed values. Retrieve those with getValue(). + static const QString *asQString(const QJSValue *jsval) + { + if (tag(jsval->d) == Kind::QStringPtr) { + if (const QString *string = qStringPtr(jsval->d)) + return string; + } + return nullptr; + } + + static QV4::ReturnedValue asReturnedValue(const QJSValue *jsval) + { + switch (tag(jsval->d)) { + case Kind::BoolValue: + return QV4::Encode(boolValue(jsval->d)); + case Kind::IntValue: + return QV4::Encode(intValue(jsval->d)); + case Kind::DoublePtr: + return QV4::Encode(*doublePtr(jsval->d)); + case Kind::Null: + return QV4::Encode::null(); + case Kind::QV4ValuePtr: + return qv4ValuePtr(jsval->d)->asReturnedValue(); + case Kind::Undefined: + case Kind::QStringPtr: break; - default: - return nullptr; } - return v; + + return QV4::Encode::undefined(); + } + + static void setString(QJSValue *jsval, QString s) + { + jsval->d = encode(std::move(s)); + } + + // Only use this with an existing persistent value. + // Ownership is transferred to the QJSValue. + static void adoptPersistentValue(QJSValue *jsval, QV4::Value *v) + { + jsval->d = encodePointer(v, Kind::QV4ValuePtr); + } + + static void setValue(QJSValue *jsval, const QV4::Value &v) + { + jsval->d = encode(v); + } + + // Moves any QString onto the V4 heap, changing the value to reflect that. + static void manageStringOnV4Heap(QV4::ExecutionEngine *e, QJSValue *jsval) + { + if (const QString *string = asQString(jsval)) { + jsval->d = encode(QV4::Value::fromHeapObject(e->newString(*string))); + delete string; + } + } + + // Converts any QString on the fly, involving an allocation. + // Does not change the value. + static QV4::ReturnedValue convertToReturnedValue(QV4::ExecutionEngine *e, + const QJSValue &jsval) + { + if (const QString *string = asQString(&jsval)) + return e->newString(*string)->asReturnedValue(); + if (const QV4::Value *val = asManagedType<QV4::Managed>(&jsval)) { + if (QV4::PersistentValueStorage::getEngine(val) == e) + return val->asReturnedValue(); + + qWarning("JSValue can't be reassigned to another engine."); + return QV4::Encode::undefined(); + } + return asPrimitiveType(&jsval); } - static QV4::ExecutionEngine *engine(const QJSValue *jsval) { - QV4::Value *v = getValue(jsval); - return v ? QV4::PersistentValueStorage::getEngine(v) : nullptr; + static QV4::ExecutionEngine *engine(const QJSValue *jsval) + { + if (tag(jsval->d) == Kind::QV4ValuePtr) { + if (const QV4::Value *value = qv4ValuePtr(jsval->d)) + return QV4::PersistentValueStorage::getEngine(value); + } + + return nullptr; } - static inline bool checkEngine(QV4::ExecutionEngine *e, const QJSValue &jsval) { + static bool checkEngine(QV4::ExecutionEngine *e, const QJSValue &jsval) + { QV4::ExecutionEngine *v4 = engine(&jsval); return !v4 || v4 == e; } - static inline void free(QJSValue *jsval) { - if (QV4::Value *v = QJSValuePrivate::getValue(jsval)) { - if (QV4::ExecutionEngine *e = engine(jsval)) { - if (QJSEngine *jsEngine = e->jsEngine()) { - if (jsEngine->thread() != QThread::currentThread()) { - QMetaObject::invokeMethod( - jsEngine, [v](){ QV4::PersistentValueStorage::free(v); }); - return; - } + static void free(QJSValue *jsval) + { + switch (tag(jsval->d)) { + case Kind::Undefined: + case Kind::Null: + case Kind::IntValue: + case Kind::BoolValue: + return; + case Kind::DoublePtr: + delete doublePtr(jsval->d); + return; + case Kind::QStringPtr: + delete qStringPtr(jsval->d); + return; + case Kind::QV4ValuePtr: + break; + } + + // We need a mutable value for free(). It needs to write to the actual memory. + QV4::Value *m = qv4ValuePtr(jsval->d); + Q_ASSERT(m); // Otherwise it would have been undefined above. + if (QV4::ExecutionEngine *e = QV4::PersistentValueStorage::getEngine(m)) { + if (QJSEngine *jsEngine = e->jsEngine()) { + if (jsEngine->thread() != QThread::currentThread()) { + QMetaObject::invokeMethod( + jsEngine, [m](){ QV4::PersistentValueStorage::free(m); }); + return; } } - QV4::PersistentValueStorage::free(v); - } else if (QVariant *v = QJSValuePrivate::getVariant(jsval)) { - delete v; } + QV4::PersistentValueStorage::free(m); } }; diff --git a/src/qml/jsapi/qjsvalueiterator.cpp b/src/qml/jsapi/qjsvalueiterator.cpp index 076b90c5f2..6f561e82ed 100644 --- a/src/qml/jsapi/qjsvalueiterator.cpp +++ b/src/qml/jsapi/qjsvalueiterator.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qjsvalueiterator.h" #include "qjsvalueiterator_p.h" @@ -58,12 +22,12 @@ void QJSValueIteratorPrivate::init(const QJSValue &v) QV4::ExecutionEngine *e = QJSValuePrivate::engine(&v); if (!e) return; - QV4::Object *o = QJSValuePrivate::getValue(&v)->objectValue(); + const QV4::Object *o = QJSValuePrivate::asManagedType<QV4::Object>(&v); if (!o) return; engine = e; - object = o; + object.set(e, o->asReturnedValue()); iterator.reset(o->ownPropertyKeys(object.valueRef())); next(); } @@ -209,7 +173,7 @@ QJSValue QJSValueIterator::value() const scope.engine->catchException(); return QJSValue(); } - return QJSValue(scope.engine, val->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(val->asReturnedValue()); } diff --git a/src/qml/jsapi/qjsvalueiterator.h b/src/qml/jsapi/qjsvalueiterator.h index f9468a2242..bffc60358d 100644 --- a/src/qml/jsapi/qjsvalueiterator.h +++ b/src/qml/jsapi/qjsvalueiterator.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QJSVALUEITERATOR_H #define QJSVALUEITERATOR_H diff --git a/src/qml/jsapi/qjsvalueiterator_p.h b/src/qml/jsapi/qjsvalueiterator_p.h index a870850c11..1ac8f22512 100644 --- a/src/qml/jsapi/qjsvalueiterator_p.h +++ b/src/qml/jsapi/qjsvalueiterator_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QJSVALUEITERATOR_P_H #define QJSVALUEITERATOR_P_H |