diff options
Diffstat (limited to 'src/qml/jsruntime/qv4engine.cpp')
-rw-r--r-- | src/qml/jsruntime/qv4engine.cpp | 2063 |
1 files changed, 1429 insertions, 634 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index b6000dbcca..bd6251caa9 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1,57 +1,21 @@ -/**************************************************************************** -** -** 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/qqmljslexer_p.h> -#include <private/qqmljsparser_p.h> -#include <private/qqmljsast_p.h> #include <private/qv4compileddata_p.h> -#include <private/qv4compiler_p.h> -#include <private/qv4compilercontext_p.h> #include <private/qv4codegen_p.h> +#include <private/qqmljsdiagnosticmessage_p.h> #include <QtCore/QTextStream> #include <QDateTime> #include <QDir> #include <QFileInfo> - -#ifndef V4_BOOTSTRAP +#include <QLoggingCategory> +#if QT_CONFIG(regularexpression) +#include <QRegularExpression> +#endif +#include <QtCore/QTimeZone> +#include <QtCore/qiterable.h> #include <qv4qmlcontext_p.h> #include <qv4value_p.h> @@ -91,19 +55,18 @@ #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" #include "qv4promiseobject_p.h" #include "qv4typedarray_p.h" -#include <private/qv8engine_p.h> #include <private/qjsvalue_p.h> #include <private/qqmltypewrapper_p.h> #include <private/qqmlvaluetypewrapper_p.h> @@ -111,41 +74,261 @@ #include <private/qqmllistwrapper_p.h> #include <private/qqmllist_p.h> #include <private/qqmltypeloader_p.h> +#include <private/qqmlbuiltinfunctions_p.h> #if QT_CONFIG(qml_locale) #include <private/qqmllocale_p.h> #endif +#if QT_CONFIG(qml_xml_http_request) +#include <private/qv4domerrors_p.h> +#include <private/qqmlxmlhttprequest_p.h> +#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 -#endif // #ifndef V4_BOOTSTRAP - QT_BEGIN_NAMESPACE -using namespace QV4; +DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE); +DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE); -#ifndef V4_BOOTSTRAP +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) +{ + const QVariant variant = value.toVariant(); + return variant.metaType() == QMetaType::fromType<QJSValue>() + ? ReturnType() + : variant.value<ReturnType>(); +} + +struct JSArrayIterator { + QJSValue const* data; + quint32 index; +}; + +namespace { +void createNewIteratorIfNonExisting(void **iterator) { + if (*iterator == nullptr) + *iterator = new JSArrayIterator; +} +} + +static QtMetaContainerPrivate::QMetaSequenceInterface emptySequenceInterface() +{ + // 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 QtMetaContainerPrivate::QMetaSequenceInterface sequenceInterface() +{ + 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; + + + 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 { + 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) : executableAllocator(new QV4::ExecutableAllocator) @@ -154,56 +337,58 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) , jsStack(new WTF::PageAllocation) , gcStack(new WTF::PageAllocation) , globalCode(nullptr) - , v8Engine(nullptr) , publicEngine(jsEngine) - , m_engineId(engineSerial.fetchAndAddOrdered(1)) + , m_engineId(engineSerial.fetchAndAddOrdered(2)) , regExpCache(nullptr) , m_multiplyWrappedQObjects(nullptr) -#if defined(V4_ENABLE_JIT) && !defined(V4_BOOTSTRAP) +#if QT_CONFIG(qml_jit) , m_canAllocateExecutableMemory(OSAllocator::canAllocateExecutableMemory()) #endif -{ - memoryManager = new QV4::MemoryManager(this); - - if (maxCallDepth == -1) { - bool 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; +#if QT_CONFIG(qml_xml_http_request) + , m_xmlHttpRequestData(nullptr) #endif + , m_qmlEngine(nullptr) +{ + 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 JSStackLimit, as we can overshoot due to ScopedValues + // 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(JSStackLimit + 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, JSStackLimit + 256*1024); + VALGRIND_MAKE_MEM_UNDEFINED(jsStackBase, m_maxJSStackSize + 256*1024); #endif jsStackTop = jsStackBase; - *gcStack = WTF::PageAllocation::allocate(GCStackLimit, WTF::OSAllocator::JSVMStackPages, - /* writable */ true, /* executable */ false, - /* includesGuardPages */ true); - - { - bool 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(); @@ -215,7 +400,7 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) jsSymbols = jsAlloca(NJSSymbols); // set up stack limits - jsStackLimit = jsStackBase + JSStackLimit/sizeof(Value); + jsStackLimit = jsStackBase + s_maxJSStackSize/sizeof(Value); identifierTable = new IdentifierTable(this); @@ -439,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()); @@ -476,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); @@ -485,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()); @@ -497,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); @@ -507,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()); @@ -531,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>())); } @@ -586,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()); @@ -602,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: @@ -638,9 +831,11 @@ 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()->frozen()); + t->setInternalClass(t->internalClass()->cryopreserved()); jsObjects[ThrowerObject] = t; ScopedProperty pd(scope); @@ -648,18 +843,31 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) pd->set = thrower(); functionPrototype()->insertMember(id_caller(), pd, Attr_Accessor|Attr_ReadOnly_ButConfigurable); functionPrototype()->insertMember(id_arguments(), pd, Attr_Accessor|Attr_ReadOnly_ButConfigurable); + + 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; @@ -669,11 +877,11 @@ ExecutionEngine::~ExecutionEngine() delete jsStack; gcStack->deallocate(); delete gcStack; -} -ExecutionContext *ExecutionEngine::currentContext() const -{ - return static_cast<ExecutionContext *>(¤tStackFrame->jsFrame->context); +#if QT_CONFIG(qml_xml_http_request) + qt_rem_qmlxmlhttprequest(this, m_xmlHttpRequestData); + m_xmlHttpRequestData = nullptr; +#endif } #if QT_CONFIG(qml_debug) @@ -693,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; @@ -727,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(); } @@ -813,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) @@ -845,10 +1064,30 @@ Heap::RegExpObject *ExecutionEngine::newRegExpObject(RegExp *re) return memoryManager->allocate<RegExpObject>(re); } -Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegExp &re) +#if QT_CONFIG(regularexpression) +Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegularExpression &re) { return memoryManager->allocate<RegExpObject>(re); } +#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) { @@ -939,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) @@ -968,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 @@ -994,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; @@ -1014,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; @@ -1050,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 << ']'; @@ -1062,6 +1289,12 @@ extern "C" Q_QML_EXPORT char *qt_v4StackTrace(void *executionContext) return v4StackTrace(reinterpret_cast<const ExecutionContext *>(executionContext)); } +extern "C" Q_QML_EXPORT char *qt_v4StackTraceForEngine(void *executionEngine) +{ + auto engine = (reinterpret_cast<const ExecutionEngine *>(executionEngine)); + return v4StackTrace(engine->currentContext()); +} + QUrl ExecutionEngine::resolvedUrl(const QString &file) { QUrl src(file); @@ -1075,7 +1308,7 @@ QUrl ExecutionEngine::resolvedUrl(const QString &file) base = f->v4Function->finalUrl(); break; } - f = f->parent; + f = f->parentFrame(); } if (base.isEmpty() && globalCode) @@ -1089,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) @@ -1241,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); @@ -1252,50 +1483,51 @@ 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); +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()); + return v4->metaTypeToJS(value.metaType(), value.constData()); } - -QVariant ExecutionEngine::toVariant(const Value &value, int typeHint, bool createJSValueForObjects) -{ - return ::toVariant(this, value, typeHint, createJSValueForObjects, nullptr); -} - - -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>()) { - return qVariantFromValue<QObject *>(wrapper->object()); + return QVariant::fromValue<QObject *>(wrapper->object()); } else if (object->as<QV4::QQmlContextWrapper>()) { return QVariant(); } else if (QV4::QQmlTypeWrapper *w = object->as<QV4::QQmlTypeWrapper>()) { @@ -1304,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); @@ -1326,17 +1557,65 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int } } - return qVariantFromValue<QList<QObject*> >(list); - } else if (typeHint == QMetaType::QJsonArray) { + return QVariant::fromValue<QList<QObject*> >(list); + } 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()) @@ -1352,33 +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 QT_CONFIG(regularexpression) if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>()) - return re->toQRegExp(); + return re->toQRegularExpression(); +#endif + + 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(e, o, visitedObjects); + return objectToVariant(o, visitedObjects, conversionBehavior); } -static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects) +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; + + QString key = name->toQStringNoThrow(); + map.insert(key, ::toVariant( + val, /*type hint*/ QMetaType {}, + conversionBehvior, visitedObjects)); + } + return map; +} + +static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects, + JSToQVariantConversionBehavior conversionBehvior) { Q_ASSERT(o); @@ -1398,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; @@ -1406,66 +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; } -static QV4::ReturnedValue arrayFromVariantList(QV4::ExecutionEngine *e, const QVariantList &list) -{ - QV4::Scope scope(e); - QV4::ScopedArrayObject a(scope, e->newArrayObject()); - int len = list.count(); - a->arrayReserve(len); - QV4::ScopedValue v(scope); - for (int ii = 0; ii < len; ++ii) - a->arrayPut(ii, (v = scope.engine->fromVariant(list.at(ii)))); - - a->setArrayLengthUnchecked(len); - return a.asReturnedValue(); -} - -static QV4::ReturnedValue objectFromVariantMap(QV4::ExecutionEngine *e, const QVariantMap &map) -{ - QV4::Scope scope(e); - QV4::ScopedObject o(scope, e->newObject()); - QV4::ScopedString s(scope); - QV4::ScopedValue v(scope); - for (QVariantMap::const_iterator iter = map.begin(), cend = map.end(); iter != cend; ++iter) { - s = e->newString(iter.key()); - o->put(s, (v = e->fromVariant(iter.value()))); - } - return o.asReturnedValue(); -} - -Q_CORE_EXPORT QString qt_regexp_toCanonical(const QString &, QRegExp::PatternSyntax); +/*! + \internal -QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) + 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: @@ -1480,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: @@ -1500,124 +1828,159 @@ 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 arrayFromVariantList(this, *reinterpret_cast<const QVariantList *>(ptr)); + return createSequence(QMetaSequence::fromContainer<QVariantList>()); case QMetaType::QVariantMap: - return objectFromVariantMap(this, *reinterpret_cast<const QVariantMap *>(ptr)); + return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(ptr)); case QMetaType::QJsonValue: return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast<const QJsonValue *>(ptr)); case QMetaType::QJsonObject: 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 (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(type)) - return QV4::QQmlValueTypeWrapper::create(this, variant, vtmo, type); - } + QSequentialIterable iterable; + if (QMetaType::convert(metaType, ptr, QMetaType::fromType<QSequentialIterable>(), &iterable)) { - // XXX TODO: To be compatible, we still need to handle: - // + QObjectList - // + QList<int> + // 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); - return QV4::Encode(newVariantObject(variant)); + // 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(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()); } +ReturnedValue ExecutionEngine::fromVariant( + const QVariant &variant, Heap::Object *parent, int property, uint flags) +{ + return fromData(variant.metaType(), variant.constData(), parent, property, flags); +} -// 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) +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. @@ -1635,9 +1998,8 @@ static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVarian s = v4->newIdentifier(it.key()); key = s->propertyKey(); v = variantToJS(v4, it.value()); - uint idx = key->asArrayIndex(); - if (idx < UINT_MAX) - o->arraySet(idx, v); + if (key->isArrayIndex()) + o->arraySet(key->asArrayIndex(), v); else o->insertMember(s, v); } @@ -1646,94 +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); - // check if it's one of the types we know - switch (QMetaType::Type(type)) { - case QMetaType::UnknownType: - case QMetaType::Void: - return QV4::Encode::undefined(); - case QMetaType::Nullptr: - case QMetaType::VoidStar: - return QV4::Encode::null(); - case QMetaType::Bool: - return QV4::Encode(*reinterpret_cast<const bool*>(data)); - case QMetaType::Int: - return QV4::Encode(*reinterpret_cast<const int*>(data)); - case QMetaType::UInt: - return QV4::Encode(*reinterpret_cast<const uint*>(data)); - case QMetaType::LongLong: - return QV4::Encode(double(*reinterpret_cast<const qlonglong*>(data))); - case QMetaType::ULongLong: -#if defined(Q_OS_WIN) && defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 12008804 -#pragma message("** NOTE: You need the Visual Studio Processor Pack to compile support for 64bit unsigned integers.") - return QV4::Encode(double((qlonglong)*reinterpret_cast<const qulonglong*>(data))); -#elif defined(Q_CC_MSVC) && !defined(Q_CC_MSVC_NET) - return QV4::Encode(double((qlonglong)*reinterpret_cast<const qulonglong*>(data))); -#else - return QV4::Encode(double(*reinterpret_cast<const qulonglong*>(data))); -#endif - case QMetaType::Double: - return QV4::Encode(*reinterpret_cast<const double*>(data)); - case QMetaType::QString: - return newString(*reinterpret_cast<const QString*>(data))->asReturnedValue(); - case QMetaType::QByteArray: - return newArrayBuffer(*reinterpret_cast<const QByteArray*>(data))->asReturnedValue(); - case QMetaType::Float: - return QV4::Encode(*reinterpret_cast<const float*>(data)); - case QMetaType::Short: - return QV4::Encode((int)*reinterpret_cast<const short*>(data)); - case QMetaType::UShort: - return QV4::Encode((int)*reinterpret_cast<const unsigned short*>(data)); - case QMetaType::Char: - return QV4::Encode((int)*reinterpret_cast<const char*>(data)); - case QMetaType::UChar: - return QV4::Encode((int)*reinterpret_cast<const unsigned char*>(data)); - case QMetaType::QChar: - return QV4::Encode((int)(*reinterpret_cast<const QChar*>(data)).unicode()); - case QMetaType::QStringList: - return QV4::Encode(newArrayObject(*reinterpret_cast<const QStringList *>(data))); - case QMetaType::QVariantList: - return variantListToJS(this, *reinterpret_cast<const QVariantList *>(data)); - case QMetaType::QVariantMap: - return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(data)); - case QMetaType::QDateTime: - return QV4::Encode(newDateObject(*reinterpret_cast<const QDateTime *>(data))); - case QMetaType::QDate: - return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast<const QDate *>(data)))); - case QMetaType::QRegExp: - return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegExp *>(data))); - case QMetaType::QObjectStar: - return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(data)); - case QMetaType::QVariant: - return variantToJS(this, *reinterpret_cast<const QVariant*>(data)); - case QMetaType::QJsonValue: - return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast<const QJsonValue *>(data)); - case QMetaType::QJsonObject: - return QV4::JsonObject::fromJsonObject(this, *reinterpret_cast<const QJsonObject *>(data)); - case QMetaType::QJsonArray: - return QV4::JsonObject::fromJsonArray(this, *reinterpret_cast<const QJsonArray *>(data)); - default: - if (type == qMetaTypeId<QJSValue>()) { - return QJSValuePrivate::convertedToValue(this, *reinterpret_cast<const QJSValue*>(data)); - } else { - QByteArray typeName = QMetaType::typeName(type); - if (typeName.endsWith('*') && !*reinterpret_cast<void* const *>(data)) { - return QV4::Encode::null(); - } - QMetaType mt(type); - if (mt.flags() & QMetaType::IsGadget) { - Q_ASSERT(mt.metaObject()); - return QV4::QQmlValueTypeWrapper::create(this, QVariant(type, data), mt.metaObject(), type); - } - // Fall back to wrapping in a QVariant. - return QV4::Encode(newVariantObject(QVariant(type, data))); - } + if (type == QMetaType::fromType<QVariant>()) { + // unwrap it: this is tested in QJSEngine, and makes the most sense for + // end-user code too. + 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 fromData(type, data); +} + +int ExecutionEngine::maxJSStackSize() const +{ + return s_maxJSStackSize; +} + +int ExecutionEngine::maxGCStackSize() const +{ + 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; } - Q_UNREACHABLE(); - return 0; + return len64; } ReturnedValue ExecutionEngine::global() @@ -1741,8 +2058,24 @@ ReturnedValue ExecutionEngine::global() return globalObject->asReturnedValue(); } -QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(const QUrl &url) -{ +QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compileModule(const QUrl &url) +{ + QQmlMetaType::CachedUnitLookupError cacheError = QQmlMetaType::CachedUnitLookupError::NoError; + 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)); if (!f.open(QIODevice::ReadOnly)) { throwError(QStringLiteral("Could not open module %1 for reading").arg(url.toString())); @@ -1758,10 +2091,12 @@ QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(con } -QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(const QUrl &url, const QString &sourceCode, const QDateTime &sourceTimeStamp) +QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compileModule( + const QUrl &url, const QString &sourceCode, const QDateTime &sourceTimeStamp) { QList<QQmlJS::DiagnosticMessage> diagnostics; - auto unit = compileModule(/*debugMode*/debugger() != nullptr, url.toString(), sourceCode, sourceTimeStamp, &diagnostics); + auto unit = Compiler::Codegen::compileModule(/*debugMode*/debugger() != nullptr, url.toString(), + sourceCode, sourceTimeStamp, &diagnostics); for (const QQmlJS::DiagnosticMessage &m : diagnostics) { if (m.isError()) { throwSyntaxError(m.message, url.toString(), m.loc.startLine, m.loc.startColumn); @@ -1771,182 +2106,524 @@ QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(con << ": warning: " << m.message; } } - return unit; + + return insertCompilationUnit(std::move(unit)); } -#endif // ifndef V4_BOOTSTRAP +QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compilationUnitForUrl(const QUrl &url) const +{ + // Gives the _most recently inserted_ CU of that URL. That's what we want. + return m_compilationUnits.value(url); +} -QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(bool debugMode, const QString &url, const QString &sourceCode, - const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics) +QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::executableCompilationUnit( + QQmlRefPointer<CompiledData::CompilationUnit> &&unit) { - QQmlJS::Engine ee; - QQmlJS::Lexer lexer(&ee); - lexer.setCode(sourceCode, /*line*/1, /*qml mode*/false); - QQmlJS::Parser parser(&ee); + const QUrl url = unit->finalUrl(); + auto [begin, end] = std::as_const(m_compilationUnits).equal_range(url); - const bool parsed = parser.parseModule(); + for (auto it = begin; it != end; ++it) { + if ((*it)->baseCompilationUnit() == unit) + return *it; + } - if (diagnostics) - *diagnostics = parser.diagnosticMessages(); + 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; +} - if (!parsed) - return nullptr; +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)); +} - QQmlJS::AST::ESModule *moduleNode = QQmlJS::AST::cast<QQmlJS::AST::ESModule*>(parser.rootNode()); - if (!moduleNode) { - // if parsing was successful, and we have no module, then - // the file was empty. - if (diagnostics) - diagnostics->clear(); - return nullptr; +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 }; +} + +ExecutionEngine::Module ExecutionEngine::loadModule(const QUrl &url, const ExecutableCompilationUnit *referrer) +{ + const auto nativeModule = nativeModules.constFind(url); + if (nativeModule != nativeModules.cend()) + return Module { nullptr, *nativeModule }; - using namespace QV4::Compiler; - Compiler::Module compilerModule(debugMode); - compilerModule.unitFlags |= CompiledData::Unit::IsESModule; - compilerModule.sourceTimeStamp = sourceTimeStamp; - JSUnitGenerator jsGenerator(&compilerModule); - Codegen cg(&jsGenerator, /*strictMode*/true); - cg.generateFromModule(url, url, sourceCode, moduleNode, &compilerModule); - auto errors = cg.errors(); - if (diagnostics) - *diagnostics << errors; - - if (!errors.isEmpty()) + 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 cg.generateCompilationUnit(); + 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; } -#ifndef V4_BOOTSTRAP +static ExecutionEngine::DiskCacheOptions transFormDiskCache(const char *v) +{ + 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; + } + + return result; +} -void ExecutionEngine::injectModule(const QQmlRefPointer<CompiledData::CompilationUnit> &moduleUnit) +ExecutionEngine::DiskCacheOptions ExecutionEngine::diskCacheOptions() 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); + if (forceDiskCache()) + return DiskCache::Enabled; + if (disableDiskCache() || debugger()) + return DiskCache::Disabled; + static const DiskCacheOptions options = qmlGetConfigOption< + DiskCacheOptions, transFormDiskCache>("QML_DISK_CACHE"); + return options; } -QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::moduleForUrl(const QUrl &_url, const CompiledData::CompilationUnit *referrer) const +void ExecutionEngine::callInContext(QV4::Function *function, QObject *self, + QV4::ExecutionContext *context, int argc, void **args, + QMetaType *types) { - QUrl url = QQmlTypeLoader::normalize(_url); - if (referrer) - url = referrer->finalUrl().resolved(url); + 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); +} - QMutexLocker moduleGuard(&moduleMutex); - auto existingModule = modules.find(url); - if (existingModule == modules.end()) - return nullptr; - return *existingModule; +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(); + lockObject(*globalObject); +} + +void ExecutionEngine::initializeGlobal() +{ + createQtObject(); + + QV4::GlobalExtensions::init(globalObject, QJSEngine::AllExtensions); + +#if QT_CONFIG(qml_locale) + QQmlLocale::registerStringLocaleCompare(this); + QQmlDateExtension::registerExtension(this); + QQmlNumberExtension::registerExtension(this); +#endif + +#if QT_CONFIG(qml_xml_http_request) + qt_add_domexceptions(this); + m_xmlHttpRequestData = qt_add_qmlxmlhttprequest(this); +#endif + + qt_add_sqlexceptions(this); + + { + for (uint i = 0; i < globalObject->internalClass()->size; ++i) { + if (globalObject->internalClass()->nameMap.at(i).isString()) { + QV4::PropertyKey id = globalObject->internalClass()->nameMap.at(i); + m_illegalNames.insert(id.toQString()); + } + } + } +} + +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; } -QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::loadModule(const QUrl &_url, const CompiledData::CompilationUnit *referrer) +void ExecutionEngine::setQmlEngine(QQmlEngine *engine) { - QUrl url = QQmlTypeLoader::normalize(_url); - if (referrer) - url = referrer->finalUrl().resolved(url); + // 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()) + return; + + QV4::Scope scope(v4); + + bool instanceOfObject = false; + QV4::ScopedObject p(scope, object->getPrototypeOf()); + while (p) { + if (p->d() == v4->objectPrototype()->d()) { + instanceOfObject = true; + break; + } + p = p->getPrototypeOf(); + } + if (!instanceOfObject) + return; + + Heap::InternalClass *frozen = object->internalClass()->frozen(); + object->setInternalClass(frozen); // Immediately assign frozen to prevent it from getting GC'd + + QV4::ScopedObject o(scope); + for (uint i = 0; i < frozen->size; ++i) { + if (!frozen->nameMap.at(i).isStringOrSymbol()) + continue; + o = *object->propertyData(i); + if (o) + freeze_recursive(v4, o); + } +} - QMutexLocker moduleGuard(&moduleMutex); - auto existingModule = modules.find(url); - if (existingModule != modules.end()) - return *existingModule; +void ExecutionEngine::freezeObject(const QV4::Value &value) +{ + QV4::Scope scope(this); + QV4::ScopedObject o(scope, value); + freeze_recursive(this, o); +} - moduleGuard.unlock(); +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); + } + } + } - auto newModule = compileModule(url); - if (newModule) { - moduleGuard.relock(); - modules.insert(url, newModule); + object->setInternalClass(locked->d()); } +} - return newModule; +void ExecutionEngine::startTimer(const QString &timerName) +{ + if (!m_time.isValid()) + m_time.start(); + m_startedTimers[timerName] = m_time.elapsed(); +} + +qint64 ExecutionEngine::stopTimer(const QString &timerName, bool *wasRunning) +{ + if (!m_startedTimers.contains(timerName)) { + *wasRunning = false; + return 0; + } + *wasRunning = true; + qint64 startedAt = m_startedTimers.take(timerName); + return m_time.elapsed() - startedAt; +} + +int ExecutionEngine::consoleCountHelper(const QString &file, quint16 line, quint16 column) +{ + const QString key = file + QString::number(line) + QString::number(column); + int number = m_consoleCount.value(key, 0); + number++; + m_consoleCount.insert(key, number); + return number; +} + +void ExecutionEngine::setExtensionData(int index, Deletable *data) +{ + if (m_extensionData.size() <= index) + m_extensionData.resize(index + 1); + + if (m_extensionData.at(index)) + delete m_extensionData.at(index); + + 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::QObjectStar: { - const QV4::QObjectWrapper *qobjectWrapper = value->as<QV4::QObjectWrapper>(); - if (qobjectWrapper || value->isNull()) { - *reinterpret_cast<QObject* *>(data) = qtObjectFromJS(this, *value); + 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>()) { + *reinterpret_cast<QRegularExpression *>(data) = r->toQRegularExpression(); return true; } break; +#endif + case QMetaType::QObjectStar: { + if (value.isNull()) { + *reinterpret_cast<QObject* *>(data) = nullptr; + return true; + } + 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; @@ -1954,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; @@ -1988,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; @@ -2082,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) { @@ -2097,11 +2863,40 @@ 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; } -#endif // ifndef V4_BOOTSTRAP +struct QV4EngineRegistrationData +{ + QV4EngineRegistrationData() : extensionCount(0) {} + + QMutex mutex; + int extensionCount; +}; +Q_GLOBAL_STATIC(QV4EngineRegistrationData, registrationData); + +QMutex *ExecutionEngine::registrationMutex() +{ + return ®istrationData()->mutex; +} + +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 |