aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime/qv4engine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/jsruntime/qv4engine.cpp')
-rw-r--r--src/qml/jsruntime/qv4engine.cpp1771
1 files changed, 1216 insertions, 555 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp
index 590cebfa7c..bd6251caa9 100644
--- a/src/qml/jsruntime/qv4engine.cpp
+++ b/src/qml/jsruntime/qv4engine.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) 2021 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 <qv4engine_p.h>
#include <private/qv4compileddata_p.h>
@@ -50,6 +14,8 @@
#if QT_CONFIG(regularexpression)
#include <QRegularExpression>
#endif
+#include <QtCore/QTimeZone>
+#include <QtCore/qiterable.h>
#include <qv4qmlcontext_p.h>
#include <qv4value_p.h>
@@ -89,13 +55,13 @@
#include "qv4reflect_p.h"
#include "qv4proxy_p.h"
#include "qv4stackframe_p.h"
+#include "qv4stacklimits_p.h"
#include "qv4atomics_p.h"
-
-#if QT_CONFIG(qml_sequence_object)
+#include "qv4urlobject_p.h"
+#include "qv4variantobject_p.h"
#include "qv4sequenceobject_p.h"
-#endif
-
#include "qv4qobjectwrapper_p.h"
+#include "qv4qmetaobjectwrapper_p.h"
#include "qv4memberdata_p.h"
#include "qv4arraybuffer_p.h"
#include "qv4dataview_p.h"
@@ -118,71 +84,250 @@
#endif
#include <private/qv4sqlerrors_p.h>
#include <qqmlfile.h>
+#include <qmetatype.h>
+#include <qsequentialiterable.h>
-#if USE(PTHREADS)
-# include <pthread.h>
-#if !defined(Q_OS_INTEGRITY)
-# include <sys/resource.h>
-#endif
-#if HAVE(PTHREAD_NP_H)
-# include <pthread_np.h>
-#endif
-#endif
+#include <private/qqmlengine_p.h>
#ifdef V4_USE_VALGRIND
#include <valgrind/memcheck.h>
#endif
-Q_DECLARE_METATYPE(QList<int>)
-
QT_BEGIN_NAMESPACE
+DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE);
+DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE);
+
using namespace QV4;
+// While engineSerial is odd the statics haven't been initialized. The engine that receives ID 1
+// initializes the statics and sets engineSerial to 2 afterwards.
+// Each engine does engineSerial.fetchAndAddOrdered(2) on creation. Therefore engineSerial stays
+// odd while the statics are being initialized, and stays even afterwards.
+// Any further engines created while the statics are being initialized busy-wait until engineSerial
+// is even.
static QBasicAtomicInt engineSerial = Q_BASIC_ATOMIC_INITIALIZER(1);
+int ExecutionEngine::s_maxCallDepth = -1;
+int ExecutionEngine::s_jitCallCountThreshold = 3;
+int ExecutionEngine::s_maxJSStackSize = 4 * 1024 * 1024;
+int ExecutionEngine::s_maxGCStackSize = 2 * 1024 * 1024;
ReturnedValue throwTypeError(const FunctionObject *b, const QV4::Value *, const QV4::Value *, int)
{
return b->engine()->throwTypeError();
}
-qint32 ExecutionEngine::maxCallDepth = -1;
template <typename ReturnType>
ReturnType convertJSValueToVariantType(const QJSValue &value)
{
- return value.toVariant().value<ReturnType>();
+ const QVariant variant = value.toVariant();
+ return variant.metaType() == QMetaType::fromType<QJSValue>()
+ ? ReturnType()
+ : variant.value<ReturnType>();
}
-static void saveJSValue(QDataStream &stream, const void *data)
+struct JSArrayIterator {
+ QJSValue const* data;
+ quint32 index;
+};
+
+namespace {
+void createNewIteratorIfNonExisting(void **iterator) {
+ if (*iterator == nullptr)
+ *iterator = new JSArrayIterator;
+}
+}
+
+static QtMetaContainerPrivate::QMetaSequenceInterface emptySequenceInterface()
{
- const QJSValue *jsv = reinterpret_cast<const QJSValue *>(data);
- quint32 isNullOrUndefined = 0;
- if (jsv->isNull())
- isNullOrUndefined |= 0x1;
- if (jsv->isUndefined())
- isNullOrUndefined |= 0x2;
- stream << isNullOrUndefined;
- if (!isNullOrUndefined)
- reinterpret_cast<const QJSValue*>(data)->toVariant().save(stream);
+ // set up some functions so that non-array QSequentialIterables do not crash
+ // but instead appear as an empty sequence
+
+ using namespace QtMetaContainerPrivate;
+ QMetaSequenceInterface iface;
+ iface.sizeFn = [](const void *) { return qsizetype(0); };
+ iface.valueAtIndexFn = [](const void *, qsizetype, void *) {};
+ iface.createIteratorFn = [](void *, QMetaSequenceInterface::Position) -> void * {
+ return nullptr;
+ };
+ iface.advanceIteratorFn = [](void *, qsizetype) {};
+ iface.compareIteratorFn = [](const void *, const void *) {
+ return true; /*all iterators are nullptr*/
+ };
+ iface.destroyIteratorFn = [](const void *) {};
+ iface.copyIteratorFn = [](void *, const void *) {};
+ iface.diffIteratorFn = [](const void *, const void *) { return qsizetype(0); };
+ return iface;
}
-static void restoreJSValue(QDataStream &stream, void *data)
+static QtMetaContainerPrivate::QMetaSequenceInterface sequenceInterface()
{
- QJSValue *jsv = reinterpret_cast<QJSValue*>(data);
+ using namespace QtMetaContainerPrivate;
+ QMetaSequenceInterface iface;
+ iface.valueMetaType = QtPrivate::qMetaTypeInterfaceForType<QVariant>();
+ iface.iteratorCapabilities = RandomAccessCapability | BiDirectionalCapability | ForwardCapability;
+ iface.addRemoveCapabilities = CanAddAtEnd;
+ iface.sizeFn = [](const void *p) -> qsizetype {
+ return static_cast<QJSValue const *>(p)->property(QString::fromLatin1("length")).toInt();
+ };
+
+ /* Lifetime management notes:
+ * valueAtIndexFn and valueAtIteratorFn return a pointer to a JSValue allocated via
+ * QMetaType::create Because we set QVariantConstructionFlags::ShouldDeleteVariantData,
+ * QSequentialIterable::at and QSequentialIterable::operator*() will free that memory
+ */
+
+ iface.valueAtIndexFn = [](const void *iterable, qsizetype index, void *dataPtr) -> void {
+ auto *data = static_cast<QVariant *>(dataPtr);
+ *data = static_cast<QJSValue const *>(iterable)->property(quint32(index)).toVariant();
+ };
+ iface.createIteratorFn = [](void *iterable, QMetaSequenceInterface::Position pos) {
+ void *iterator = nullptr;
+ createNewIteratorIfNonExisting(&iterator);
+ auto jsArrayIterator = static_cast<JSArrayIterator *>(iterator);
+ jsArrayIterator->index = 0;
+ jsArrayIterator->data = reinterpret_cast<QJSValue const*>(iterable);
+ if (pos == QMetaSequenceInterface::AtEnd) {
+ auto length = static_cast<QJSValue const *>(iterable)->property(
+ QString::fromLatin1("length")).toInt();
+ jsArrayIterator->index = quint32(length);
+ }
+ return iterator;
+ };
+ iface.createConstIteratorFn = [](const void *iterable, QMetaSequenceInterface::Position pos) {
+ void *iterator = nullptr;
+ createNewIteratorIfNonExisting(&iterator);
+ auto jsArrayIterator = static_cast<JSArrayIterator *>(iterator);
+ jsArrayIterator->index = 0;
+ jsArrayIterator->data = reinterpret_cast<QJSValue const*>(iterable);
+ if (pos == QMetaSequenceInterface::AtEnd) {
+ auto length = static_cast<QJSValue const *>(iterable)->property(
+ QString::fromLatin1("length")).toInt();
+ jsArrayIterator->index = quint32(length);
+ }
+ return iterator;
+ };
+ iface.advanceIteratorFn = [](void *iterator, qsizetype advanceBy) {
+ static_cast<JSArrayIterator *>(iterator)->index += quint32(advanceBy);
+ };
+ iface.advanceConstIteratorFn = [](void *iterator, qsizetype advanceBy) {
+ static_cast<JSArrayIterator *>(iterator)->index += quint32(advanceBy);
+ };
+ iface.valueAtIteratorFn = [](const void *iterator, void *dataPtr) -> void {
+ const auto *arrayIterator = static_cast<const JSArrayIterator *>(iterator);
+ const QJSValue *jsArray = arrayIterator->data;
+ auto *data = static_cast<QVariant *>(dataPtr);
+ *data = jsArray->property(arrayIterator->index).toVariant();
+ };
+ iface.valueAtConstIteratorFn = [](const void *iterator, void *dataPtr) -> void {
+ const auto *arrayIterator = static_cast<const JSArrayIterator *>(iterator);
+ const QJSValue *jsArray = arrayIterator->data;
+ auto *data = static_cast<QVariant *>(dataPtr);
+ *data = jsArray->property(arrayIterator->index).toVariant();
+ };
+ iface.destroyIteratorFn = [](const void *iterator) {
+ delete static_cast<const JSArrayIterator *>(iterator);
+ };
+ iface.destroyConstIteratorFn = [](const void *iterator) {
+ delete static_cast<const JSArrayIterator *>(iterator);
+ };
+ iface.compareIteratorFn = [](const void *p, const void *other) {
+ auto this_ = static_cast<const JSArrayIterator *>(p);
+ auto that_ = static_cast<const JSArrayIterator *>(other);
+ return this_->index == that_->index && this_->data == that_->data;
+ };
+ iface.compareConstIteratorFn = [](const void *p, const void *other) {
+ auto this_ = static_cast<const JSArrayIterator *>(p);
+ auto that_ = static_cast<const JSArrayIterator *>(other);
+ return this_->index == that_->index && this_->data == that_->data;
+ };
+ iface.copyIteratorFn = [](void *iterator, const void *otherIterator) {
+ auto *otherIter = (static_cast<JSArrayIterator const *>(otherIterator));
+ static_cast<JSArrayIterator *>(iterator)->index = otherIter->index;
+ static_cast<JSArrayIterator *>(iterator)->data = otherIter->data;
+ };
+ iface.copyConstIteratorFn = [](void *iterator, const void *otherIterator) {
+ auto *otherIter = (static_cast<JSArrayIterator const *>(otherIterator));
+ static_cast<JSArrayIterator *>(iterator)->index = otherIter->index;
+ static_cast<JSArrayIterator *>(iterator)->data = otherIter->data;
+ };
+ iface.diffIteratorFn = [](const void *iterator, const void *otherIterator) -> qsizetype {
+ const auto *self = static_cast<const JSArrayIterator *>(iterator);
+ const auto *other = static_cast<const JSArrayIterator *>(otherIterator);
+ return self->index - other->index;
+ };
+ iface.diffConstIteratorFn = [](const void *iterator, const void *otherIterator) -> qsizetype {
+ const auto *self = static_cast<const JSArrayIterator *>(iterator);
+ const auto *other = static_cast<const JSArrayIterator *>(otherIterator);
+ return self->index - other->index;
+ };
+ iface.addValueFn = [](void *iterable, const void *data, QMetaSequenceInterface::Position) {
+ auto *jsvalue = static_cast<QJSValue *>(iterable);
+ QV4::Scope scope(QJSValuePrivate::engine(jsvalue));
+ QV4::ScopedArrayObject a(scope, QJSValuePrivate::asManagedType<QV4::ArrayObject>(jsvalue));
+ QV4::ScopedValue v(scope, scope.engine->fromVariant(*static_cast<const QVariant *>(data)));
+ if (!a)
+ return;
+ int len = a->getLength();
+ a->setIndexed(len, v, QV4::Object::DoNotThrow);
+ };
+ return iface;
+}
+
+static QSequentialIterable jsvalueToSequence (const QJSValue& value) {
+ using namespace QtMetaTypePrivate;
+ using namespace QtMetaContainerPrivate;
- quint32 isNullOrUndefined;
- stream >> isNullOrUndefined;
- if (isNullOrUndefined & 0x1) {
- *jsv = QJSValue(QJSValue::NullValue);
- } else if (isNullOrUndefined & 0x2) {
- *jsv = QJSValue();
+ if (!value.isArray()) {
+ static QMetaSequenceInterface emptySequence = emptySequenceInterface();
+ return QSequentialIterable(QMetaSequence(&emptySequence), nullptr);
+ }
+
+ static QMetaSequenceInterface sequence = sequenceInterface();
+ return QSequentialIterable(QMetaSequence(&sequence), &value);
+}
+
+void ExecutionEngine::initializeStaticMembers()
+{
+ bool ok = false;
+
+ const int envMaxJSStackSize = qEnvironmentVariableIntValue("QV4_JS_MAX_STACK_SIZE", &ok);
+ if (ok && envMaxJSStackSize > 0)
+ s_maxJSStackSize = envMaxJSStackSize;
+
+ const int envMaxGCStackSize = qEnvironmentVariableIntValue("QV4_GC_MAX_STACK_SIZE", &ok);
+ if (ok && envMaxGCStackSize > 0)
+ s_maxGCStackSize = envMaxGCStackSize;
+
+ if (qEnvironmentVariableIsSet("QV4_CRASH_ON_STACKOVERFLOW")) {
+ s_maxCallDepth = std::numeric_limits<qint32>::max();
} else {
- QVariant v;
- v.load(stream);
- QJSValuePrivate::setVariant(jsv, v);
+ ok = false;
+ s_maxCallDepth = qEnvironmentVariableIntValue("QV4_MAX_CALL_DEPTH", &ok);
+ if (!ok || s_maxCallDepth <= 0)
+ s_maxCallDepth = -1;
}
+
+ ok = false;
+ s_jitCallCountThreshold = qEnvironmentVariableIntValue("QV4_JIT_CALL_THRESHOLD", &ok);
+ if (!ok)
+ s_jitCallCountThreshold = 3;
+ if (qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER"))
+ s_jitCallCountThreshold = std::numeric_limits<int>::max();
+
+ qMetaTypeId<QJSValue>();
+ qMetaTypeId<QList<int> >();
+
+ if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantMap>())
+ QMetaType::registerConverter<QJSValue, QVariantMap>(convertJSValueToVariantType<QVariantMap>);
+ if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantList>())
+ QMetaType::registerConverter<QJSValue, QVariantList>(convertJSValueToVariantType<QVariantList>);
+ if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QStringList>())
+ QMetaType::registerConverter<QJSValue, QStringList>(convertJSValueToVariantType<QStringList>);
+ if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QSequentialIterable>())
+ QMetaType::registerConverter<QJSValue, QSequentialIterable>(jsvalueToSequence);
}
ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
@@ -193,7 +338,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
, gcStack(new WTF::PageAllocation)
, globalCode(nullptr)
, publicEngine(jsEngine)
- , m_engineId(engineSerial.fetchAndAddOrdered(1))
+ , m_engineId(engineSerial.fetchAndAddOrdered(2))
, regExpCache(nullptr)
, m_multiplyWrappedQObjects(nullptr)
#if QT_CONFIG(qml_jit)
@@ -204,41 +349,36 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
#endif
, m_qmlEngine(nullptr)
{
- bool ok = false;
- const int envMaxJSStackSize = qEnvironmentVariableIntValue("QV4_JS_MAX_STACK_SIZE", &ok);
- if (ok && envMaxJSStackSize > 0)
- m_maxJSStackSize = envMaxJSStackSize;
-
- const int envMaxGCStackSize = qEnvironmentVariableIntValue("QV4_GC_MAX_STACK_SIZE", &ok);
- if (ok && envMaxGCStackSize > 0)
- m_maxGCStackSize = envMaxGCStackSize;
-
- memoryManager = new QV4::MemoryManager(this);
-
- if (maxCallDepth == -1) {
- if (qEnvironmentVariableIsSet("QV4_CRASH_ON_STACKOVERFLOW")) {
- maxCallDepth = std::numeric_limits<qint32>::max();
- } else {
- ok = false;
- maxCallDepth = qEnvironmentVariableIntValue("QV4_MAX_CALL_DEPTH", &ok);
- if (!ok || maxCallDepth <= 0) {
-#if defined(QT_NO_DEBUG) && !defined(__SANITIZE_ADDRESS__) && !QT_HAS_FEATURE(address_sanitizer)
- maxCallDepth = 1234;
-#else
- // no (tail call) optimization is done, so there'll be a lot mare stack frames active
- maxCallDepth = 200;
-#endif
- }
+ if (m_engineId == 1) {
+ initializeStaticMembers();
+ engineSerial.storeRelease(2); // make it even
+ } else if (Q_UNLIKELY(m_engineId & 1)) {
+ // This should be rare. You usually don't create lots of engines at the same time.
+ while (engineSerial.loadAcquire() & 1) {
+ QThread::yieldCurrentThread();
}
}
- Q_ASSERT(maxCallDepth > 0);
+ if (s_maxCallDepth < 0) {
+ const StackProperties stack = stackProperties();
+ cppStackBase = stack.base;
+ cppStackLimit = stack.softLimit;
+ } else {
+ callDepth = 0;
+ }
+
+ // We allocate guard pages around our stacks.
+ const size_t guardPages = 2 * WTF::pageSize();
+
+ memoryManager = new QV4::MemoryManager(this);
+ // we don't want to run the gc while the initial setup is not done; not even in aggressive mode
+ GCCriticalSection gcCriticalSection(this);
// reserve space for the JS stack
// we allow it to grow to a bit more than m_maxJSStackSize, as we can overshoot due to ScopedValues
// allocated outside of JIT'ed methods.
- *jsStack = WTF::PageAllocation::allocate(m_maxJSStackSize + 256*1024, WTF::OSAllocator::JSVMStackPages,
- /* writable */ true, /* executable */ false,
- /* includesGuardPages */ true);
+ *jsStack = WTF::PageAllocation::allocate(
+ s_maxJSStackSize + 256*1024 + guardPages, WTF::OSAllocator::JSVMStackPages,
+ /* writable */ true, /* executable */ false, /* includesGuardPages */ true);
jsStackBase = (Value *)jsStack->base();
#ifdef V4_USE_VALGRIND
VALGRIND_MAKE_MEM_UNDEFINED(jsStackBase, m_maxJSStackSize + 256*1024);
@@ -246,18 +386,9 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
jsStackTop = jsStackBase;
- *gcStack = WTF::PageAllocation::allocate(m_maxGCStackSize, WTF::OSAllocator::JSVMStackPages,
- /* writable */ true, /* executable */ false,
- /* includesGuardPages */ true);
-
- {
- ok = false;
- jitCallCountThreshold = qEnvironmentVariableIntValue("QV4_JIT_CALL_THRESHOLD", &ok);
- if (!ok)
- jitCallCountThreshold = 3;
- if (qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER"))
- jitCallCountThreshold = std::numeric_limits<int>::max();
- }
+ *gcStack = WTF::PageAllocation::allocate(
+ s_maxGCStackSize + guardPages, WTF::OSAllocator::JSVMStackPages,
+ /* writable */ true, /* executable */ false, /* includesGuardPages */ true);
exceptionValue = jsAlloca(1);
*exceptionValue = Encode::undefined();
@@ -269,7 +400,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
jsSymbols = jsAlloca(NJSSymbols);
// set up stack limits
- jsStackLimit = jsStackBase + m_maxJSStackSize/sizeof(Value);
+ jsStackLimit = jsStackBase + s_maxJSStackSize/sizeof(Value);
identifierTable = new IdentifierTable(this);
@@ -493,30 +624,26 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
jsObjects[VariantProto] = memoryManager->allocate<VariantPrototype>();
Q_ASSERT(variantPrototype()->getPrototypeOf() == objectPrototype()->d());
-#if QT_CONFIG(qml_sequence_object)
ic = newInternalClass(SequencePrototype::staticVTable(), SequencePrototype::defaultPrototype(this));
jsObjects[SequenceProto] = ScopedValue(scope, memoryManager->allocObject<SequencePrototype>(ic->d()));
-#endif
- ExecutionContext *global = rootContext();
-
- jsObjects[Object_Ctor] = memoryManager->allocate<ObjectCtor>(global);
- jsObjects[String_Ctor] = memoryManager->allocate<StringCtor>(global);
- jsObjects[Symbol_Ctor] = memoryManager->allocate<SymbolCtor>(global);
- jsObjects[Number_Ctor] = memoryManager->allocate<NumberCtor>(global);
- jsObjects[Boolean_Ctor] = memoryManager->allocate<BooleanCtor>(global);
- jsObjects[Array_Ctor] = memoryManager->allocate<ArrayCtor>(global);
- jsObjects[Function_Ctor] = memoryManager->allocate<FunctionCtor>(global);
- jsObjects[GeneratorFunction_Ctor] = memoryManager->allocate<GeneratorFunctionCtor>(global);
- jsObjects[Date_Ctor] = memoryManager->allocate<DateCtor>(global);
- jsObjects[RegExp_Ctor] = memoryManager->allocate<RegExpCtor>(global);
- jsObjects[Error_Ctor] = memoryManager->allocate<ErrorCtor>(global);
- jsObjects[EvalError_Ctor] = memoryManager->allocate<EvalErrorCtor>(global);
- jsObjects[RangeError_Ctor] = memoryManager->allocate<RangeErrorCtor>(global);
- jsObjects[ReferenceError_Ctor] = memoryManager->allocate<ReferenceErrorCtor>(global);
- jsObjects[SyntaxError_Ctor] = memoryManager->allocate<SyntaxErrorCtor>(global);
- jsObjects[TypeError_Ctor] = memoryManager->allocate<TypeErrorCtor>(global);
- jsObjects[URIError_Ctor] = memoryManager->allocate<URIErrorCtor>(global);
+ jsObjects[Object_Ctor] = memoryManager->allocate<ObjectCtor>(this);
+ jsObjects[String_Ctor] = memoryManager->allocate<StringCtor>(this);
+ jsObjects[Symbol_Ctor] = memoryManager->allocate<SymbolCtor>(this);
+ jsObjects[Number_Ctor] = memoryManager->allocate<NumberCtor>(this);
+ jsObjects[Boolean_Ctor] = memoryManager->allocate<BooleanCtor>(this);
+ jsObjects[Array_Ctor] = memoryManager->allocate<ArrayCtor>(this);
+ jsObjects[Function_Ctor] = memoryManager->allocate<FunctionCtor>(this);
+ jsObjects[GeneratorFunction_Ctor] = memoryManager->allocate<GeneratorFunctionCtor>(this);
+ jsObjects[Date_Ctor] = memoryManager->allocate<DateCtor>(this);
+ jsObjects[RegExp_Ctor] = memoryManager->allocate<RegExpCtor>(this);
+ jsObjects[Error_Ctor] = memoryManager->allocate<ErrorCtor>(this);
+ jsObjects[EvalError_Ctor] = memoryManager->allocate<EvalErrorCtor>(this);
+ jsObjects[RangeError_Ctor] = memoryManager->allocate<RangeErrorCtor>(this);
+ jsObjects[ReferenceError_Ctor] = memoryManager->allocate<ReferenceErrorCtor>(this);
+ jsObjects[SyntaxError_Ctor] = memoryManager->allocate<SyntaxErrorCtor>(this);
+ jsObjects[TypeError_Ctor] = memoryManager->allocate<TypeErrorCtor>(this);
+ jsObjects[URIError_Ctor] = memoryManager->allocate<URIErrorCtor>(this);
jsObjects[IteratorProto] = memoryManager->allocate<IteratorPrototype>();
ic = newInternalClass(ForInIteratorPrototype::staticVTable(), iteratorPrototype());
@@ -530,6 +657,15 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
ic = newInternalClass(StringIteratorPrototype::staticVTable(), iteratorPrototype());
jsObjects[StringIteratorProto] = memoryManager->allocObject<StringIteratorPrototype>(ic);
+ //
+ // url
+ //
+
+ jsObjects[Url_Ctor] = memoryManager->allocate<UrlCtor>(this);
+ jsObjects[UrlProto] = memoryManager->allocate<UrlPrototype>();
+ jsObjects[UrlSearchParams_Ctor] = memoryManager->allocate<UrlSearchParamsCtor>(this);
+ jsObjects[UrlSearchParamsProto] = memoryManager->allocate<UrlSearchParamsPrototype>();
+
str = newString(QStringLiteral("get [Symbol.species]"));
jsObjects[GetSymbolSpecies] = FunctionObject::createBuiltinFunction(this, str, ArrayPrototype::method_get_species, 0);
@@ -539,7 +675,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
static_cast<NumberPrototype *>(numberPrototype())->init(this, numberCtor());
static_cast<BooleanPrototype *>(booleanPrototype())->init(this, booleanCtor());
static_cast<ArrayPrototype *>(arrayPrototype())->init(this, arrayCtor());
- static_cast<PropertyListPrototype *>(propertyListPrototype())->init(this);
+ static_cast<PropertyListPrototype *>(propertyListPrototype())->init();
static_cast<DatePrototype *>(datePrototype())->init(this, dateCtor());
static_cast<FunctionPrototype *>(functionPrototype())->init(this, functionCtor());
static_cast<GeneratorPrototype *>(generatorPrototype())->init(this, generatorFunctionCtor());
@@ -551,6 +687,8 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
static_cast<SyntaxErrorPrototype *>(syntaxErrorPrototype())->init(this, syntaxErrorCtor());
static_cast<TypeErrorPrototype *>(typeErrorPrototype())->init(this, typeErrorCtor());
static_cast<URIErrorPrototype *>(uRIErrorPrototype())->init(this, uRIErrorCtor());
+ static_cast<UrlPrototype *>(urlPrototype())->init(this, urlCtor());
+ static_cast<UrlSearchParamsPrototype *>(urlSearchParamsPrototype())->init(this, urlSearchParamsCtor());
static_cast<IteratorPrototype *>(iteratorPrototype())->init(this);
static_cast<ForInIteratorPrototype *>(forInIteratorPrototype())->init(this);
@@ -561,23 +699,21 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
static_cast<VariantPrototype *>(variantPrototype())->init();
-#if QT_CONFIG(qml_sequence_object)
sequencePrototype()->cast<SequencePrototype>()->init();
-#endif
- jsObjects[WeakMap_Ctor] = memoryManager->allocate<WeakMapCtor>(global);
+ jsObjects[WeakMap_Ctor] = memoryManager->allocate<WeakMapCtor>(this);
jsObjects[WeakMapProto] = memoryManager->allocate<WeakMapPrototype>();
static_cast<WeakMapPrototype *>(weakMapPrototype())->init(this, weakMapCtor());
- jsObjects[Map_Ctor] = memoryManager->allocate<MapCtor>(global);
+ jsObjects[Map_Ctor] = memoryManager->allocate<MapCtor>(this);
jsObjects[MapProto] = memoryManager->allocate<MapPrototype>();
static_cast<MapPrototype *>(mapPrototype())->init(this, mapCtor());
- jsObjects[WeakSet_Ctor] = memoryManager->allocate<WeakSetCtor>(global);
+ jsObjects[WeakSet_Ctor] = memoryManager->allocate<WeakSetCtor>(this);
jsObjects[WeakSetProto] = memoryManager->allocate<WeakSetPrototype>();
static_cast<WeakSetPrototype *>(weakSetPrototype())->init(this, weakSetCtor());
- jsObjects[Set_Ctor] = memoryManager->allocate<SetCtor>(global);
+ jsObjects[Set_Ctor] = memoryManager->allocate<SetCtor>(this);
jsObjects[SetProto] = memoryManager->allocate<SetPrototype>();
static_cast<SetPrototype *>(setPrototype())->init(this, setCtor());
@@ -585,33 +721,34 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
// promises
//
- jsObjects[Promise_Ctor] = memoryManager->allocate<PromiseCtor>(global);
+ jsObjects[Promise_Ctor] = memoryManager->allocate<PromiseCtor>(this);
jsObjects[PromiseProto] = memoryManager->allocate<PromisePrototype>();
static_cast<PromisePrototype *>(promisePrototype())->init(this, promiseCtor());
// typed arrays
- jsObjects[SharedArrayBuffer_Ctor] = memoryManager->allocate<SharedArrayBufferCtor>(global);
+ jsObjects[SharedArrayBuffer_Ctor] = memoryManager->allocate<SharedArrayBufferCtor>(this);
jsObjects[SharedArrayBufferProto] = memoryManager->allocate<SharedArrayBufferPrototype>();
static_cast<SharedArrayBufferPrototype *>(sharedArrayBufferPrototype())->init(this, sharedArrayBufferCtor());
- jsObjects[ArrayBuffer_Ctor] = memoryManager->allocate<ArrayBufferCtor>(global);
+ jsObjects[ArrayBuffer_Ctor] = memoryManager->allocate<ArrayBufferCtor>(this);
jsObjects[ArrayBufferProto] = memoryManager->allocate<ArrayBufferPrototype>();
static_cast<ArrayBufferPrototype *>(arrayBufferPrototype())->init(this, arrayBufferCtor());
- jsObjects[DataView_Ctor] = memoryManager->allocate<DataViewCtor>(global);
+ jsObjects[DataView_Ctor] = memoryManager->allocate<DataViewCtor>(this);
jsObjects[DataViewProto] = memoryManager->allocate<DataViewPrototype>();
static_cast<DataViewPrototype *>(dataViewPrototype())->init(this, dataViewCtor());
jsObjects[ValueTypeProto] = (Heap::Base *) nullptr;
jsObjects[SignalHandlerProto] = (Heap::Base *) nullptr;
+ jsObjects[TypeWrapperProto] = (Heap::Base *) nullptr;
- jsObjects[IntrinsicTypedArray_Ctor] = memoryManager->allocate<IntrinsicTypedArrayCtor>(global);
+ jsObjects[IntrinsicTypedArray_Ctor] = memoryManager->allocate<IntrinsicTypedArrayCtor>(this);
jsObjects[IntrinsicTypedArrayProto] = memoryManager->allocate<IntrinsicTypedArrayPrototype>();
static_cast<IntrinsicTypedArrayPrototype *>(intrinsicTypedArrayPrototype())
->init(this, static_cast<IntrinsicTypedArrayCtor *>(intrinsicTypedArrayCtor()));
for (int i = 0; i < NTypedArrayTypes; ++i) {
- static_cast<Value &>(typedArrayCtors[i]) = memoryManager->allocate<TypedArrayCtor>(global, Heap::TypedArray::Type(i));
+ static_cast<Value &>(typedArrayCtors[i]) = memoryManager->allocate<TypedArrayCtor>(this, Heap::TypedArray::Type(i));
static_cast<Value &>(typedArrayPrototype[i]) = memoryManager->allocate<TypedArrayPrototype>(Heap::TypedArray::Type(i));
typedArrayPrototype[i].as<TypedArrayPrototype>()->init(this, static_cast<TypedArrayCtor *>(typedArrayCtors[i].as<Object>()));
}
@@ -640,6 +777,8 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
globalObject->defineDefaultProperty(QStringLiteral("TypeError"), *typeErrorCtor());
globalObject->defineDefaultProperty(QStringLiteral("URIError"), *uRIErrorCtor());
globalObject->defineDefaultProperty(QStringLiteral("Promise"), *promiseCtor());
+ globalObject->defineDefaultProperty(QStringLiteral("URL"), *urlCtor());
+ globalObject->defineDefaultProperty(QStringLiteral("URLSearchParams"), *urlSearchParamsCtor());
globalObject->defineDefaultProperty(QStringLiteral("SharedArrayBuffer"), *sharedArrayBufferCtor());
globalObject->defineDefaultProperty(QStringLiteral("ArrayBuffer"), *arrayBufferCtor());
@@ -656,14 +795,14 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
globalObject->defineDefaultProperty(QStringLiteral("Math"), (o = memoryManager->allocate<MathObject>()));
globalObject->defineDefaultProperty(QStringLiteral("JSON"), (o = memoryManager->allocate<JsonObject>()));
globalObject->defineDefaultProperty(QStringLiteral("Reflect"), (o = memoryManager->allocate<Reflect>()));
- globalObject->defineDefaultProperty(QStringLiteral("Proxy"), (o = memoryManager->allocate<Proxy>(rootContext())));
+ globalObject->defineDefaultProperty(QStringLiteral("Proxy"), (o = memoryManager->allocate<Proxy>(this)));
globalObject->defineReadonlyProperty(QStringLiteral("undefined"), Value::undefinedValue());
globalObject->defineReadonlyProperty(QStringLiteral("NaN"), Value::fromDouble(std::numeric_limits<double>::quiet_NaN()));
globalObject->defineReadonlyProperty(QStringLiteral("Infinity"), Value::fromDouble(Q_INFINITY));
- jsObjects[Eval_Function] = memoryManager->allocate<EvalFunction>(global);
+ jsObjects[Eval_Function] = memoryManager->allocate<EvalFunction>(this);
globalObject->defineDefaultProperty(QStringLiteral("eval"), *evalFunction());
// ES6: 20.1.2.12 & 20.1.2.13:
@@ -692,7 +831,9 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
globalObject->defineDefaultProperty(QStringLiteral("escape"), GlobalFunctions::method_escape, 1);
globalObject->defineDefaultProperty(QStringLiteral("unescape"), GlobalFunctions::method_unescape, 1);
- ScopedFunctionObject t(scope, memoryManager->allocate<FunctionObject>(rootContext(), nullptr, ::throwTypeError));
+ ScopedFunctionObject t(
+ scope,
+ memoryManager->allocate<DynamicFunctionObject>(this, nullptr, ::throwTypeError));
t->defineReadonlyProperty(id_length(), Value::fromInt32(0));
t->setInternalClass(t->internalClass()->cryopreserved());
jsObjects[ThrowerObject] = t;
@@ -703,33 +844,30 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
functionPrototype()->insertMember(id_caller(), pd, Attr_Accessor|Attr_ReadOnly_ButConfigurable);
functionPrototype()->insertMember(id_arguments(), pd, Attr_Accessor|Attr_ReadOnly_ButConfigurable);
- qMetaTypeId<QJSValue>();
- qMetaTypeId<QList<int> >();
-
- if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantMap>())
- QMetaType::registerConverter<QJSValue, QVariantMap>(convertJSValueToVariantType<QVariantMap>);
- if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantList>())
- QMetaType::registerConverter<QJSValue, QVariantList>(convertJSValueToVariantType<QVariantList>);
- if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QStringList>())
- QMetaType::registerConverter<QJSValue, QStringList>(convertJSValueToVariantType<QStringList>);
- QMetaType::registerStreamOperators(qMetaTypeId<QJSValue>(), saveJSValue, restoreJSValue);
-
QV4::QObjectWrapper::initializeBindings(this);
m_delayedCallQueue.init(this);
+ isInitialized = true;
}
ExecutionEngine::~ExecutionEngine()
{
- modules.clear();
+ for (auto val : nativeModules) {
+ PersistentValueStorage::free(val);
+ }
+ nativeModules.clear();
qDeleteAll(m_extensionData);
delete m_multiplyWrappedQObjects;
m_multiplyWrappedQObjects = nullptr;
delete identifierTable;
delete memoryManager;
- while (!compilationUnits.isEmpty())
- (*compilationUnits.begin())->unlink();
+ for (const auto &cu : std::as_const(m_compilationUnits)) {
+ Q_ASSERT(cu->engine == this);
+ cu->clear();
+ cu->engine = nullptr;
+ }
+ m_compilationUnits.clear();
delete bumperPointerAllocator;
delete regExpCache;
@@ -746,11 +884,6 @@ ExecutionEngine::~ExecutionEngine()
#endif
}
-ExecutionContext *ExecutionEngine::currentContext() const
-{
- return static_cast<ExecutionContext *>(&currentStackFrame->jsFrame->context);
-}
-
#if QT_CONFIG(qml_debug)
void ExecutionEngine::setDebugger(Debugging::Debugger *debugger)
{
@@ -768,7 +901,7 @@ void ExecutionEngine::setProfiler(Profiling::Profiler *profiler)
void ExecutionEngine::initRootContext()
{
Scope scope(this);
- Scoped<ExecutionContext> r(scope, memoryManager->allocManaged<ExecutionContext>(sizeof(ExecutionContext::Data)));
+ Scoped<ExecutionContext> r(scope, memoryManager->allocManaged<ExecutionContext>());
r->d_unchecked()->init(Heap::ExecutionContext::Type_GlobalContext);
r->d()->activation.set(this, globalObject->d());
jsObjects[RootContext] = r;
@@ -802,13 +935,13 @@ Heap::Object *ExecutionEngine::newObject(Heap::InternalClass *internalClass)
Heap::String *ExecutionEngine::newString(const QString &s)
{
- return memoryManager->allocWithStringData<String>(s.length() * sizeof(QChar), s);
+ return memoryManager->allocWithStringData<String>(s.size() * sizeof(QChar), s);
}
Heap::String *ExecutionEngine::newIdentifier(const QString &text)
{
Scope scope(this);
- ScopedString s(scope, memoryManager->allocWithStringData<String>(text.length() * sizeof(QChar), text));
+ ScopedString s(scope, memoryManager->allocWithStringData<String>(text.size() * sizeof(QChar), text));
s->toPropertyKey();
return s->d();
}
@@ -888,24 +1021,35 @@ Heap::ArrayBuffer *ExecutionEngine::newArrayBuffer(size_t length)
return memoryManager->allocate<ArrayBuffer>(length);
}
+Heap::DateObject *ExecutionEngine::newDateObject(double dateTime)
+{
+ return memoryManager->allocate<DateObject>(dateTime);
+}
-Heap::DateObject *ExecutionEngine::newDateObject(const Value &value)
+Heap::DateObject *ExecutionEngine::newDateObject(const QDateTime &dateTime)
{
- return memoryManager->allocate<DateObject>(value);
+ return memoryManager->allocate<DateObject>(dateTime);
}
-Heap::DateObject *ExecutionEngine::newDateObject(const QDateTime &dt)
+Heap::DateObject *ExecutionEngine::newDateObject(
+ QDate date, Heap::Object *parent, int index, uint flags)
{
- Scope scope(this);
- Scoped<DateObject> object(scope, memoryManager->allocate<DateObject>(dt));
- return object->d();
+ return memoryManager->allocate<DateObject>(
+ date, parent, index, Heap::ReferenceObject::Flags(flags));
}
-Heap::DateObject *ExecutionEngine::newDateObjectFromTime(const QTime &t)
+Heap::DateObject *ExecutionEngine::newDateObject(
+ QTime time, Heap::Object *parent, int index, uint flags)
{
- Scope scope(this);
- Scoped<DateObject> object(scope, memoryManager->allocate<DateObject>(t));
- return object->d();
+ return memoryManager->allocate<DateObject>(
+ time, parent, index, Heap::ReferenceObject::Flags(flags));
+}
+
+Heap::DateObject *ExecutionEngine::newDateObject(
+ QDateTime dateTime, Heap::Object *parent, int index, uint flags)
+{
+ return memoryManager->allocate<DateObject>(
+ dateTime, parent, index, Heap::ReferenceObject::Flags(flags));
}
Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QString &pattern, int flags)
@@ -920,11 +1064,6 @@ Heap::RegExpObject *ExecutionEngine::newRegExpObject(RegExp *re)
return memoryManager->allocate<RegExpObject>(re);
}
-Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegExp &re)
-{
- return memoryManager->allocate<RegExpObject>(re);
-}
-
#if QT_CONFIG(regularexpression)
Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegularExpression &re)
{
@@ -932,6 +1071,24 @@ Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegularExpression &r
}
#endif
+Heap::UrlObject *ExecutionEngine::newUrlObject()
+{
+ return memoryManager->allocate<UrlObject>();
+}
+
+Heap::UrlObject *ExecutionEngine::newUrlObject(const QUrl &url)
+{
+ Scope scope(this);
+ Scoped<UrlObject> urlObject(scope, newUrlObject());
+ urlObject->setUrl(url);
+ return urlObject->d();
+}
+
+Heap::UrlSearchParamsObject *ExecutionEngine::newUrlSearchParamsObject()
+{
+ return memoryManager->allocate<UrlSearchParamsObject>();
+}
+
Heap::Object *ExecutionEngine::newErrorObject(const Value &value)
{
return ErrorObject::create<ErrorObject>(this, value, errorCtor());
@@ -1021,9 +1178,9 @@ Heap::Object *ExecutionEngine::newEvalErrorObject(const QString &message)
return ErrorObject::create<EvalErrorObject>(this, message);
}
-Heap::Object *ExecutionEngine::newVariantObject(const QVariant &v)
+Heap::Object *ExecutionEngine::newVariantObject(const QMetaType type, const void *data)
{
- return memoryManager->allocate<VariantObject>(v);
+ return memoryManager->allocate<VariantObject>(type, data);
}
Heap::Object *ExecutionEngine::newForInIteratorObject(Object *o)
@@ -1050,21 +1207,9 @@ Heap::Object *ExecutionEngine::newArrayIteratorObject(Object *o)
Heap::QmlContext *ExecutionEngine::qmlContext() const
{
- if (!currentStackFrame)
- return nullptr;
- Heap::ExecutionContext *ctx = currentContext()->d();
-
- if (ctx->type != Heap::ExecutionContext::Type_QmlContext && !ctx->outer)
- return nullptr;
-
- while (ctx->outer && ctx->outer->type != Heap::ExecutionContext::Type_GlobalContext)
- ctx = ctx->outer;
-
- Q_ASSERT(ctx);
- if (ctx->type != Heap::ExecutionContext::Type_QmlContext)
- return nullptr;
-
- return static_cast<Heap::QmlContext *>(ctx);
+ return currentStackFrame
+ ? static_cast<Heap::QmlContext *>(qmlContext(currentContext()->d()))
+ : nullptr;
}
QObject *ExecutionEngine::qmlScopeObject() const
@@ -1076,19 +1221,17 @@ QObject *ExecutionEngine::qmlScopeObject() const
return ctx->qml()->scopeObject;
}
-QQmlContextData *ExecutionEngine::callingQmlContext() const
+QQmlRefPointer<QQmlContextData> ExecutionEngine::callingQmlContext() const
{
Heap::QmlContext *ctx = qmlContext();
if (!ctx)
return nullptr;
- return ctx->qml()->context->contextData();
+ return ctx->qml()->context;
}
StackTrace ExecutionEngine::stackTrace(int frameLimit) const
{
- Scope scope(const_cast<ExecutionEngine *>(this));
- ScopedString name(scope);
StackTrace stack;
CppStackFrame *f = currentStackFrame;
@@ -1096,16 +1239,18 @@ StackTrace ExecutionEngine::stackTrace(int frameLimit) const
QV4::StackFrame frame;
frame.source = f->source();
frame.function = f->function();
- frame.line = qAbs(f->lineNumber());
- frame.column = -1;
+ frame.line = f->lineNumber();
+
stack.append(frame);
- if (f->isTailCalling) {
- QV4::StackFrame frame;
- frame.function = QStringLiteral("[elided tail calls]");
- stack.append(frame);
+ if (f->isJSTypesFrame()) {
+ if (static_cast<JSTypesStackFrame *>(f)->isTailCalling()) {
+ QV4::StackFrame frame;
+ frame.function = QStringLiteral("[elided tail calls]");
+ stack.append(frame);
+ }
}
--frameLimit;
- f = f->parent;
+ f = f->parentFrame();
}
return stack;
@@ -1132,7 +1277,7 @@ static inline char *v4StackTrace(const ExecutionContext *context)
const QString fileName = url.isLocalFile() ? url.toLocalFile() : url.toString();
str << "frame={level=\"" << i << "\",func=\"" << stackTrace.at(i).function
<< "\",file=\"" << fileName << "\",fullname=\"" << fileName
- << "\",line=\"" << stackTrace.at(i).line << "\",language=\"js\"}";
+ << "\",line=\"" << qAbs(stackTrace.at(i).line) << "\",language=\"js\"}";
}
}
str << ']';
@@ -1163,7 +1308,7 @@ QUrl ExecutionEngine::resolvedUrl(const QString &file)
base = f->v4Function->finalUrl();
break;
}
- f = f->parent;
+ f = f->parentFrame();
}
if (base.isEmpty() && globalCode)
@@ -1177,17 +1322,15 @@ QUrl ExecutionEngine::resolvedUrl(const QString &file)
void ExecutionEngine::markObjects(MarkStack *markStack)
{
- for (int i = 0; i < NClasses; ++i)
- if (classes[i])
- classes[i]->mark(markStack);
- markStack->drain();
+ for (int i = 0; i < NClasses; ++i) {
+ if (Heap::InternalClass *c = classes[i])
+ c->mark(markStack);
+ }
identifierTable->markObjects(markStack);
- for (auto compilationUnit: compilationUnits) {
+ for (const auto &compilationUnit : std::as_const(m_compilationUnits))
compilationUnit->markObjects(markStack);
- markStack->drain();
- }
}
ReturnedValue ExecutionEngine::throwError(const Value &value)
@@ -1329,7 +1472,7 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError()
if (!trace.isEmpty()) {
QV4::StackFrame frame = trace.constFirst();
error.setUrl(QUrl(frame.source));
- error.setLine(frame.line);
+ error.setLine(qAbs(frame.line));
error.setColumn(frame.column);
}
QV4::Scoped<QV4::ErrorObject> errorObj(scope, exception);
@@ -1340,47 +1483,47 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError()
// Variant conversion code
typedef QSet<QV4::Heap::Object *> V4ObjectSet;
-static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects);
-static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &value);
-static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr);
-static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &value,
- const QByteArray &targetType,
- void **result);
-static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVariantList &lst);
-static QV4::ReturnedValue sequentialIterableToJS(QV4::ExecutionEngine *v4, const QSequentialIterable &lst);
+enum class JSToQVariantConversionBehavior {Never, Safish, Aggressive };
+static QVariant toVariant(
+ const QV4::Value &value, QMetaType typeHint, JSToQVariantConversionBehavior conversionBehavior,
+ V4ObjectSet *visitedObjects);
+static QObject *qtObjectFromJS(const QV4::Value &value);
+static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr,
+ JSToQVariantConversionBehavior behavior = JSToQVariantConversionBehavior::Safish);
+static bool convertToNativeQObject(const QV4::Value &value, QMetaType targetType, void **result);
static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVariantMap &vmap);
static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant &value)
{
- return v4->metaTypeToJS(value.userType(), value.constData());
-}
-
-
-QVariant ExecutionEngine::toVariant(const Value &value, int typeHint, bool createJSValueForObjects)
-{
- return ::toVariant(this, value, typeHint, createJSValueForObjects, nullptr);
+ return v4->metaTypeToJS(value.metaType(), value.constData());
}
-
-static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects)
+static QVariant toVariant(const QV4::Value &value, QMetaType metaType, JSToQVariantConversionBehavior conversionBehavior,
+ V4ObjectSet *visitedObjects)
{
Q_ASSERT (!value.isEmpty());
- QV4::Scope scope(e);
if (const QV4::VariantObject *v = value.as<QV4::VariantObject>())
return v->d()->data();
- if (typeHint == QVariant::Bool)
+ if (metaType == QMetaType::fromType<bool>())
return QVariant(value.toBoolean());
- if (typeHint == QMetaType::QJsonValue)
+ if (metaType == QMetaType::fromType<double>())
+ return QVariant(value.toNumber());
+
+ if (metaType == QMetaType::fromType<float>())
+ return QVariant(float(value.toNumber()));
+
+ if (metaType == QMetaType::fromType<QJsonValue>())
return QVariant::fromValue(QV4::JsonObject::toJsonValue(value));
- if (typeHint == qMetaTypeId<QJSValue>())
- return QVariant::fromValue(QJSValue(e, value.asReturnedValue()));
+ if (metaType == QMetaType::fromType<QJSValue>())
+ return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(value.asReturnedValue()));
- if (value.as<QV4::Object>()) {
- QV4::ScopedObject object(scope, value);
- if (typeHint == QMetaType::QJsonObject
+ if (const QV4::Object *o = value.as<QV4::Object>()) {
+ QV4::Scope scope(o->engine());
+ QV4::ScopedObject object(scope, o);
+ if (metaType == QMetaType::fromType<QJsonObject>()
&& !value.as<ArrayObject>() && !value.as<FunctionObject>()) {
return QVariant::fromValue(QV4::JsonObject::toJsonObject(object));
} else if (QV4::QObjectWrapper *wrapper = object->as<QV4::QObjectWrapper>()) {
@@ -1393,16 +1536,15 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int
return v->toVariant();
} else if (QV4::QmlListWrapper *l = object->as<QV4::QmlListWrapper>()) {
return l->toVariant();
-#if QT_CONFIG(qml_sequence_object)
- } else if (object->isListType()) {
- return QV4::SequencePrototype::toVariant(object);
-#endif
+ } else if (QV4::Sequence *s = object->as<QV4::Sequence>()) {
+ return QV4::SequencePrototype::toVariant(s);
}
}
- if (value.as<ArrayObject>()) {
- QV4::ScopedArrayObject a(scope, value);
- if (typeHint == qMetaTypeId<QList<QObject *> >()) {
+ if (const QV4::ArrayObject *o = value.as<ArrayObject>()) {
+ QV4::Scope scope(o->engine());
+ QV4::ScopedArrayObject a(scope, o);
+ if (metaType == QMetaType::fromType<QList<QObject *>>()) {
QList<QObject *> list;
uint length = a->getLength();
QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope);
@@ -1416,16 +1558,64 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int
}
return QVariant::fromValue<QList<QObject*> >(list);
- } else if (typeHint == QMetaType::QJsonArray) {
+ } else if (metaType == QMetaType::fromType<QJsonArray>()) {
return QVariant::fromValue(QV4::JsonObject::toJsonArray(a));
}
-#if QT_CONFIG(qml_sequence_object)
- bool succeeded = false;
- QVariant retn = QV4::SequencePrototype::toVariant(value, typeHint, &succeeded);
- if (succeeded)
+ QVariant retn = QV4::SequencePrototype::toVariant(value, metaType);
+ if (retn.isValid())
return retn;
-#endif
+
+ if (metaType.isValid()) {
+ retn = QVariant(metaType, nullptr);
+ auto retnAsIterable = retn.value<QSequentialIterable>();
+ if (retnAsIterable.metaContainer().canAddValue()) {
+ QMetaType valueMetaType = retnAsIterable.metaContainer().valueMetaType();
+ auto const length = a->getLength();
+ QV4::ScopedValue arrayValue(scope);
+ for (qint64 i = 0; i < length; ++i) {
+ arrayValue = a->get(i);
+ QVariant asVariant = QQmlValueTypeProvider::createValueType(
+ arrayValue, valueMetaType);
+ if (asVariant.isValid()) {
+ retnAsIterable.metaContainer().addValue(retn.data(), asVariant.constData());
+ continue;
+ }
+
+ if (QMetaType::canConvert(QMetaType::fromType<QJSValue>(), valueMetaType)) {
+ // before attempting a conversion from the concrete types,
+ // check if there exists a conversion from QJSValue -> out type
+ // prefer that one for compatibility reasons
+ asVariant = QVariant::fromValue(QJSValuePrivate::fromReturnedValue(
+ arrayValue->asReturnedValue()));
+ if (asVariant.convert(valueMetaType)) {
+ retnAsIterable.metaContainer().addValue(retn.data(), asVariant.constData());
+ continue;
+ }
+ }
+
+ asVariant = toVariant(arrayValue, valueMetaType, JSToQVariantConversionBehavior::Never, visitedObjects);
+ if (valueMetaType == QMetaType::fromType<QVariant>()) {
+ retnAsIterable.metaContainer().addValue(retn.data(), &asVariant);
+ } else {
+ auto originalType = asVariant.metaType();
+ bool couldConvert = asVariant.convert(valueMetaType);
+ if (!couldConvert && originalType.isValid()) {
+ // If the original type was void, we're converting a "hole" in a sparse
+ // array. There is no point in warning about that.
+ qWarning().noquote()
+ << QLatin1String("Could not convert array value "
+ "at position %1 from %2 to %3")
+ .arg(QString::number(i),
+ QString::fromUtf8(originalType.name()),
+ QString::fromUtf8(valueMetaType.name()));
+ }
+ retnAsIterable.metaContainer().addValue(retn.data(), asVariant.constData());
+ }
+ }
+ return retn;
+ }
+ }
}
if (value.isUndefined())
@@ -1441,38 +1631,93 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int
if (String *s = value.stringValue()) {
const QString &str = s->toQString();
// QChars are stored as a strings
- if (typeHint == QVariant::Char && str.size() == 1)
+ if (metaType == QMetaType::fromType<QChar>() && str.size() == 1)
return str.at(0);
return str;
}
-#if QT_CONFIG(qml_locale)
- if (const QV4::QQmlLocaleData *ld = value.as<QV4::QQmlLocaleData>())
- return *ld->d()->locale;
-#endif
- if (const QV4::DateObject *d = value.as<DateObject>())
+ if (const QV4::DateObject *d = value.as<DateObject>()) {
+ // NOTE: since we convert QTime to JS Date,
+ // round trip will change the variant type (to QDateTime)!
+
+ if (metaType == QMetaType::fromType<QDate>())
+ return DateObject::dateTimeToDate(d->toQDateTime());
+
+ if (metaType == QMetaType::fromType<QTime>())
+ return d->toQDateTime().time();
+
+ if (metaType == QMetaType::fromType<QString>())
+ return d->toString();
+
return d->toQDateTime();
+ }
+ if (const QV4::UrlObject *d = value.as<UrlObject>())
+ return d->toQUrl();
if (const ArrayBuffer *d = value.as<ArrayBuffer>())
return d->asByteArray();
- // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)!
+ if (const Symbol *symbol = value.as<Symbol>()) {
+ return conversionBehavior == JSToQVariantConversionBehavior::Never
+ ? QVariant::fromValue(QJSValuePrivate::fromReturnedValue(symbol->asReturnedValue()))
+ : symbol->descriptiveString();
+ }
- QV4::ScopedObject o(scope, value);
- Q_ASSERT(o);
+ const QV4::Object *object = value.as<QV4::Object>();
+ Q_ASSERT(object);
+ QV4::Scope scope(object->engine());
+ QV4::ScopedObject o(scope, object);
- if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>()) {
#if QT_CONFIG(regularexpression)
- if (typeHint != QMetaType::QRegExp)
- return re->toQRegularExpression();
+ if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>())
+ return re->toQRegularExpression();
#endif
- return re->toQRegExp();
+
+ if (metaType.isValid() && !(metaType.flags() & QMetaType::PointerToQObject)) {
+ const QVariant result = QQmlValueTypeProvider::createValueType(value, metaType);
+ if (result.isValid())
+ return result;
}
- if (createJSValueForObjects)
- return QVariant::fromValue(QJSValue(scope.engine, o->asReturnedValue()));
+ if (conversionBehavior == JSToQVariantConversionBehavior::Never)
+ return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue()));
+
+ return objectToVariant(o, visitedObjects, conversionBehavior);
+}
+
+QVariant ExecutionEngine::toVariantLossy(const Value &value)
+{
+ return ::toVariant(value, QMetaType(), JSToQVariantConversionBehavior::Aggressive, nullptr);
+}
+
+QVariant ExecutionEngine::toVariant(
+ const Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols)
+{
+ auto behavior = createJSValueForObjectsAndSymbols ? JSToQVariantConversionBehavior::Never
+ : JSToQVariantConversionBehavior::Safish;
+ return ::toVariant(value, typeHint, behavior, nullptr);
+}
+
+static QVariantMap objectToVariantMap(const QV4::Object *o, V4ObjectSet *visitedObjects,
+ JSToQVariantConversionBehavior conversionBehvior)
+{
+ QVariantMap map;
+ QV4::Scope scope(o->engine());
+ QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
+ QV4::ScopedValue name(scope);
+ QV4::ScopedValue val(scope);
+ while (1) {
+ name = it.nextPropertyNameAsString(val);
+ if (name->isNull())
+ break;
- return objectToVariant(e, o, visitedObjects);
+ QString key = name->toQStringNoThrow();
+ map.insert(key, ::toVariant(
+ val, /*type hint*/ QMetaType {},
+ conversionBehvior, visitedObjects));
+ }
+ return map;
}
-static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects)
+static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects,
+ JSToQVariantConversionBehavior conversionBehvior)
{
Q_ASSERT(o);
@@ -1492,7 +1737,7 @@ static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V
QVariant result;
if (o->as<ArrayObject>()) {
- QV4::Scope scope(e);
+ QV4::Scope scope(o->engine());
QV4::ScopedArrayObject a(scope, o->asReturnedValue());
QV4::ScopedValue v(scope);
QVariantList list;
@@ -1500,37 +1745,51 @@ static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V
int length = a->getLength();
for (int ii = 0; ii < length; ++ii) {
v = a->get(ii);
- list << ::toVariant(e, v, -1, /*createJSValueForObjects*/false, visitedObjects);
+ list << ::toVariant(v, QMetaType {}, conversionBehvior,
+ visitedObjects);
}
result = list;
- } else if (!o->as<FunctionObject>()) {
- QVariantMap map;
- QV4::Scope scope(e);
- QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
- QV4::ScopedValue name(scope);
- QV4::ScopedValue val(scope);
- while (1) {
- name = it.nextPropertyNameAsString(val);
- if (name->isNull())
- break;
-
- QString key = name->toQStringNoThrow();
- map.insert(key, ::toVariant(e, val, /*type hint*/-1, /*createJSValueForObjects*/false, visitedObjects));
- }
-
- result = map;
+ } else if (o->getPrototypeOf() == o->engine()->objectPrototype()->d()
+ || (conversionBehvior == JSToQVariantConversionBehavior::Aggressive &&
+ !o->as<QV4::FunctionObject>())) {
+ /* FunctionObject is excluded for historical reasons, even though
+ objects with a custom prototype risk losing information
+ But the Aggressive path is used only in QJSValue::toVariant
+ which is documented to be lossy
+ */
+ result = objectToVariantMap(o, visitedObjects, conversionBehvior);
+ } else {
+ // If it's not a plain object, we can only save it as QJSValue.
+ result = QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue()));
}
visitedObjects->remove(o->d());
return result;
}
-QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant)
+/*!
+ \internal
+
+ Transform the given \a metaType and \a ptr into a JavaScript representation.
+ */
+QV4::ReturnedValue ExecutionEngine::fromData(
+ QMetaType metaType, const void *ptr,
+ QV4::Heap::Object *container, int property, uint flags)
{
- int type = variant.userType();
- const void *ptr = variant.constData();
+ const auto createSequence = [&](const QMetaSequence metaSequence) {
+ QV4::Scope scope(this);
+ QV4::Scoped<Sequence> sequence(scope);
+ if (container) {
+ return QV4::SequencePrototype::newSequence(
+ this, metaType, metaSequence, ptr,
+ container, property, Heap::ReferenceObject::Flags(flags));
+ } else {
+ return QV4::SequencePrototype::fromData(this, metaType, metaSequence, ptr);
+ }
+ };
+ const int type = metaType.id();
if (type < QMetaType::User) {
switch (QMetaType::Type(type)) {
case QMetaType::UnknownType:
@@ -1545,6 +1804,10 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant)
return QV4::Encode(*reinterpret_cast<const int*>(ptr));
case QMetaType::UInt:
return QV4::Encode(*reinterpret_cast<const uint*>(ptr));
+ case QMetaType::Long:
+ return QV4::Encode((double)*reinterpret_cast<const long *>(ptr));
+ case QMetaType::ULong:
+ return QV4::Encode((double)*reinterpret_cast<const ulong *>(ptr));
case QMetaType::LongLong:
return QV4::Encode((double)*reinterpret_cast<const qlonglong*>(ptr));
case QMetaType::ULongLong:
@@ -1565,35 +1828,34 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant)
return QV4::Encode((int)*reinterpret_cast<const char*>(ptr));
case QMetaType::UChar:
return QV4::Encode((int)*reinterpret_cast<const unsigned char*>(ptr));
+ case QMetaType::SChar:
+ return QV4::Encode((int)*reinterpret_cast<const signed char*>(ptr));
case QMetaType::QChar:
return newString(*reinterpret_cast<const QChar *>(ptr))->asReturnedValue();
+ case QMetaType::Char16:
+ return newString(QChar(*reinterpret_cast<const char16_t *>(ptr)))->asReturnedValue();
case QMetaType::QDateTime:
- return QV4::Encode(newDateObject(*reinterpret_cast<const QDateTime *>(ptr)));
+ return QV4::Encode(newDateObject(
+ *reinterpret_cast<const QDateTime *>(ptr),
+ container, property, flags));
case QMetaType::QDate:
- return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast<const QDate *>(ptr), QTime(0, 0, 0), Qt::UTC)));
+ return QV4::Encode(newDateObject(
+ *reinterpret_cast<const QDate *>(ptr),
+ container, property, flags));
case QMetaType::QTime:
- return QV4::Encode(newDateObjectFromTime(*reinterpret_cast<const QTime *>(ptr)));
- case QMetaType::QRegExp:
- return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegExp *>(ptr)));
+ return QV4::Encode(newDateObject(
+ *reinterpret_cast<const QTime *>(ptr),
+ container, property, flags));
#if QT_CONFIG(regularexpression)
case QMetaType::QRegularExpression:
return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegularExpression *>(ptr)));
#endif
case QMetaType::QObjectStar:
return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(ptr));
-#if QT_CONFIG(qml_sequence_object)
case QMetaType::QStringList:
- {
- bool succeeded = false;
- QV4::Scope scope(this);
- QV4::ScopedValue retn(scope, QV4::SequencePrototype::fromVariant(this, variant, &succeeded));
- if (succeeded)
- return retn->asReturnedValue();
- return QV4::Encode(newArrayObject(*reinterpret_cast<const QStringList *>(ptr)));
- }
-#endif
+ return createSequence(QMetaSequence::fromContainer<QStringList>());
case QMetaType::QVariantList:
- return variantListToJS(this, *reinterpret_cast<const QVariantList *>(ptr));
+ return createSequence(QMetaSequence::fromContainer<QVariantList>());
case QMetaType::QVariantMap:
return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(ptr));
case QMetaType::QJsonValue:
@@ -1602,112 +1864,123 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant)
return QV4::JsonObject::fromJsonObject(this, *reinterpret_cast<const QJsonObject *>(ptr));
case QMetaType::QJsonArray:
return QV4::JsonObject::fromJsonArray(this, *reinterpret_cast<const QJsonArray *>(ptr));
-#if QT_CONFIG(qml_locale)
- case QMetaType::QLocale:
- return QQmlLocale::wrap(this, *reinterpret_cast<const QLocale*>(ptr));
-#endif
case QMetaType::QPixmap:
case QMetaType::QImage:
// Scarce value types
- return QV4::Encode(newVariantObject(variant));
+ return QV4::Encode(newVariantObject(metaType, ptr));
default:
break;
}
+ }
- if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(type))
- return QV4::QQmlValueTypeWrapper::create(this, variant, vtmo, type);
- } else {
- QV4::Scope scope(this);
- if (type == qMetaTypeId<QQmlListReference>()) {
- typedef QQmlListReferencePrivate QDLRP;
- QDLRP *p = QDLRP::get((QQmlListReference*)const_cast<void *>(ptr));
- if (p->object) {
- return QV4::QmlListWrapper::create(scope.engine, p->property, p->propertyType);
- } else {
- return QV4::Encode::null();
- }
- } else if (type == qMetaTypeId<QJSValue>()) {
- const QJSValue *value = reinterpret_cast<const QJSValue *>(ptr);
- return QJSValuePrivate::convertedToValue(this, *value);
- } else if (type == qMetaTypeId<QList<QObject *> >()) {
- // XXX Can this be made more by using Array as a prototype and implementing
- // directly against QList<QObject*>?
- const QList<QObject *> &list = *(const QList<QObject *>*)ptr;
- QV4::ScopedArrayObject a(scope, newArrayObject());
- a->arrayReserve(list.count());
- QV4::ScopedValue v(scope);
- for (int ii = 0; ii < list.count(); ++ii)
- a->arrayPut(ii, (v = QV4::QObjectWrapper::wrap(this, list.at(ii))));
- a->setArrayLengthUnchecked(list.count());
- return a.asReturnedValue();
- } else if (QMetaType::typeFlags(type) & QMetaType::PointerToQObject) {
+ if (metaType.flags() & QMetaType::IsEnumeration)
+ return fromData(metaType.underlyingType(), ptr, container, property, flags);
+
+ QV4::Scope scope(this);
+ if (metaType == QMetaType::fromType<QQmlListReference>()) {
+ typedef QQmlListReferencePrivate QDLRP;
+ QDLRP *p = QDLRP::get((QQmlListReference*)const_cast<void *>(ptr));
+ if (p->object)
+ return QV4::QmlListWrapper::create(scope.engine, p->property, p->propertyType);
+ else
+ return QV4::Encode::null();
+ } else if (auto flags = metaType.flags(); flags & QMetaType::IsQmlList) {
+ // casting to QQmlListProperty<QObject> is slightly nasty, but it's the
+ // same QQmlListReference does.
+ const auto *p = static_cast<const QQmlListProperty<QObject> *>(ptr);
+ if (p->object)
+ return QV4::QmlListWrapper::create(scope.engine, *p, metaType);
+ else
+ return QV4::Encode::null();
+ } else if (metaType == QMetaType::fromType<QJSValue>()) {
+ return QJSValuePrivate::convertToReturnedValue(
+ this, *reinterpret_cast<const QJSValue *>(ptr));
+ } else if (metaType == QMetaType::fromType<QList<QObject *> >()) {
+ // XXX Can this be made more by using Array as a prototype and implementing
+ // directly against QList<QObject*>?
+ const QList<QObject *> &list = *(const QList<QObject *>*)ptr;
+ QV4::ScopedArrayObject a(scope, newArrayObject());
+ a->arrayReserve(list.size());
+ QV4::ScopedValue v(scope);
+ for (int ii = 0; ii < list.size(); ++ii)
+ a->arrayPut(ii, (v = QV4::QObjectWrapper::wrap(this, list.at(ii))));
+ a->setArrayLengthUnchecked(list.size());
+ return a.asReturnedValue();
+ } else if (auto flags = metaType.flags(); flags & QMetaType::PointerToQObject) {
+ if (flags.testFlag(QMetaType::IsConst))
+ return QV4::QObjectWrapper::wrapConst(this, *reinterpret_cast<QObject* const *>(ptr));
+ else
return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(ptr));
+ } else if (metaType == QMetaType::fromType<QJSPrimitiveValue>()) {
+ const QJSPrimitiveValue *primitive = static_cast<const QJSPrimitiveValue *>(ptr);
+ switch (primitive->type()) {
+ case QJSPrimitiveValue::Boolean:
+ return Encode(primitive->asBoolean());
+ case QJSPrimitiveValue::Integer:
+ return Encode(primitive->asInteger());
+ case QJSPrimitiveValue::String:
+ return newString(primitive->asString())->asReturnedValue();
+ case QJSPrimitiveValue::Undefined:
+ return Encode::undefined();
+ case QJSPrimitiveValue::Null:
+ return Encode::null();
+ case QJSPrimitiveValue::Double:
+ return Encode(primitive->asDouble());
}
+ }
- bool objOk;
- QObject *obj = QQmlMetaType::toQObject(variant, &objOk);
- if (objOk)
- return QV4::QObjectWrapper::wrap(this, obj);
+ if (const QMetaObject *vtmo = QQmlMetaType::metaObjectForValueType(metaType)) {
+ if (container) {
+ return QV4::QQmlValueTypeWrapper::create(
+ this, ptr, vtmo, metaType,
+ container, property, Heap::ReferenceObject::Flags(flags));
+ } else {
+ return QV4::QQmlValueTypeWrapper::create(this, ptr, vtmo, metaType);
+ }
+ }
-#if QT_CONFIG(qml_sequence_object)
- bool succeeded = false;
- QV4::ScopedValue retn(scope, QV4::SequencePrototype::fromVariant(this, variant, &succeeded));
- if (succeeded)
- return retn->asReturnedValue();
-#endif
+ const QQmlType listType = QQmlMetaType::qmlListType(metaType);
+ if (listType.isSequentialContainer())
+ return createSequence(listType.listMetaSequence());
- if (QMetaType::hasRegisteredConverterFunction(type, qMetaTypeId<QtMetaTypePrivate::QSequentialIterableImpl>())) {
- QSequentialIterable lst = variant.value<QSequentialIterable>();
- return sequentialIterableToJS(this, lst);
- }
+ QSequentialIterable iterable;
+ if (QMetaType::convert(metaType, ptr, QMetaType::fromType<QSequentialIterable>(), &iterable)) {
- if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(type))
- return QV4::QQmlValueTypeWrapper::create(this, variant, vtmo, type);
- }
+ // If the resulting iterable is useful for anything, turn it into a QV4::Sequence.
+ const QMetaSequence sequence = iterable.metaContainer();
+ if (sequence.hasSize() && sequence.canGetValueAtIndex())
+ return createSequence(sequence);
- // XXX TODO: To be compatible, we still need to handle:
- // + QObjectList
- // + QList<int>
+ // As a last resort, try to read the contents of the container via an iterator
+ // and build a JS array from them.
+ if (sequence.hasConstIterator() && sequence.canGetValueAtConstIterator()) {
+ QV4::ScopedArrayObject a(scope, newArrayObject());
+ for (auto it = iterable.constBegin(), end = iterable.constEnd(); it != end; ++it)
+ a->push_back(fromVariant(*it));
+ return a.asReturnedValue();
+ }
+ }
- return QV4::Encode(newVariantObject(variant));
+ return QV4::Encode(newVariantObject(metaType, ptr));
}
-QVariantMap ExecutionEngine::variantMapFromJS(const Object *o)
+QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant)
{
- return objectToVariant(this, o).toMap();
+ return fromData(variant.metaType(), variant.constData());
}
-
-// Converts a QVariantList to JS.
-// The result is a new Array object with length equal to the length
-// of the QVariantList, and the elements being the QVariantList's
-// elements converted to JS, recursively.
-static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVariantList &lst)
+ReturnedValue ExecutionEngine::fromVariant(
+ const QVariant &variant, Heap::Object *parent, int property, uint flags)
{
- QV4::Scope scope(v4);
- QV4::ScopedArrayObject a(scope, v4->newArrayObject());
- a->arrayReserve(lst.size());
- QV4::ScopedValue v(scope);
- for (int i = 0; i < lst.size(); i++)
- a->arrayPut(i, (v = variantToJS(v4, lst.at(i))));
- a->setArrayLengthUnchecked(lst.size());
- return a.asReturnedValue();
+ return fromData(variant.metaType(), variant.constData(), parent, property, flags);
}
-// Converts a QSequentialIterable to JS.
-// The result is a new Array object with length equal to the length
-// of the QSequentialIterable, and the elements being the QSequentialIterable's
-// elements converted to JS, recursively.
-static QV4::ReturnedValue sequentialIterableToJS(QV4::ExecutionEngine *v4, const QSequentialIterable &lst)
+QVariantMap ExecutionEngine::variantMapFromJS(const Object *o)
{
- QV4::Scope scope(v4);
- QV4::ScopedArrayObject a(scope, v4->newArrayObject());
- a->arrayReserve(lst.size());
- QV4::ScopedValue v(scope);
- for (int i = 0; i < lst.size(); i++)
- a->arrayPut(i, (v = variantToJS(v4, lst.at(i))));
- a->setArrayLengthUnchecked(lst.size());
- return a.asReturnedValue();
+ Q_ASSERT(o);
+ V4ObjectSet visitedObjects;
+ visitedObjects.insert(o->d());
+ return objectToVariantMap(o, &visitedObjects, JSToQVariantConversionBehavior::Safish);
}
// Converts a QVariantMap to JS.
@@ -1735,27 +2008,49 @@ static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVarian
// Converts the meta-type defined by the given type and data to JS.
// Returns the value if conversion succeeded, an empty handle otherwise.
-QV4::ReturnedValue ExecutionEngine::metaTypeToJS(int type, const void *data)
+QV4::ReturnedValue ExecutionEngine::metaTypeToJS(QMetaType type, const void *data)
{
Q_ASSERT(data != nullptr);
- QVariant variant(type, data);
- if (QMetaType::Type(variant.type()) == QMetaType::QVariant) {
+ if (type == QMetaType::fromType<QVariant>()) {
// unwrap it: this is tested in QJSEngine, and makes the most sense for
// end-user code too.
- return variantToJS(this, *reinterpret_cast<const QVariant*>(data));
+ return fromVariant(*reinterpret_cast<const QVariant*>(data));
+ } else if (type == QMetaType::fromType<QUrl>()) {
+ // Create a proper URL object here, rather than a variant.
+ return newUrlObject(*reinterpret_cast<const QUrl *>(data))->asReturnedValue();
}
- return fromVariant(variant);
+
+ return fromData(type, data);
}
int ExecutionEngine::maxJSStackSize() const
{
- return m_maxJSStackSize;
+ return s_maxJSStackSize;
}
int ExecutionEngine::maxGCStackSize() const
{
- return m_maxGCStackSize;
+ return s_maxGCStackSize;
+}
+
+/*!
+ \internal
+ Returns \a length converted to int if its safe to
+ pass to \c Scope::alloc.
+ Otherwise it throws a RangeError, and returns 0.
+ */
+int ExecutionEngine::safeForAllocLength(qint64 len64)
+{
+ if (len64 < 0ll || len64 > qint64(std::numeric_limits<int>::max())) {
+ throwRangeError(QStringLiteral("Invalid array length."));
+ return 0;
+ }
+ if (len64 > qint64(this->jsStackLimit - this->jsStackTop)) {
+ throwRangeError(QStringLiteral("Array too large for apply()."));
+ return 0;
+ }
+ return len64;
}
ReturnedValue ExecutionEngine::global()
@@ -1766,9 +2061,19 @@ ReturnedValue ExecutionEngine::global()
QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compileModule(const QUrl &url)
{
QQmlMetaType::CachedUnitLookupError cacheError = QQmlMetaType::CachedUnitLookupError::NoError;
- if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(url, &cacheError)) {
- return ExecutableCompilationUnit::create(
- QV4::CompiledData::CompilationUnit(cachedUnit, url.fileName(), url.toString()));
+ const DiskCacheOptions options = diskCacheOptions();
+ if (const QQmlPrivate::CachedQmlUnit *cachedUnit = (options & DiskCache::Aot)
+ ? QQmlMetaType::findCachedCompilationUnit(
+ url,
+ (options & DiskCache::AotByteCode)
+ ? QQmlMetaType::AcceptUntyped
+ : QQmlMetaType::RequireFullyTyped,
+ &cacheError)
+ : nullptr) {
+ return executableCompilationUnit(
+ QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>(
+ cachedUnit->qmlData, cachedUnit->aotCompiledFunctions, url.fileName(),
+ url.toString()));
}
QFile f(QQmlFile::urlToLocalFileOrQrc(url));
@@ -1794,73 +2099,192 @@ QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compileModule(
sourceCode, sourceTimeStamp, &diagnostics);
for (const QQmlJS::DiagnosticMessage &m : diagnostics) {
if (m.isError()) {
- throwSyntaxError(m.message, url.toString(), m.line, m.column);
+ throwSyntaxError(m.message, url.toString(), m.loc.startLine, m.loc.startColumn);
return nullptr;
} else {
- qWarning() << url << ':' << m.line << ':' << m.column
+ qWarning() << url << ':' << m.loc.startLine << ':' << m.loc.startColumn
<< ": warning: " << m.message;
}
}
- return ExecutableCompilationUnit::create(std::move(unit));
+ return insertCompilationUnit(std::move(unit));
}
-void ExecutionEngine::injectModule(const QQmlRefPointer<ExecutableCompilationUnit> &moduleUnit)
+QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compilationUnitForUrl(const QUrl &url) const
{
- // Injection can happen from the QML type loader thread for example, but instantiation and
- // evaluation must be limited to the ExecutionEngine's thread.
- QMutexLocker moduleGuard(&moduleMutex);
- modules.insert(moduleUnit->finalUrl(), moduleUnit);
+ // Gives the _most recently inserted_ CU of that URL. That's what we want.
+ return m_compilationUnits.value(url);
+}
+
+QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::executableCompilationUnit(
+ QQmlRefPointer<CompiledData::CompilationUnit> &&unit)
+{
+ const QUrl url = unit->finalUrl();
+ auto [begin, end] = std::as_const(m_compilationUnits).equal_range(url);
+
+ for (auto it = begin; it != end; ++it) {
+ if ((*it)->baseCompilationUnit() == unit)
+ return *it;
+ }
+
+ auto executableUnit = m_compilationUnits.insert(
+ url, ExecutableCompilationUnit::create(std::move(unit), this));
+ // runtime data should not be initialized yet, so we don't need to mark the CU
+ Q_ASSERT(!(*executableUnit)->runtimeStrings);
+ return *executableUnit;
+}
+
+QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::insertCompilationUnit(QQmlRefPointer<CompiledData::CompilationUnit> &&unit) {
+ QUrl url = unit->finalUrl();
+ auto executableUnit = ExecutableCompilationUnit::create(std::move(unit), this);
+ /* Compilation Units stored in the engine are part of the gc roots,
+ so we don't trigger any write-barrier when they are added. Use
+ markCustom to make sure they are still marked when we insert them */
+ QV4::WriteBarrier::markCustom(this, [&executableUnit](QV4::MarkStack *ms) {
+ executableUnit->markObjects(ms);
+ });
+ return *m_compilationUnits.insert(std::move(url), std::move(executableUnit));
+}
+
+void ExecutionEngine::trimCompilationUnits()
+{
+ for (auto it = m_compilationUnits.begin(); it != m_compilationUnits.end();) {
+ if ((*it)->count() == 1)
+ it = m_compilationUnits.erase(it);
+ else
+ ++it;
+ }
+}
+
+ExecutionEngine::Module ExecutionEngine::moduleForUrl(
+ const QUrl &url, const ExecutableCompilationUnit *referrer) const
+{
+ const auto nativeModule = nativeModules.find(url);
+ if (nativeModule != nativeModules.end())
+ return Module { nullptr, *nativeModule };
+
+ const QUrl resolved = referrer
+ ? referrer->finalUrl().resolved(QQmlTypeLoader::normalize(url))
+ : QQmlTypeLoader::normalize(url);
+ auto existingModule = m_compilationUnits.find(resolved);
+ if (existingModule == m_compilationUnits.end())
+ return Module { nullptr, nullptr };
+ return Module { *existingModule, nullptr };
}
-QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::moduleForUrl(const QUrl &_url, const ExecutableCompilationUnit *referrer) const
+ExecutionEngine::Module ExecutionEngine::loadModule(const QUrl &url, const ExecutableCompilationUnit *referrer)
{
- QUrl url = QQmlTypeLoader::normalize(_url);
- if (referrer)
- url = referrer->finalUrl().resolved(url);
+ const auto nativeModule = nativeModules.constFind(url);
+ if (nativeModule != nativeModules.cend())
+ return Module { nullptr, *nativeModule };
- QMutexLocker moduleGuard(&moduleMutex);
- auto existingModule = modules.find(url);
- if (existingModule == modules.end())
+ const QUrl resolved = referrer
+ ? referrer->finalUrl().resolved(QQmlTypeLoader::normalize(url))
+ : QQmlTypeLoader::normalize(url);
+ auto existingModule = m_compilationUnits.constFind(resolved);
+ if (existingModule != m_compilationUnits.cend())
+ return Module { *existingModule, nullptr };
+
+ auto newModule = compileModule(resolved);
+ Q_ASSERT(!newModule || m_compilationUnits.contains(resolved, newModule));
+
+ return Module { newModule, nullptr };
+}
+
+QV4::Value *ExecutionEngine::registerNativeModule(const QUrl &url, const QV4::Value &module)
+{
+ const auto existingModule = nativeModules.constFind(url);
+ if (existingModule != nativeModules.cend())
return nullptr;
- return *existingModule;
+
+ QV4::Value *val = this->memoryManager->m_persistentValues->allocate();
+ *val = module.asReturnedValue();
+ nativeModules.insert(url, val);
+
+ // Make sure the type loader doesn't try to resolve the script anymore.
+ if (m_qmlEngine)
+ QQmlEnginePrivate::get(m_qmlEngine)->typeLoader.injectScript(url, *val);
+
+ return val;
}
-QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::loadModule(const QUrl &_url, const ExecutableCompilationUnit *referrer)
+static ExecutionEngine::DiskCacheOptions transFormDiskCache(const char *v)
{
- QUrl url = QQmlTypeLoader::normalize(_url);
- if (referrer)
- url = referrer->finalUrl().resolved(url);
+ using DiskCache = ExecutionEngine::DiskCache;
+
+ if (v == nullptr)
+ return DiskCache::Enabled;
+
+ ExecutionEngine::DiskCacheOptions result = DiskCache::Disabled;
+ const QList<QByteArray> options = QByteArray(v).split(',');
+ for (const QByteArray &option : options) {
+ if (option == "aot-bytecode")
+ result |= DiskCache::AotByteCode;
+ else if (option == "aot-native")
+ result |= DiskCache::AotNative;
+ else if (option == "aot")
+ result |= DiskCache::Aot;
+ else if (option == "qmlc-read")
+ result |= DiskCache::QmlcRead;
+ else if (option == "qmlc-write")
+ result |= DiskCache::QmlcWrite;
+ else if (option == "qmlc")
+ result |= DiskCache::Qmlc;
+ else
+ qWarning() << "Ignoring unknown option to QML_DISK_CACHE:" << option;
+ }
- QMutexLocker moduleGuard(&moduleMutex);
- auto existingModule = modules.find(url);
- if (existingModule != modules.end())
- return *existingModule;
+ return result;
+}
- moduleGuard.unlock();
+ExecutionEngine::DiskCacheOptions ExecutionEngine::diskCacheOptions() const
+{
+ if (forceDiskCache())
+ return DiskCache::Enabled;
+ if (disableDiskCache() || debugger())
+ return DiskCache::Disabled;
+ static const DiskCacheOptions options = qmlGetConfigOption<
+ DiskCacheOptions, transFormDiskCache>("QML_DISK_CACHE");
+ return options;
+}
- auto newModule = compileModule(url);
- if (newModule) {
- moduleGuard.relock();
- modules.insert(url, newModule);
+void ExecutionEngine::callInContext(QV4::Function *function, QObject *self,
+ QV4::ExecutionContext *context, int argc, void **args,
+ QMetaType *types)
+{
+ if (!args) {
+ Q_ASSERT(argc == 0);
+ void *dummyArgs[] = { nullptr };
+ QMetaType dummyTypes[] = { QMetaType::fromType<void>() };
+ function->call(self, dummyArgs, dummyTypes, argc, context);
+ return;
}
+ Q_ASSERT(types); // both args and types must be present
+ // implicitly sets the return value, which is args[0]
+ function->call(self, args, types, argc, context);
+}
- return newModule;
+QV4::ReturnedValue ExecutionEngine::callInContext(QV4::Function *function, QObject *self,
+ QV4::ExecutionContext *context, int argc,
+ const QV4::Value *argv)
+{
+ QV4::Scope scope(this);
+ QV4::ScopedObject jsSelf(scope, QV4::QObjectWrapper::wrap(this, self));
+ Q_ASSERT(jsSelf);
+ return function->call(jsSelf, argv, argc, context);
}
void ExecutionEngine::initQmlGlobalObject()
{
initializeGlobal();
- freezeObject(*globalObject);
+ lockObject(*globalObject);
}
void ExecutionEngine::initializeGlobal()
{
- QV4::Scope scope(this);
- QV4::GlobalExtensions::init(globalObject, QJSEngine::AllExtensions);
+ createQtObject();
- QV4::ScopedObject qt(scope, memoryManager->allocate<QV4::QtObject>(qmlEngine()));
- globalObject->defineDefaultProperty(QStringLiteral("Qt"), qt);
+ QV4::GlobalExtensions::init(globalObject, QJSEngine::AllExtensions);
#if QT_CONFIG(qml_locale)
QQmlLocale::registerStringLocaleCompare(this);
@@ -1885,6 +2309,25 @@ void ExecutionEngine::initializeGlobal()
}
}
+void ExecutionEngine::createQtObject()
+{
+ QV4::Scope scope(this);
+ QtObject *qtObject = new QtObject(this);
+ QJSEngine::setObjectOwnership(qtObject, QJSEngine::JavaScriptOwnership);
+
+ QV4::ScopedObject qtObjectWrapper(
+ scope, QV4::QObjectWrapper::wrap(this, qtObject));
+ QV4::ScopedObject qtNamespaceWrapper(
+ scope, QV4::QMetaObjectWrapper::create(this, &Qt::staticMetaObject));
+ QV4::ScopedObject qtObjectProtoWrapper(
+ scope, qtObjectWrapper->getPrototypeOf());
+
+ qtNamespaceWrapper->setPrototypeOf(qtObjectProtoWrapper);
+ qtObjectWrapper->setPrototypeOf(qtNamespaceWrapper);
+
+ globalObject->defineDefaultProperty(QStringLiteral("Qt"), qtObjectWrapper);
+}
+
const QSet<QString> &ExecutionEngine::illegalNames() const
{
return m_illegalNames;
@@ -1892,13 +2335,16 @@ const QSet<QString> &ExecutionEngine::illegalNames() const
void ExecutionEngine::setQmlEngine(QQmlEngine *engine)
{
+ // Second stage of initialization. We're updating some more prototypes here.
+ isInitialized = false;
m_qmlEngine = engine;
initQmlGlobalObject();
+ isInitialized = true;
}
static void freeze_recursive(QV4::ExecutionEngine *v4, QV4::Object *object)
{
- if (object->as<QV4::QObjectWrapper>() || object->internalClass()->isFrozen)
+ if (object->as<QV4::QObjectWrapper>() || object->internalClass()->isFrozen())
return;
QV4::Scope scope(v4);
@@ -1935,6 +2381,58 @@ void ExecutionEngine::freezeObject(const QV4::Value &value)
freeze_recursive(this, o);
}
+void ExecutionEngine::lockObject(const QV4::Value &value)
+{
+ QV4::Scope scope(this);
+ ScopedObject object(scope, value);
+ if (!object)
+ return;
+
+ std::vector<Heap::Object *> stack { object->d() };
+
+ // Methods meant to be overridden
+ const PropertyKey writableMembers[] = {
+ id_toString()->propertyKey(),
+ id_toLocaleString()->propertyKey(),
+ id_valueOf()->propertyKey(),
+ id_constructor()->propertyKey()
+ };
+ const auto writableBegin = std::begin(writableMembers);
+ const auto writableEnd = std::end(writableMembers);
+
+ while (!stack.empty()) {
+ object = stack.back();
+ stack.pop_back();
+
+ if (object->as<QV4::QObjectWrapper>() || object->internalClass()->isLocked())
+ continue;
+
+ Scoped<InternalClass> locked(scope, object->internalClass()->locked());
+ QV4::ScopedObject member(scope);
+
+ // Taking this copy is cheap. It's refcounted. This avoids keeping a reference
+ // to the original IC.
+ const SharedInternalClassData<PropertyKey> nameMap = locked->d()->nameMap;
+
+ for (uint i = 0, end = locked->d()->size; i < end; ++i) {
+ const PropertyKey key = nameMap.at(i);
+ if (!key.isStringOrSymbol())
+ continue;
+ if ((member = *object->propertyData(i))) {
+ stack.push_back(member->d());
+ if (std::find(writableBegin, writableEnd, key) == writableEnd) {
+ PropertyAttributes attributes = locked->d()->find(key).attributes;
+ attributes.setConfigurable(false);
+ attributes.setWritable(false);
+ locked = locked->changeMember(key, attributes);
+ }
+ }
+ }
+
+ object->setInternalClass(locked->d());
+ }
+}
+
void ExecutionEngine::startTimer(const QString &timerName)
{
if (!m_time.isValid())
@@ -1964,7 +2462,7 @@ int ExecutionEngine::consoleCountHelper(const QString &file, quint16 line, quint
void ExecutionEngine::setExtensionData(int index, Deletable *data)
{
- if (m_extensionData.count() <= index)
+ if (m_extensionData.size() <= index)
m_extensionData.resize(index + 1);
if (m_extensionData.at(index))
@@ -1973,97 +2471,159 @@ void ExecutionEngine::setExtensionData(int index, Deletable *data)
m_extensionData[index] = data;
}
+template<typename Source>
+bool convertToIterable(QMetaType metaType, void *data, Source *sequence)
+{
+ QSequentialIterable iterable;
+ if (!QMetaType::view(metaType, data, QMetaType::fromType<QSequentialIterable>(), &iterable))
+ return false;
+
+ const QMetaType elementMetaType = iterable.valueMetaType();
+ QVariant element(elementMetaType);
+ for (qsizetype i = 0, end = sequence->getLength(); i < end; ++i) {
+ if (!ExecutionEngine::metaTypeFromJS(sequence->get(i), elementMetaType, element.data()))
+ element = QVariant(elementMetaType);
+ iterable.addValue(element, QSequentialIterable::AtEnd);
+ }
+ return true;
+}
+
// Converts a JS value to a meta-type.
// data must point to a place that can store a value of the given type.
// Returns true if conversion succeeded, false otherwise.
-bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data)
+bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, void *data)
{
// check if it's one of the types we know
- switch (QMetaType::Type(type)) {
+ switch (metaType.id()) {
case QMetaType::Bool:
- *reinterpret_cast<bool*>(data) = value->toBoolean();
+ *reinterpret_cast<bool*>(data) = value.toBoolean();
return true;
case QMetaType::Int:
- *reinterpret_cast<int*>(data) = value->toInt32();
+ *reinterpret_cast<int*>(data) = value.toInt32();
return true;
case QMetaType::UInt:
- *reinterpret_cast<uint*>(data) = value->toUInt32();
+ *reinterpret_cast<uint*>(data) = value.toUInt32();
+ return true;
+ case QMetaType::Long:
+ *reinterpret_cast<long*>(data) = long(value.toInteger());
+ return true;
+ case QMetaType::ULong:
+ *reinterpret_cast<ulong*>(data) = ulong(value.toInteger());
return true;
case QMetaType::LongLong:
- *reinterpret_cast<qlonglong*>(data) = qlonglong(value->toInteger());
+ *reinterpret_cast<qlonglong*>(data) = qlonglong(value.toInteger());
return true;
case QMetaType::ULongLong:
- *reinterpret_cast<qulonglong*>(data) = qulonglong(value->toInteger());
+ *reinterpret_cast<qulonglong*>(data) = qulonglong(value.toInteger());
return true;
case QMetaType::Double:
- *reinterpret_cast<double*>(data) = value->toNumber();
+ *reinterpret_cast<double*>(data) = value.toNumber();
return true;
case QMetaType::QString:
- if (value->isUndefined() || value->isNull())
- *reinterpret_cast<QString*>(data) = QString();
+ if (value.isUndefined())
+ *reinterpret_cast<QString*>(data) = QStringLiteral("undefined");
+ else if (value.isNull())
+ *reinterpret_cast<QString*>(data) = QStringLiteral("null");
else
- *reinterpret_cast<QString*>(data) = value->toQString();
+ *reinterpret_cast<QString*>(data) = value.toQString();
return true;
case QMetaType::QByteArray:
- if (const ArrayBuffer *ab = value->as<ArrayBuffer>())
+ if (const ArrayBuffer *ab = value.as<ArrayBuffer>()) {
*reinterpret_cast<QByteArray*>(data) = ab->asByteArray();
- else
+ } else if (const String *string = value.as<String>()) {
+ *reinterpret_cast<QByteArray*>(data) = string->toQString().toUtf8();
+ } else if (const ArrayObject *ao = value.as<ArrayObject>()) {
+ // Since QByteArray is sequentially iterable, we have to construct it from a JS Array.
+ QByteArray result;
+ const qint64 length = ao->getLength();
+ result.reserve(length);
+ for (qint64 i = 0; i < length; ++i) {
+ char value = 0;
+ ExecutionEngine::metaTypeFromJS(ao->get(i), QMetaType::fromType<char>(), &value);
+ result.push_back(value);
+ }
+ *reinterpret_cast<QByteArray*>(data) = std::move(result);
+ } else {
*reinterpret_cast<QByteArray*>(data) = QByteArray();
+ }
return true;
case QMetaType::Float:
- *reinterpret_cast<float*>(data) = value->toNumber();
+ *reinterpret_cast<float*>(data) = value.toNumber();
return true;
case QMetaType::Short:
- *reinterpret_cast<short*>(data) = short(value->toInt32());
+ *reinterpret_cast<short*>(data) = short(value.toInt32());
return true;
case QMetaType::UShort:
- *reinterpret_cast<unsigned short*>(data) = value->toUInt16();
+ *reinterpret_cast<unsigned short*>(data) = value.toUInt16();
return true;
case QMetaType::Char:
- *reinterpret_cast<char*>(data) = char(value->toInt32());
+ *reinterpret_cast<char*>(data) = char(value.toInt32());
return true;
case QMetaType::UChar:
- *reinterpret_cast<unsigned char*>(data) = (unsigned char)(value->toInt32());
+ *reinterpret_cast<unsigned char*>(data) = (unsigned char)(value.toInt32());
+ return true;
+ case QMetaType::SChar:
+ *reinterpret_cast<signed char*>(data) = (signed char)(value.toInt32());
return true;
case QMetaType::QChar:
- if (String *s = value->stringValue()) {
+ if (String *s = value.stringValue()) {
QString str = s->toQString();
*reinterpret_cast<QChar*>(data) = str.isEmpty() ? QChar() : str.at(0);
} else {
- *reinterpret_cast<QChar*>(data) = QChar(ushort(value->toUInt16()));
+ *reinterpret_cast<QChar*>(data) = QChar(ushort(value.toUInt16()));
}
return true;
case QMetaType::QDateTime:
- if (const QV4::DateObject *d = value->as<DateObject>()) {
+ if (const QV4::DateObject *d = value.as<DateObject>()) {
*reinterpret_cast<QDateTime *>(data) = d->toQDateTime();
return true;
} break;
case QMetaType::QDate:
- if (const QV4::DateObject *d = value->as<DateObject>()) {
- *reinterpret_cast<QDate *>(data) = d->toQDateTime().date();
+ if (const QV4::DateObject *d = value.as<DateObject>()) {
+ *reinterpret_cast<QDate *>(data) = DateObject::dateTimeToDate(d->toQDateTime());
return true;
} break;
- case QMetaType::QRegExp:
- if (const QV4::RegExpObject *r = value->as<QV4::RegExpObject>()) {
- *reinterpret_cast<QRegExp *>(data) = r->toQRegExp();
+ case QMetaType::QTime:
+ if (const QV4::DateObject *d = value.as<DateObject>()) {
+ *reinterpret_cast<QTime *>(data) = d->toQDateTime().time();
return true;
} break;
+ case QMetaType::QUrl:
+ if (String *s = value.stringValue()) {
+ *reinterpret_cast<QUrl *>(data) = QUrl(s->toQString());
+ return true;
+ } else if (const QV4::UrlObject *d = value.as<UrlObject>()) {
+ *reinterpret_cast<QUrl *>(data) = d->toQUrl();
+ return true;
+ } else if (const QV4::VariantObject *d = value.as<VariantObject>()) {
+ const QVariant *variant = &d->d()->data();
+ if (variant->metaType() == QMetaType::fromType<QUrl>()) {
+ *reinterpret_cast<QUrl *>(data)
+ = *reinterpret_cast<const QUrl *>(variant->constData());
+ return true;
+ }
+ }
+ break;
#if QT_CONFIG(regularexpression)
case QMetaType::QRegularExpression:
- if (const QV4::RegExpObject *r = value->as<QV4::RegExpObject>()) {
+ if (const QV4::RegExpObject *r = value.as<QV4::RegExpObject>()) {
*reinterpret_cast<QRegularExpression *>(data) = r->toQRegularExpression();
return true;
} break;
#endif
case QMetaType::QObjectStar: {
- const QV4::QObjectWrapper *qobjectWrapper = value->as<QV4::QObjectWrapper>();
- if (qobjectWrapper || value->isNull()) {
- *reinterpret_cast<QObject* *>(data) = qtObjectFromJS(this, *value);
+ if (value.isNull()) {
+ *reinterpret_cast<QObject* *>(data) = nullptr;
return true;
- } break;
+ }
+ if (value.as<QV4::QObjectWrapper>()) {
+ *reinterpret_cast<QObject* *>(data) = qtObjectFromJS(value);
+ return true;
+ }
+ break;
}
case QMetaType::QStringList: {
- const QV4::ArrayObject *a = value->as<QV4::ArrayObject>();
+ const QV4::ArrayObject *a = value.as<QV4::ArrayObject>();
if (a) {
*reinterpret_cast<QStringList *>(data) = a->toQStringList();
return true;
@@ -2071,33 +2631,48 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data)
break;
}
case QMetaType::QVariantList: {
- const QV4::ArrayObject *a = value->as<QV4::ArrayObject>();
+ const QV4::ArrayObject *a = value.as<QV4::ArrayObject>();
if (a) {
- *reinterpret_cast<QVariantList *>(data) = toVariant(*a, /*typeHint*/-1, /*createJSValueForObjects*/false).toList();
+ *reinterpret_cast<QVariantList *>(data) = ExecutionEngine::toVariant(
+ *a, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false)
+ .toList();
return true;
}
break;
}
case QMetaType::QVariantMap: {
- const QV4::Object *o = value->as<QV4::Object>();
+ const QV4::Object *o = value.as<QV4::Object>();
if (o) {
- *reinterpret_cast<QVariantMap *>(data) = variantMapFromJS(o);
+ *reinterpret_cast<QVariantMap *>(data) = o->engine()->variantMapFromJS(o);
return true;
}
break;
}
case QMetaType::QVariant:
- *reinterpret_cast<QVariant*>(data) = toVariant(*value, /*typeHint*/-1, /*createJSValueForObjects*/false);
+ if (value.as<QV4::Managed>()) {
+ *reinterpret_cast<QVariant*>(data) = ExecutionEngine::toVariant(
+ value, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false);
+ } else if (value.isNull()) {
+ *reinterpret_cast<QVariant*>(data) = QVariant::fromValue(nullptr);
+ } else if (value.isUndefined()) {
+ *reinterpret_cast<QVariant*>(data) = QVariant();
+ } else if (value.isBoolean()) {
+ *reinterpret_cast<QVariant*>(data) = QVariant(value.booleanValue());
+ } else if (value.isInteger()) {
+ *reinterpret_cast<QVariant*>(data) = QVariant(value.integerValue());
+ } else if (value.isDouble()) {
+ *reinterpret_cast<QVariant*>(data) = QVariant(value.doubleValue());
+ }
return true;
case QMetaType::QJsonValue:
- *reinterpret_cast<QJsonValue *>(data) = QV4::JsonObject::toJsonValue(*value);
+ *reinterpret_cast<QJsonValue *>(data) = QV4::JsonObject::toJsonValue(value);
return true;
case QMetaType::QJsonObject: {
- *reinterpret_cast<QJsonObject *>(data) = QV4::JsonObject::toJsonObject(value->as<Object>());
+ *reinterpret_cast<QJsonObject *>(data) = QV4::JsonObject::toJsonObject(value.as<Object>());
return true;
}
case QMetaType::QJsonArray: {
- const QV4::ArrayObject *a = value->as<ArrayObject>();
+ const QV4::ArrayObject *a = value.as<ArrayObject>();
if (a) {
*reinterpret_cast<QJsonArray *>(data) = JsonObject::toJsonArray(a);
return true;
@@ -2105,92 +2680,166 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data)
break;
}
default:
- ;
+ break;
}
- {
- const QQmlValueTypeWrapper *vtw = value->as<QQmlValueTypeWrapper>();
- if (vtw && vtw->typeId() == type) {
- return vtw->toGadget(data);
- }
+ if (metaType.flags() & QMetaType::IsEnumeration) {
+ *reinterpret_cast<int *>(data) = value.toInt32();
+ return true;
}
-#if 0
- if (isQtVariant(value)) {
- const QVariant &var = variantValue(value);
- // ### Enable once constructInPlace() is in qt master.
- if (var.userType() == type) {
- QMetaType::constructInPlace(type, data, var.constData());
+ if (const QV4::QmlListWrapper *wrapper = value.as<QV4::QmlListWrapper>()) {
+ if (metaType == QMetaType::fromType<QQmlListReference>()) {
+ *reinterpret_cast<QQmlListReference *>(data) = wrapper->toListReference();
return true;
}
- if (var.canConvert(type)) {
- QVariant vv = var;
- vv.convert(type);
- Q_ASSERT(vv.userType() == type);
- QMetaType::constructInPlace(type, data, vv.constData());
+
+ const auto wrapperPrivate = wrapper->d();
+ if (wrapperPrivate->propertyType() == metaType) {
+ *reinterpret_cast<QQmlListProperty<QObject> *>(data) = *wrapperPrivate->property();
return true;
}
+ }
+ if (const QQmlValueTypeWrapper *vtw = value.as<QQmlValueTypeWrapper>()) {
+ const QMetaType valueType = vtw->type();
+ if (valueType == metaType)
+ return vtw->toGadget(data);
+
+ Heap::QQmlValueTypeWrapper *d = vtw->d();
+ if (d->isReference())
+ d->readReference();
+
+ if (void *gadgetPtr = d->gadgetPtr()) {
+ if (QQmlValueTypeProvider::populateValueType(metaType, data, valueType, gadgetPtr))
+ return true;
+ if (QMetaType::canConvert(valueType, metaType))
+ return QMetaType::convert(valueType, gadgetPtr, metaType, data);
+ } else {
+ QVariant empty(valueType);
+ if (QQmlValueTypeProvider::populateValueType(metaType, data, valueType, empty.data()))
+ return true;
+ if (QMetaType::canConvert(valueType, metaType))
+ return QMetaType::convert(valueType, empty.data(), metaType, data);
+ }
}
-#endif
// Try to use magic; for compatibility with qjsvalue_cast.
- QByteArray name = QMetaType::typeName(type);
- if (convertToNativeQObject(this, *value, name, reinterpret_cast<void* *>(data)))
+ if (convertToNativeQObject(value, metaType, reinterpret_cast<void **>(data)))
return true;
- if (value->as<QV4::VariantObject>() && name.endsWith('*')) {
- int valueType = QMetaType::type(name.left(name.size()-1));
- QVariant &var = value->as<QV4::VariantObject>()->d()->data();
- if (valueType == var.userType()) {
- // We have T t, T* is requested, so return &t.
- *reinterpret_cast<void* *>(data) = var.data();
+
+ const bool isPointer = (metaType.flags() & QMetaType::IsPointer);
+ const QV4::VariantObject *variantObject = value.as<QV4::VariantObject>();
+ if (variantObject) {
+ // Actually a reference, because we're poking it for its data() below and we want
+ // the _original_ data, not some copy.
+ QVariant &var = variantObject->d()->data();
+
+ if (var.metaType() == metaType) {
+ metaType.destruct(data);
+ metaType.construct(data, var.data());
return true;
- } else if (Object *o = value->objectValue()) {
- // Look in the prototype chain.
- QV4::Scope scope(this);
- QV4::ScopedObject proto(scope, o->getPrototypeOf());
- while (proto) {
- bool canCast = false;
- if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) {
- const QVariant &v = vo->d()->data();
- canCast = (type == v.userType()) || (valueType && (valueType == v.userType()));
- }
- else if (proto->as<QV4::QObjectWrapper>()) {
- QByteArray className = name.left(name.size()-1);
- QV4::ScopedObject p(scope, proto.getPointer());
- if (QObject *qobject = qtObjectFromJS(this, p))
- canCast = qobject->qt_metacast(className) != nullptr;
- }
- if (canCast) {
- QByteArray varTypeName = QMetaType::typeName(var.userType());
- if (varTypeName.endsWith('*'))
- *reinterpret_cast<void* *>(data) = *reinterpret_cast<void* *>(var.data());
- else
- *reinterpret_cast<void* *>(data) = var.data();
- return true;
+ }
+
+ if (isPointer) {
+ const QByteArray pointedToTypeName = QByteArray(metaType.name()).chopped(1);
+ const QMetaType valueType = QMetaType::fromName(pointedToTypeName);
+
+ if (valueType == var.metaType()) {
+ // ### Qt7: Remove this. Returning pointers to potentially gc'd data is crazy.
+ // We have T t, T* is requested, so return &t.
+ *reinterpret_cast<const void **>(data) = var.data();
+ return true;
+ } else if (Object *o = value.objectValue()) {
+ // Look in the prototype chain.
+ QV4::Scope scope(o->engine());
+ QV4::ScopedObject proto(scope, o->getPrototypeOf());
+ while (proto) {
+ bool canCast = false;
+ if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) {
+ const QVariant &v = vo->d()->data();
+ canCast = (metaType == v.metaType());
+ }
+ else if (proto->as<QV4::QObjectWrapper>()) {
+ QV4::ScopedObject p(scope, proto.getPointer());
+ if (QObject *qobject = qtObjectFromJS(p)) {
+ if (const QMetaObject *metaObject = metaType.metaObject())
+ canCast = metaObject->cast(qobject) != nullptr;
+ else
+ canCast = qobject->qt_metacast(pointedToTypeName);
+ }
+ }
+ if (canCast) {
+ const QMetaType varType = var.metaType();
+ if (varType.flags() & QMetaType::IsPointer) {
+ *reinterpret_cast<const void **>(data)
+ = *reinterpret_cast<void *const *>(var.data());
+ } else {
+ *reinterpret_cast<const void **>(data) = var.data();
+ }
+ return true;
+ }
+ proto = proto->getPrototypeOf();
}
- proto = proto->getPrototypeOf();
}
+ } else if (QQmlValueTypeProvider::populateValueType(
+ metaType, data, var.metaType(), var.data())) {
+ return true;
}
- } else if (value->isNull() && name.endsWith('*')) {
+ } else if (value.isNull() && isPointer) {
*reinterpret_cast<void* *>(data) = nullptr;
return true;
- } else if (type == qMetaTypeId<QJSValue>()) {
- *reinterpret_cast<QJSValue*>(data) = QJSValue(this, value->asReturnedValue());
+ } else if (metaType == QMetaType::fromType<QJSValue>()) {
+ QJSValuePrivate::setValue(reinterpret_cast<QJSValue*>(data), value.asReturnedValue());
+ return true;
+ } else if (metaType == QMetaType::fromType<QJSPrimitiveValue>()) {
+ *reinterpret_cast<QJSPrimitiveValue *>(data) = createPrimitive(&value);
return true;
+ } else if (!isPointer) {
+ if (QQmlValueTypeProvider::populateValueType(metaType, data, value))
+ return true;
+ }
+
+ if (const QV4::Sequence *sequence = value.as<Sequence>()) {
+ const QVariant result = QV4::SequencePrototype::toVariant(sequence);
+ if (result.metaType() == metaType) {
+ metaType.destruct(data);
+ metaType.construct(data, result.constData());
+ return true;
+ }
+
+ if (convertToIterable(metaType, data, sequence))
+ return true;
+ }
+
+ if (const QV4::ArrayObject *array = value.as<ArrayObject>()) {
+ if (convertToIterable(metaType, data, array))
+ return true;
}
return false;
}
-static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &value, const QByteArray &targetType, void **result)
+static bool convertToNativeQObject(const QV4::Value &value, QMetaType targetType, void **result)
{
- if (!targetType.endsWith('*'))
+ if (!(targetType.flags() & QMetaType::IsPointer))
return false;
- if (QObject *qobject = qtObjectFromJS(e, value)) {
- int start = targetType.startsWith("const ") ? 6 : 0;
- QByteArray className = targetType.mid(start, targetType.size()-start-1);
+ if (QObject *qobject = qtObjectFromJS(value)) {
+ // If the target type has a metaObject, use that for casting.
+ if (const QMetaObject *targetMetaObject = targetType.metaObject()) {
+ if (QObject *instance = targetMetaObject->cast(qobject)) {
+ *result = instance;
+ return true;
+ }
+ return false;
+ }
+
+ // We have to call the generated qt_metacast rather than metaObject->cast() here so that
+ // it works for types without QMetaObject, such as QStandardItem.
+ const QByteArray targetTypeName = targetType.name();
+ const int start = targetTypeName.startsWith("const ") ? 6 : 0;
+ const QByteArray className = targetTypeName.mid(start, targetTypeName.size() - start - 1);
if (void *instance = qobject->qt_metacast(className)) {
*result = instance;
return true;
@@ -2199,12 +2848,12 @@ static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &va
return false;
}
-static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &value)
+static QObject *qtObjectFromJS(const QV4::Value &value)
{
if (!value.isObject())
return nullptr;
- QV4::Scope scope(engine);
+ QV4::Scope scope(value.as<QV4::Managed>()->engine());
QV4::Scoped<QV4::VariantObject> v(scope, value);
if (v) {
@@ -2214,9 +2863,14 @@ static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &v
return *reinterpret_cast<QObject* const *>(variant.constData());
}
QV4::Scoped<QV4::QObjectWrapper> wrapper(scope, value);
- if (!wrapper)
- return nullptr;
- return wrapper->object();
+ if (wrapper)
+ return wrapper->object();
+
+ QV4::Scoped<QV4::QQmlTypeWrapper> typeWrapper(scope, value);
+ if (typeWrapper)
+ return typeWrapper->object();
+
+ return nullptr;
}
struct QV4EngineRegistrationData
@@ -2238,4 +2892,11 @@ int ExecutionEngine::registerExtension()
return registrationData()->extensionCount++;
}
+#if QT_CONFIG(qml_network)
+QNetworkAccessManager *QV4::detail::getNetworkAccessManager(ExecutionEngine *engine)
+{
+ return engine->qmlEngine()->networkAccessManager();
+}
+#endif // qml_network
+
QT_END_NAMESPACE