// 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 #include #include #include #include #include #include #include #include #if QT_CONFIG(regularexpression) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qv4function_p.h" #include #include #include #include #include "qv4symbol_p.h" #include "qv4setobject_p.h" #include "qv4mapobject_p.h" #include #include #include #include #include #include #include #include #include "qv4debugging_p.h" #include "qv4profiling_p.h" #include "qv4executableallocator_p.h" #include "qv4iterator_p.h" #include "qv4stringiterator_p.h" #include "qv4generatorobject_p.h" #include "qv4reflect_p.h" #include "qv4proxy_p.h" #include "qv4stackframe_p.h" #include "qv4stacklimits_p.h" #include "qv4atomics_p.h" #include "qv4urlobject_p.h" #include "qv4variantobject_p.h" #include "qv4sequenceobject_p.h" #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 #include #include #include #include #include #include #include #if QT_CONFIG(qml_locale) #include #endif #if QT_CONFIG(qml_xml_http_request) #include #include #endif #include #include #include #include #include #ifdef V4_USE_VALGRIND #include #endif QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE); DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE); using namespace QV4; // While engineSerial is odd the statics haven't been initialized. The engine that receives ID 1 // initializes the statics and sets engineSerial to 2 afterwards. // Each engine does engineSerial.fetchAndAddOrdered(2) on creation. Therefore engineSerial stays // odd while the statics are being initialized, and stays even afterwards. // Any further engines created while the statics are being initialized busy-wait until engineSerial // is even. static QBasicAtomicInt engineSerial = Q_BASIC_ATOMIC_INITIALIZER(1); int ExecutionEngine::s_maxCallDepth = -1; int ExecutionEngine::s_jitCallCountThreshold = 3; int ExecutionEngine::s_maxJSStackSize = 4 * 1024 * 1024; int ExecutionEngine::s_maxGCStackSize = 2 * 1024 * 1024; ReturnedValue throwTypeError(const FunctionObject *b, const QV4::Value *, const QV4::Value *, int) { return b->engine()->throwTypeError(); } template ReturnType convertJSValueToVariantType(const QJSValue &value) { const QVariant variant = value.toVariant(); return variant.metaType() == QMetaType::fromType() ? ReturnType() : variant.value(); } 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(); iface.iteratorCapabilities = RandomAccessCapability | BiDirectionalCapability | ForwardCapability; iface.addRemoveCapabilities = CanAddAtEnd; iface.sizeFn = [](const void *p) -> qsizetype { return static_cast(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(dataPtr); *data = static_cast(iterable)->property(quint32(index)).toVariant(); }; iface.createIteratorFn = [](void *iterable, QMetaSequenceInterface::Position pos) { void *iterator = nullptr; createNewIteratorIfNonExisting(&iterator); auto jsArrayIterator = static_cast(iterator); jsArrayIterator->index = 0; jsArrayIterator->data = reinterpret_cast(iterable); if (pos == QMetaSequenceInterface::AtEnd) { auto length = static_cast(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(iterator); jsArrayIterator->index = 0; jsArrayIterator->data = reinterpret_cast(iterable); if (pos == QMetaSequenceInterface::AtEnd) { auto length = static_cast(iterable)->property( QString::fromLatin1("length")).toInt(); jsArrayIterator->index = quint32(length); } return iterator; }; iface.advanceIteratorFn = [](void *iterator, qsizetype advanceBy) { static_cast(iterator)->index += quint32(advanceBy); }; iface.advanceConstIteratorFn = [](void *iterator, qsizetype advanceBy) { static_cast(iterator)->index += quint32(advanceBy); }; iface.valueAtIteratorFn = [](const void *iterator, void *dataPtr) -> void { const auto *arrayIterator = static_cast(iterator); const QJSValue *jsArray = arrayIterator->data; auto *data = static_cast(dataPtr); *data = jsArray->property(arrayIterator->index).toVariant(); }; iface.valueAtConstIteratorFn = [](const void *iterator, void *dataPtr) -> void { const auto *arrayIterator = static_cast(iterator); const QJSValue *jsArray = arrayIterator->data; auto *data = static_cast(dataPtr); *data = jsArray->property(arrayIterator->index).toVariant(); }; iface.destroyIteratorFn = [](const void *iterator) { delete static_cast(iterator); }; iface.destroyConstIteratorFn = [](const void *iterator) { delete static_cast(iterator); }; iface.compareIteratorFn = [](const void *p, const void *other) { auto this_ = static_cast(p); auto that_ = static_cast(other); return this_->index == that_->index && this_->data == that_->data; }; iface.compareConstIteratorFn = [](const void *p, const void *other) { auto this_ = static_cast(p); auto that_ = static_cast(other); return this_->index == that_->index && this_->data == that_->data; }; iface.copyIteratorFn = [](void *iterator, const void *otherIterator) { auto *otherIter = (static_cast(otherIterator)); static_cast(iterator)->index = otherIter->index; static_cast(iterator)->data = otherIter->data; }; iface.copyConstIteratorFn = [](void *iterator, const void *otherIterator) { auto *otherIter = (static_cast(otherIterator)); static_cast(iterator)->index = otherIter->index; static_cast(iterator)->data = otherIter->data; }; iface.diffIteratorFn = [](const void *iterator, const void *otherIterator) -> qsizetype { const auto *self = static_cast(iterator); const auto *other = static_cast(otherIterator); return self->index - other->index; }; iface.diffConstIteratorFn = [](const void *iterator, const void *otherIterator) -> qsizetype { const auto *self = static_cast(iterator); const auto *other = static_cast(otherIterator); return self->index - other->index; }; iface.addValueFn = [](void *iterable, const void *data, QMetaSequenceInterface::Position) { auto *jsvalue = static_cast(iterable); QV4::Scope scope(QJSValuePrivate::engine(jsvalue)); QV4::ScopedArrayObject a(scope, QJSValuePrivate::asManagedType(jsvalue)); QV4::ScopedValue v(scope, scope.engine->fromVariant(*static_cast(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::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::max(); qMetaTypeId(); qMetaTypeId >(); if (!QMetaType::hasRegisteredConverterFunction()) QMetaType::registerConverter(convertJSValueToVariantType); if (!QMetaType::hasRegisteredConverterFunction()) QMetaType::registerConverter(convertJSValueToVariantType); if (!QMetaType::hasRegisteredConverterFunction()) QMetaType::registerConverter(convertJSValueToVariantType); if (!QMetaType::hasRegisteredConverterFunction()) QMetaType::registerConverter(jsvalueToSequence); } ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) : executableAllocator(new QV4::ExecutableAllocator) , regExpAllocator(new QV4::ExecutableAllocator) , bumperPointerAllocator(new WTF::BumpPointerAllocator) , jsStack(new WTF::PageAllocation) , gcStack(new WTF::PageAllocation) , globalCode(nullptr) , publicEngine(jsEngine) , m_engineId(engineSerial.fetchAndAddOrdered(2)) , regExpCache(nullptr) , m_multiplyWrappedQObjects(nullptr) #if QT_CONFIG(qml_jit) , m_canAllocateExecutableMemory(OSAllocator::canAllocateExecutableMemory()) #endif #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(); } } if (s_maxCallDepth < 0) { const StackProperties stack = stackProperties(); cppStackBase = stack.base; cppStackLimit = stack.softLimit; } else { callDepth = 0; } // We allocate guard pages around our stacks. const size_t guardPages = 2 * WTF::pageSize(); memoryManager = new QV4::MemoryManager(this); // we don't want to run the gc while the initial setup is not done; not even in aggressive mode GCCriticalSection gcCriticalSection(this); // reserve space for the JS stack // we allow it to grow to a bit more than m_maxJSStackSize, as we can overshoot due to ScopedValues // allocated outside of JIT'ed methods. *jsStack = WTF::PageAllocation::allocate( s_maxJSStackSize + 256*1024 + guardPages, WTF::OSAllocator::JSVMStackPages, /* writable */ true, /* executable */ false, /* includesGuardPages */ true); jsStackBase = (Value *)jsStack->base(); #ifdef V4_USE_VALGRIND VALGRIND_MAKE_MEM_UNDEFINED(jsStackBase, m_maxJSStackSize + 256*1024); #endif jsStackTop = jsStackBase; *gcStack = WTF::PageAllocation::allocate( s_maxGCStackSize + guardPages, WTF::OSAllocator::JSVMStackPages, /* writable */ true, /* executable */ false, /* includesGuardPages */ true); exceptionValue = jsAlloca(1); *exceptionValue = Encode::undefined(); globalObject = static_cast(jsAlloca(1)); jsObjects = jsAlloca(NJSObjects); typedArrayPrototype = static_cast(jsAlloca(NTypedArrayTypes)); typedArrayCtors = static_cast(jsAlloca(NTypedArrayTypes)); jsStrings = jsAlloca(NJSStrings); jsSymbols = jsAlloca(NJSSymbols); // set up stack limits jsStackLimit = jsStackBase + s_maxJSStackSize/sizeof(Value); identifierTable = new IdentifierTable(this); memset(classes, 0, sizeof(classes)); classes[Class_Empty] = memoryManager->allocIC(); classes[Class_Empty]->init(this); classes[Class_MemberData] = classes[Class_Empty]->changeVTable(QV4::MemberData::staticVTable()); classes[Class_SimpleArrayData] = classes[Class_Empty]->changeVTable(QV4::SimpleArrayData::staticVTable()); classes[Class_SparseArrayData] = classes[Class_Empty]->changeVTable(QV4::SparseArrayData::staticVTable()); classes[Class_ExecutionContext] = classes[Class_Empty]->changeVTable(QV4::ExecutionContext::staticVTable()); classes[Class_CallContext] = classes[Class_Empty]->changeVTable(QV4::CallContext::staticVTable()); classes[Class_QmlContext] = classes[Class_Empty]->changeVTable(QV4::QmlContext::staticVTable()); Scope scope(this); Scoped ic(scope); ic = classes[Class_Empty]->changeVTable(QV4::Object::staticVTable()); jsObjects[ObjectProto] = memoryManager->allocObject(ic->d()); classes[Class_Object] = ic->changePrototype(objectPrototype()->d()); classes[Class_QmlContextWrapper] = classes[Class_Object]->changeVTable(QV4::QQmlContextWrapper::staticVTable()); ic = newInternalClass(QV4::StringObject::staticVTable(), objectPrototype()); jsObjects[StringProto] = memoryManager->allocObject(ic->d(), /*init =*/ false); classes[Class_String] = classes[Class_Empty]->changeVTable(QV4::String::staticVTable())->changePrototype(stringPrototype()->d()); Q_ASSERT(stringPrototype()->d() && classes[Class_String]->prototype); jsObjects[SymbolProto] = memoryManager->allocate(); classes[Class_Symbol] = classes[EngineBase::Class_Empty]->changeVTable(QV4::Symbol::staticVTable())->changePrototype(symbolPrototype()->d()); jsStrings[String_Empty] = newIdentifier(QString()); jsStrings[String_undefined] = newIdentifier(QStringLiteral("undefined")); jsStrings[String_null] = newIdentifier(QStringLiteral("null")); jsStrings[String_true] = newIdentifier(QStringLiteral("true")); jsStrings[String_false] = newIdentifier(QStringLiteral("false")); jsStrings[String_boolean] = newIdentifier(QStringLiteral("boolean")); jsStrings[String_number] = newIdentifier(QStringLiteral("number")); jsStrings[String_string] = newIdentifier(QStringLiteral("string")); jsStrings[String_default] = newIdentifier(QStringLiteral("default")); jsStrings[String_symbol] = newIdentifier(QStringLiteral("symbol")); jsStrings[String_object] = newIdentifier(QStringLiteral("object")); jsStrings[String_function] = newIdentifier(QStringLiteral("function")); jsStrings[String_length] = newIdentifier(QStringLiteral("length")); jsStrings[String_prototype] = newIdentifier(QStringLiteral("prototype")); jsStrings[String_constructor] = newIdentifier(QStringLiteral("constructor")); jsStrings[String_arguments] = newIdentifier(QStringLiteral("arguments")); jsStrings[String_caller] = newIdentifier(QStringLiteral("caller")); jsStrings[String_callee] = newIdentifier(QStringLiteral("callee")); jsStrings[String_this] = newIdentifier(QStringLiteral("this")); jsStrings[String___proto__] = newIdentifier(QStringLiteral("__proto__")); jsStrings[String_enumerable] = newIdentifier(QStringLiteral("enumerable")); jsStrings[String_configurable] = newIdentifier(QStringLiteral("configurable")); jsStrings[String_writable] = newIdentifier(QStringLiteral("writable")); jsStrings[String_value] = newIdentifier(QStringLiteral("value")); jsStrings[String_get] = newIdentifier(QStringLiteral("get")); jsStrings[String_set] = newIdentifier(QStringLiteral("set")); jsStrings[String_eval] = newIdentifier(QStringLiteral("eval")); jsStrings[String_uintMax] = newIdentifier(QStringLiteral("4294967295")); jsStrings[String_name] = newIdentifier(QStringLiteral("name")); jsStrings[String_index] = newIdentifier(QStringLiteral("index")); jsStrings[String_input] = newIdentifier(QStringLiteral("input")); jsStrings[String_toString] = newIdentifier(QStringLiteral("toString")); jsStrings[String_toLocaleString] = newIdentifier(QStringLiteral("toLocaleString")); jsStrings[String_destroy] = newIdentifier(QStringLiteral("destroy")); jsStrings[String_valueOf] = newIdentifier(QStringLiteral("valueOf")); jsStrings[String_byteLength] = newIdentifier(QStringLiteral("byteLength")); jsStrings[String_byteOffset] = newIdentifier(QStringLiteral("byteOffset")); jsStrings[String_buffer] = newIdentifier(QStringLiteral("buffer")); jsStrings[String_lastIndex] = newIdentifier(QStringLiteral("lastIndex")); jsStrings[String_next] = newIdentifier(QStringLiteral("next")); jsStrings[String_done] = newIdentifier(QStringLiteral("done")); jsStrings[String_return] = newIdentifier(QStringLiteral("return")); jsStrings[String_throw] = newIdentifier(QStringLiteral("throw")); jsStrings[String_global] = newIdentifier(QStringLiteral("global")); jsStrings[String_ignoreCase] = newIdentifier(QStringLiteral("ignoreCase")); jsStrings[String_multiline] = newIdentifier(QStringLiteral("multiline")); jsStrings[String_unicode] = newIdentifier(QStringLiteral("unicode")); jsStrings[String_sticky] = newIdentifier(QStringLiteral("sticky")); jsStrings[String_source] = newIdentifier(QStringLiteral("source")); jsStrings[String_flags] = newIdentifier(QStringLiteral("flags")); jsSymbols[Symbol_hasInstance] = Symbol::create(this, QStringLiteral("@Symbol.hasInstance")); jsSymbols[Symbol_isConcatSpreadable] = Symbol::create(this, QStringLiteral("@Symbol.isConcatSpreadable")); jsSymbols[Symbol_iterator] = Symbol::create(this, QStringLiteral("@Symbol.iterator")); jsSymbols[Symbol_match] = Symbol::create(this, QStringLiteral("@Symbol.match")); jsSymbols[Symbol_replace] = Symbol::create(this, QStringLiteral("@Symbol.replace")); jsSymbols[Symbol_search] = Symbol::create(this, QStringLiteral("@Symbol.search")); jsSymbols[Symbol_species] = Symbol::create(this, QStringLiteral("@Symbol.species")); jsSymbols[Symbol_split] = Symbol::create(this, QStringLiteral("@Symbol.split")); jsSymbols[Symbol_toPrimitive] = Symbol::create(this, QStringLiteral("@Symbol.toPrimitive")); jsSymbols[Symbol_toStringTag] = Symbol::create(this, QStringLiteral("@Symbol.toStringTag")); jsSymbols[Symbol_unscopables] = Symbol::create(this, QStringLiteral("@Symbol.unscopables")); jsSymbols[Symbol_revokableProxy] = Symbol::create(this, QStringLiteral("@Proxy.revokableProxy")); ic = newInternalClass(ArrayPrototype::staticVTable(), objectPrototype()); Q_ASSERT(ic->d()->prototype); ic = ic->addMember(id_length()->propertyKey(), Attr_NotConfigurable|Attr_NotEnumerable); Q_ASSERT(ic->d()->prototype); jsObjects[ArrayProto] = memoryManager->allocObject(ic->d()); classes[Class_ArrayObject] = ic->changePrototype(arrayPrototype()->d()); jsObjects[PropertyListProto] = memoryManager->allocate(); Scoped argsClass(scope); argsClass = newInternalClass(ArgumentsObject::staticVTable(), objectPrototype()); argsClass = argsClass->addMember(id_length()->propertyKey(), Attr_NotEnumerable); argsClass = argsClass->addMember(symbol_iterator()->propertyKey(), Attr_Data|Attr_NotEnumerable); classes[Class_ArgumentsObject] = argsClass->addMember(id_callee()->propertyKey(), Attr_Data|Attr_NotEnumerable); argsClass = newInternalClass(StrictArgumentsObject::staticVTable(), objectPrototype()); argsClass = argsClass->addMember(id_length()->propertyKey(), Attr_NotEnumerable); argsClass = argsClass->addMember(symbol_iterator()->propertyKey(), Attr_Data|Attr_NotEnumerable); classes[Class_StrictArgumentsObject] = argsClass->addMember(id_callee()->propertyKey(), Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable); *static_cast(globalObject) = newObject(); Q_ASSERT(globalObject->d()->vtable()); initRootContext(); ic = newInternalClass(QV4::StringObject::staticVTable(), objectPrototype()); ic = ic->addMember(id_length()->propertyKey(), Attr_ReadOnly); classes[Class_StringObject] = ic->changePrototype(stringPrototype()->d()); Q_ASSERT(classes[Class_StringObject]->verifyIndex(id_length()->propertyKey(), Heap::StringObject::LengthPropertyIndex)); classes[Class_SymbolObject] = newInternalClass(QV4::SymbolObject::staticVTable(), symbolPrototype()); jsObjects[NumberProto] = memoryManager->allocate(); jsObjects[BooleanProto] = memoryManager->allocate(); jsObjects[DateProto] = memoryManager->allocate(); #if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS) InternalClassEntry *index = nullptr; #else InternalClassEntry _index; auto *index = &_index; #endif ic = newInternalClass(QV4::FunctionPrototype::staticVTable(), objectPrototype()); auto addProtoHasInstance = [&] { // Add an invalid prototype slot, so that all function objects have the same layout // This helps speed up instanceof operations and other things where we need to query // prototype property (as we always know it's location) ic = ic->addMember(id_prototype()->propertyKey(), Attr_Invalid, index); Q_ASSERT(index->index == Heap::FunctionObject::Index_Prototype); // add an invalid @hasInstance slot, so that we can quickly track whether the // hasInstance method has been reimplemented. This is required for a fast // instanceof implementation ic = ic->addMember(symbol_hasInstance()->propertyKey(), Attr_Invalid, index); Q_ASSERT(index->index == Heap::FunctionObject::Index_HasInstance); }; addProtoHasInstance(); jsObjects[FunctionProto] = memoryManager->allocObject(ic->d()); ic = newInternalClass(FunctionObject::staticVTable(), functionPrototype()); addProtoHasInstance(); classes[Class_FunctionObject] = ic->d(); ic = ic->addMember(id_name()->propertyKey(), Attr_ReadOnly, index); Q_ASSERT(index->index == Heap::ArrowFunction::Index_Name); ic = ic->addMember(id_length()->propertyKey(), Attr_ReadOnly_ButConfigurable, index); Q_ASSERT(index->index == Heap::ArrowFunction::Index_Length); classes[Class_ArrowFunction] = ic->changeVTable(ArrowFunction::staticVTable()); ic = ic->changeVTable(MemberFunction::staticVTable()); classes[Class_MemberFunction] = ic->d(); ic = ic->changeVTable(GeneratorFunction::staticVTable()); classes[Class_GeneratorFunction] = ic->d(); ic = ic->changeVTable(MemberGeneratorFunction::staticVTable()); classes[Class_MemberGeneratorFunction] = ic->d(); ic = ic->changeMember(id_prototype()->propertyKey(), Attr_NotConfigurable|Attr_NotEnumerable); ic = ic->changeVTable(ScriptFunction::staticVTable()); classes[Class_ScriptFunction] = ic->d(); ic = ic->changeVTable(ConstructorFunction::staticVTable()); classes[Class_ConstructorFunction] = ic->d(); classes[Class_ObjectProto] = classes[Class_Object]->addMember(id_constructor()->propertyKey(), Attr_NotEnumerable, index); Q_ASSERT(index->index == Heap::FunctionObject::Index_ProtoConstructor); jsObjects[GeneratorProto] = memoryManager->allocObject(classes[Class_Object]); classes[Class_GeneratorObject] = newInternalClass(QV4::GeneratorObject::staticVTable(), generatorPrototype()); ScopedString str(scope); classes[Class_RegExp] = classes[Class_Empty]->changeVTable(QV4::RegExp::staticVTable()); ic = newInternalClass(QV4::RegExpObject::staticVTable(), objectPrototype()); ic = ic->addMember(id_lastIndex()->propertyKey(), Attr_NotEnumerable|Attr_NotConfigurable, index); Q_ASSERT(index->index == RegExpObject::Index_LastIndex); jsObjects[RegExpProto] = memoryManager->allocObject(classes[Class_Object]); classes[Class_RegExpObject] = ic->changePrototype(regExpPrototype()->d()); ic = classes[Class_ArrayObject]->addMember(id_index()->propertyKey(), Attr_Data, index); Q_ASSERT(index->index == RegExpObject::Index_ArrayIndex); classes[Class_RegExpExecArray] = ic->addMember(id_input()->propertyKey(), Attr_Data, index); Q_ASSERT(index->index == RegExpObject::Index_ArrayInput); ic = newInternalClass(ErrorObject::staticVTable(), nullptr); ic = ic->addMember((str = newIdentifier(QStringLiteral("stack")))->propertyKey(), Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable, index); Q_ASSERT(index->index == ErrorObject::Index_Stack); Q_ASSERT(index->setterIndex == ErrorObject::Index_StackSetter); ic = ic->addMember((str = newIdentifier(QStringLiteral("fileName")))->propertyKey(), Attr_Data|Attr_NotEnumerable, index); Q_ASSERT(index->index == ErrorObject::Index_FileName); ic = ic->addMember((str = newIdentifier(QStringLiteral("lineNumber")))->propertyKey(), Attr_Data|Attr_NotEnumerable, index); classes[Class_ErrorObject] = ic->d(); Q_ASSERT(index->index == ErrorObject::Index_LineNumber); classes[Class_ErrorObjectWithMessage] = ic->addMember((str = newIdentifier(QStringLiteral("message")))->propertyKey(), Attr_Data|Attr_NotEnumerable, index); Q_ASSERT(index->index == ErrorObject::Index_Message); ic = newInternalClass(Object::staticVTable(), objectPrototype()); ic = ic->addMember(id_constructor()->propertyKey(), Attr_Data|Attr_NotEnumerable, index); Q_ASSERT(index->index == ErrorPrototype::Index_Constructor); ic = ic->addMember((str = newIdentifier(QStringLiteral("message")))->propertyKey(), Attr_Data|Attr_NotEnumerable, index); Q_ASSERT(index->index == ErrorPrototype::Index_Message); classes[Class_ErrorProto] = ic->addMember(id_name()->propertyKey(), Attr_Data|Attr_NotEnumerable, index); Q_ASSERT(index->index == ErrorPrototype::Index_Name); classes[Class_ProxyObject] = classes[Class_Empty]->changeVTable(ProxyObject::staticVTable()); classes[Class_ProxyFunctionObject] = classes[Class_Empty]->changeVTable(ProxyFunctionObject::staticVTable()); jsObjects[GetStack_Function] = FunctionObject::createBuiltinFunction(this, str = newIdentifier(QStringLiteral("stack")), ErrorObject::method_get_stack, 0); jsObjects[ErrorProto] = memoryManager->allocObject(classes[Class_ErrorProto]); ic = classes[Class_ErrorProto]->changePrototype(errorPrototype()->d()); jsObjects[EvalErrorProto] = memoryManager->allocObject(ic->d()); jsObjects[RangeErrorProto] = memoryManager->allocObject(ic->d()); jsObjects[ReferenceErrorProto] = memoryManager->allocObject(ic->d()); jsObjects[SyntaxErrorProto] = memoryManager->allocObject(ic->d()); jsObjects[TypeErrorProto] = memoryManager->allocObject(ic->d()); jsObjects[URIErrorProto] = memoryManager->allocObject(ic->d()); jsObjects[VariantProto] = memoryManager->allocate(); Q_ASSERT(variantPrototype()->getPrototypeOf() == objectPrototype()->d()); ic = newInternalClass(SequencePrototype::staticVTable(), SequencePrototype::defaultPrototype(this)); jsObjects[SequenceProto] = ScopedValue(scope, memoryManager->allocObject(ic->d())); ExecutionContext *global = rootContext(); jsObjects[Object_Ctor] = memoryManager->allocate(global); jsObjects[String_Ctor] = memoryManager->allocate(global); jsObjects[Symbol_Ctor] = memoryManager->allocate(global); jsObjects[Number_Ctor] = memoryManager->allocate(global); jsObjects[Boolean_Ctor] = memoryManager->allocate(global); jsObjects[Array_Ctor] = memoryManager->allocate(global); jsObjects[Function_Ctor] = memoryManager->allocate(global); jsObjects[GeneratorFunction_Ctor] = memoryManager->allocate(global); jsObjects[Date_Ctor] = memoryManager->allocate(global); jsObjects[RegExp_Ctor] = memoryManager->allocate(global); jsObjects[Error_Ctor] = memoryManager->allocate(global); jsObjects[EvalError_Ctor] = memoryManager->allocate(global); jsObjects[RangeError_Ctor] = memoryManager->allocate(global); jsObjects[ReferenceError_Ctor] = memoryManager->allocate(global); jsObjects[SyntaxError_Ctor] = memoryManager->allocate(global); jsObjects[TypeError_Ctor] = memoryManager->allocate(global); jsObjects[URIError_Ctor] = memoryManager->allocate(global); jsObjects[IteratorProto] = memoryManager->allocate(); ic = newInternalClass(ForInIteratorPrototype::staticVTable(), iteratorPrototype()); jsObjects[ForInIteratorProto] = memoryManager->allocObject(ic); ic = newInternalClass(SetIteratorPrototype::staticVTable(), iteratorPrototype()); jsObjects[MapIteratorProto] = memoryManager->allocObject(ic); ic = newInternalClass(SetIteratorPrototype::staticVTable(), iteratorPrototype()); jsObjects[SetIteratorProto] = memoryManager->allocObject(ic); ic = newInternalClass(ArrayIteratorPrototype::staticVTable(), iteratorPrototype()); jsObjects[ArrayIteratorProto] = memoryManager->allocObject(ic); ic = newInternalClass(StringIteratorPrototype::staticVTable(), iteratorPrototype()); jsObjects[StringIteratorProto] = memoryManager->allocObject(ic); // // url // jsObjects[Url_Ctor] = memoryManager->allocate(global); jsObjects[UrlProto] = memoryManager->allocate(); jsObjects[UrlSearchParams_Ctor] = memoryManager->allocate(global); jsObjects[UrlSearchParamsProto] = memoryManager->allocate(); str = newString(QStringLiteral("get [Symbol.species]")); jsObjects[GetSymbolSpecies] = FunctionObject::createBuiltinFunction(this, str, ArrayPrototype::method_get_species, 0); static_cast(objectPrototype())->init(this, objectCtor()); static_cast(stringPrototype())->init(this, stringCtor()); static_cast(symbolPrototype())->init(this, symbolCtor()); static_cast(numberPrototype())->init(this, numberCtor()); static_cast(booleanPrototype())->init(this, booleanCtor()); static_cast(arrayPrototype())->init(this, arrayCtor()); static_cast(propertyListPrototype())->init(); static_cast(datePrototype())->init(this, dateCtor()); static_cast(functionPrototype())->init(this, functionCtor()); static_cast(generatorPrototype())->init(this, generatorFunctionCtor()); static_cast(regExpPrototype())->init(this, regExpCtor()); static_cast(errorPrototype())->init(this, errorCtor()); static_cast(evalErrorPrototype())->init(this, evalErrorCtor()); static_cast(rangeErrorPrototype())->init(this, rangeErrorCtor()); static_cast(referenceErrorPrototype())->init(this, referenceErrorCtor()); static_cast(syntaxErrorPrototype())->init(this, syntaxErrorCtor()); static_cast(typeErrorPrototype())->init(this, typeErrorCtor()); static_cast(uRIErrorPrototype())->init(this, uRIErrorCtor()); static_cast(urlPrototype())->init(this, urlCtor()); static_cast(urlSearchParamsPrototype())->init(this, urlSearchParamsCtor()); static_cast(iteratorPrototype())->init(this); static_cast(forInIteratorPrototype())->init(this); static_cast(mapIteratorPrototype())->init(this); static_cast(setIteratorPrototype())->init(this); static_cast(arrayIteratorPrototype())->init(this); static_cast(stringIteratorPrototype())->init(this); static_cast(variantPrototype())->init(); sequencePrototype()->cast()->init(); jsObjects[WeakMap_Ctor] = memoryManager->allocate(global); jsObjects[WeakMapProto] = memoryManager->allocate(); static_cast(weakMapPrototype())->init(this, weakMapCtor()); jsObjects[Map_Ctor] = memoryManager->allocate(global); jsObjects[MapProto] = memoryManager->allocate(); static_cast(mapPrototype())->init(this, mapCtor()); jsObjects[WeakSet_Ctor] = memoryManager->allocate(global); jsObjects[WeakSetProto] = memoryManager->allocate(); static_cast(weakSetPrototype())->init(this, weakSetCtor()); jsObjects[Set_Ctor] = memoryManager->allocate(global); jsObjects[SetProto] = memoryManager->allocate(); static_cast(setPrototype())->init(this, setCtor()); // // promises // jsObjects[Promise_Ctor] = memoryManager->allocate(global); jsObjects[PromiseProto] = memoryManager->allocate(); static_cast(promisePrototype())->init(this, promiseCtor()); // typed arrays jsObjects[SharedArrayBuffer_Ctor] = memoryManager->allocate(global); jsObjects[SharedArrayBufferProto] = memoryManager->allocate(); static_cast(sharedArrayBufferPrototype())->init(this, sharedArrayBufferCtor()); jsObjects[ArrayBuffer_Ctor] = memoryManager->allocate(global); jsObjects[ArrayBufferProto] = memoryManager->allocate(); static_cast(arrayBufferPrototype())->init(this, arrayBufferCtor()); jsObjects[DataView_Ctor] = memoryManager->allocate(global); jsObjects[DataViewProto] = memoryManager->allocate(); static_cast(dataViewPrototype())->init(this, dataViewCtor()); jsObjects[ValueTypeProto] = (Heap::Base *) nullptr; jsObjects[SignalHandlerProto] = (Heap::Base *) nullptr; jsObjects[IntrinsicTypedArray_Ctor] = memoryManager->allocate(global); jsObjects[IntrinsicTypedArrayProto] = memoryManager->allocate(); static_cast(intrinsicTypedArrayPrototype()) ->init(this, static_cast(intrinsicTypedArrayCtor())); for (int i = 0; i < NTypedArrayTypes; ++i) { static_cast(typedArrayCtors[i]) = memoryManager->allocate(global, Heap::TypedArray::Type(i)); static_cast(typedArrayPrototype[i]) = memoryManager->allocate(Heap::TypedArray::Type(i)); typedArrayPrototype[i].as()->init(this, static_cast(typedArrayCtors[i].as())); } // // set up the global object // rootContext()->d()->activation.set(scope.engine, globalObject->d()); Q_ASSERT(globalObject->d()->vtable()); globalObject->defineDefaultProperty(QStringLiteral("Object"), *objectCtor()); globalObject->defineDefaultProperty(QStringLiteral("String"), *stringCtor()); globalObject->defineDefaultProperty(QStringLiteral("Symbol"), *symbolCtor()); FunctionObject *numberObject = numberCtor(); globalObject->defineDefaultProperty(QStringLiteral("Number"), *numberObject); globalObject->defineDefaultProperty(QStringLiteral("Boolean"), *booleanCtor()); globalObject->defineDefaultProperty(QStringLiteral("Array"), *arrayCtor()); globalObject->defineDefaultProperty(QStringLiteral("Function"), *functionCtor()); globalObject->defineDefaultProperty(QStringLiteral("Date"), *dateCtor()); globalObject->defineDefaultProperty(QStringLiteral("RegExp"), *regExpCtor()); globalObject->defineDefaultProperty(QStringLiteral("Error"), *errorCtor()); globalObject->defineDefaultProperty(QStringLiteral("EvalError"), *evalErrorCtor()); globalObject->defineDefaultProperty(QStringLiteral("RangeError"), *rangeErrorCtor()); globalObject->defineDefaultProperty(QStringLiteral("ReferenceError"), *referenceErrorCtor()); globalObject->defineDefaultProperty(QStringLiteral("SyntaxError"), *syntaxErrorCtor()); 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()); globalObject->defineDefaultProperty(QStringLiteral("DataView"), *dataViewCtor()); globalObject->defineDefaultProperty(QStringLiteral("WeakSet"), *weakSetCtor()); globalObject->defineDefaultProperty(QStringLiteral("Set"), *setCtor()); globalObject->defineDefaultProperty(QStringLiteral("WeakMap"), *weakMapCtor()); globalObject->defineDefaultProperty(QStringLiteral("Map"), *mapCtor()); for (int i = 0; i < NTypedArrayTypes; ++i) globalObject->defineDefaultProperty((str = typedArrayCtors[i].as()->name()), typedArrayCtors[i]); ScopedObject o(scope); globalObject->defineDefaultProperty(QStringLiteral("Atomics"), (o = memoryManager->allocate())); globalObject->defineDefaultProperty(QStringLiteral("Math"), (o = memoryManager->allocate())); globalObject->defineDefaultProperty(QStringLiteral("JSON"), (o = memoryManager->allocate())); globalObject->defineDefaultProperty(QStringLiteral("Reflect"), (o = memoryManager->allocate())); globalObject->defineDefaultProperty(QStringLiteral("Proxy"), (o = memoryManager->allocate(rootContext()))); globalObject->defineReadonlyProperty(QStringLiteral("undefined"), Value::undefinedValue()); globalObject->defineReadonlyProperty(QStringLiteral("NaN"), Value::fromDouble(std::numeric_limits::quiet_NaN())); globalObject->defineReadonlyProperty(QStringLiteral("Infinity"), Value::fromDouble(Q_INFINITY)); jsObjects[Eval_Function] = memoryManager->allocate(global); globalObject->defineDefaultProperty(QStringLiteral("eval"), *evalFunction()); // ES6: 20.1.2.12 & 20.1.2.13: // parseInt and parseFloat must be the same FunctionObject on the global & // Number object. { QString piString(QStringLiteral("parseInt")); QString pfString(QStringLiteral("parseFloat")); Scope scope(this); ScopedString pi(scope, newIdentifier(piString)); ScopedString pf(scope, newIdentifier(pfString)); ScopedFunctionObject parseIntFn(scope, FunctionObject::createBuiltinFunction(this, pi, GlobalFunctions::method_parseInt, 2)); ScopedFunctionObject parseFloatFn(scope, FunctionObject::createBuiltinFunction(this, pf, GlobalFunctions::method_parseFloat, 1)); globalObject->defineDefaultProperty(piString, parseIntFn); globalObject->defineDefaultProperty(pfString, parseFloatFn); numberObject->defineDefaultProperty(piString, parseIntFn); numberObject->defineDefaultProperty(pfString, parseFloatFn); } globalObject->defineDefaultProperty(QStringLiteral("isNaN"), GlobalFunctions::method_isNaN, 1); globalObject->defineDefaultProperty(QStringLiteral("isFinite"), GlobalFunctions::method_isFinite, 1); globalObject->defineDefaultProperty(QStringLiteral("decodeURI"), GlobalFunctions::method_decodeURI, 1); globalObject->defineDefaultProperty(QStringLiteral("decodeURIComponent"), GlobalFunctions::method_decodeURIComponent, 1); globalObject->defineDefaultProperty(QStringLiteral("encodeURI"), GlobalFunctions::method_encodeURI, 1); globalObject->defineDefaultProperty(QStringLiteral("encodeURIComponent"), GlobalFunctions::method_encodeURIComponent, 1); globalObject->defineDefaultProperty(QStringLiteral("escape"), GlobalFunctions::method_escape, 1); globalObject->defineDefaultProperty(QStringLiteral("unescape"), GlobalFunctions::method_unescape, 1); ScopedFunctionObject t(scope, memoryManager->allocate(rootContext(), nullptr, ::throwTypeError)); t->defineReadonlyProperty(id_length(), Value::fromInt32(0)); t->setInternalClass(t->internalClass()->cryopreserved()); jsObjects[ThrowerObject] = t; ScopedProperty pd(scope); pd->value = thrower(); 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() { for (auto val : nativeModules) { PersistentValueStorage::free(val); } nativeModules.clear(); qDeleteAll(m_extensionData); delete m_multiplyWrappedQObjects; m_multiplyWrappedQObjects = nullptr; delete identifierTable; delete memoryManager; 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; delete regExpAllocator; delete executableAllocator; jsStack->deallocate(); delete jsStack; gcStack->deallocate(); delete gcStack; #if QT_CONFIG(qml_xml_http_request) qt_rem_qmlxmlhttprequest(this, m_xmlHttpRequestData); m_xmlHttpRequestData = nullptr; #endif } #if QT_CONFIG(qml_debug) void ExecutionEngine::setDebugger(Debugging::Debugger *debugger) { Q_ASSERT(!m_debugger); m_debugger.reset(debugger); } void ExecutionEngine::setProfiler(Profiling::Profiler *profiler) { Q_ASSERT(!m_profiler); m_profiler.reset(profiler); } #endif // QT_CONFIG(qml_debug) void ExecutionEngine::initRootContext() { Scope scope(this); Scoped r(scope, memoryManager->allocManaged()); r->d_unchecked()->init(Heap::ExecutionContext::Type_GlobalContext); r->d()->activation.set(this, globalObject->d()); jsObjects[RootContext] = r; jsObjects[ScriptContext] = r; jsObjects[IntegerNull] = Encode((int)0); } Heap::InternalClass *ExecutionEngine::newClass(Heap::InternalClass *other) { Heap::InternalClass *ic = memoryManager->allocIC(); ic->init(other); return ic; } Heap::InternalClass *ExecutionEngine::newInternalClass(const VTable *vtable, Object *prototype) { Scope scope(this); Scoped ic(scope, internalClasses(Class_Empty)->changeVTable(vtable)); return ic->changePrototype(prototype ? prototype->d() : nullptr); } Heap::Object *ExecutionEngine::newObject() { return memoryManager->allocate(); } Heap::Object *ExecutionEngine::newObject(Heap::InternalClass *internalClass) { return memoryManager->allocObject(internalClass); } Heap::String *ExecutionEngine::newString(const QString &s) { return memoryManager->allocWithStringData(s.size() * sizeof(QChar), s); } Heap::String *ExecutionEngine::newIdentifier(const QString &text) { Scope scope(this); ScopedString s(scope, memoryManager->allocWithStringData(text.size() * sizeof(QChar), text)); s->toPropertyKey(); return s->d(); } Heap::Object *ExecutionEngine::newStringObject(const String *string) { return memoryManager->allocate(string); } Heap::Object *ExecutionEngine::newSymbolObject(const Symbol *symbol) { return memoryManager->allocObject(classes[Class_SymbolObject], symbol); } Heap::Object *ExecutionEngine::newNumberObject(double value) { return memoryManager->allocate(value); } Heap::Object *ExecutionEngine::newBooleanObject(bool b) { return memoryManager->allocate(b); } Heap::ArrayObject *ExecutionEngine::newArrayObject(int count) { Scope scope(this); ScopedArrayObject object(scope, memoryManager->allocate()); if (count) { if (count < 0x1000) object->arrayReserve(count); object->setArrayLengthUnchecked(count); } return object->d(); } Heap::ArrayObject *ExecutionEngine::newArrayObject(const Value *values, int length) { Scope scope(this); ScopedArrayObject a(scope, memoryManager->allocate()); if (length) { size_t size = sizeof(Heap::ArrayData) + (length-1)*sizeof(Value); Heap::SimpleArrayData *d = scope.engine->memoryManager->allocManaged(size); d->init(); d->type = Heap::ArrayData::Simple; d->offset = 0; d->values.alloc = length; d->values.size = length; // this doesn't require a write barrier, things will be ok, when the new array data gets inserted into // the parent object memcpy(&d->values.values, values, length*sizeof(Value)); a->d()->arrayData.set(this, d); a->setArrayLengthUnchecked(length); } return a->d(); } Heap::ArrayObject *ExecutionEngine::newArrayObject(const QStringList &list) { return memoryManager->allocate(list); } Heap::ArrayObject *ExecutionEngine::newArrayObject(Heap::InternalClass *internalClass) { return memoryManager->allocObject(internalClass); } Heap::ArrayBuffer *ExecutionEngine::newArrayBuffer(const QByteArray &array) { return memoryManager->allocate(array); } Heap::ArrayBuffer *ExecutionEngine::newArrayBuffer(size_t length) { return memoryManager->allocate(length); } Heap::DateObject *ExecutionEngine::newDateObject(double dateTime) { return memoryManager->allocate(dateTime); } Heap::DateObject *ExecutionEngine::newDateObject(const QDateTime &dateTime) { return memoryManager->allocate(dateTime); } Heap::DateObject *ExecutionEngine::newDateObject( QDate date, Heap::Object *parent, int index, uint flags) { return memoryManager->allocate( date, parent, index, Heap::ReferenceObject::Flags(flags)); } Heap::DateObject *ExecutionEngine::newDateObject( QTime time, Heap::Object *parent, int index, uint flags) { return memoryManager->allocate( time, parent, index, Heap::ReferenceObject::Flags(flags)); } Heap::DateObject *ExecutionEngine::newDateObject( QDateTime dateTime, Heap::Object *parent, int index, uint flags) { return memoryManager->allocate( dateTime, parent, index, Heap::ReferenceObject::Flags(flags)); } Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QString &pattern, int flags) { Scope scope(this); Scoped re(scope, RegExp::create(this, pattern, static_cast(flags))); return newRegExpObject(re); } Heap::RegExpObject *ExecutionEngine::newRegExpObject(RegExp *re) { return memoryManager->allocate(re); } #if QT_CONFIG(regularexpression) Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegularExpression &re) { return memoryManager->allocate(re); } #endif Heap::UrlObject *ExecutionEngine::newUrlObject() { return memoryManager->allocate(); } Heap::UrlObject *ExecutionEngine::newUrlObject(const QUrl &url) { Scope scope(this); Scoped urlObject(scope, newUrlObject()); urlObject->setUrl(url); return urlObject->d(); } Heap::UrlSearchParamsObject *ExecutionEngine::newUrlSearchParamsObject() { return memoryManager->allocate(); } Heap::Object *ExecutionEngine::newErrorObject(const Value &value) { return ErrorObject::create(this, value, errorCtor()); } Heap::Object *ExecutionEngine::newErrorObject(const QString &message) { return ErrorObject::create(this, message); } Heap::Object *ExecutionEngine::newSyntaxErrorObject(const QString &message) { return ErrorObject::create(this, message); } Heap::Object *ExecutionEngine::newSyntaxErrorObject(const QString &message, const QString &fileName, int line, int column) { return ErrorObject::create(this, message, fileName, line, column); } Heap::Object *ExecutionEngine::newReferenceErrorObject(const QString &message) { return ErrorObject::create(this, message); } Heap::Object *ExecutionEngine::newReferenceErrorObject(const QString &message, const QString &fileName, int line, int column) { return ErrorObject::create(this, message, fileName, line, column); } Heap::Object *ExecutionEngine::newTypeErrorObject(const QString &message) { return ErrorObject::create(this, message); } Heap::Object *ExecutionEngine::newRangeErrorObject(const QString &message) { return ErrorObject::create(this, message); } Heap::Object *ExecutionEngine::newURIErrorObject(const Value &message) { return ErrorObject::create(this, message, uRIErrorCtor()); } Heap::PromiseObject *ExecutionEngine::newPromiseObject() { if (!m_reactionHandler) { m_reactionHandler.reset(new Promise::ReactionHandler); } Scope scope(this); Scoped object(scope, memoryManager->allocate(this)); return object->d(); } Heap::Object *ExecutionEngine::newPromiseObject(const QV4::FunctionObject *thisObject, const QV4::PromiseCapability *capability) { if (!m_reactionHandler) { m_reactionHandler.reset(new Promise::ReactionHandler); } Scope scope(this); Scoped executor(scope, memoryManager->allocate()); executor->d()->capabilities.set(this, capability->d()); executor->insertMember(id_length(), Primitive::fromInt32(2), Attr_NotWritable|Attr_NotEnumerable); ScopedObject object(scope, thisObject->callAsConstructor(executor, 1)); return object->d(); } Promise::ReactionHandler *ExecutionEngine::getPromiseReactionHandler() { Q_ASSERT(m_reactionHandler); return m_reactionHandler.data(); } Heap::Object *ExecutionEngine::newURIErrorObject(const QString &message) { return ErrorObject::create(this, message); } Heap::Object *ExecutionEngine::newEvalErrorObject(const QString &message) { return ErrorObject::create(this, message); } Heap::Object *ExecutionEngine::newVariantObject(const QMetaType type, const void *data) { return memoryManager->allocate(type, data); } Heap::Object *ExecutionEngine::newForInIteratorObject(Object *o) { Scope scope(this); ScopedObject obj(scope, memoryManager->allocate(o)); return obj->d(); } Heap::Object *ExecutionEngine::newMapIteratorObject(Object *o) { return memoryManager->allocate(o->d(), this); } Heap::Object *ExecutionEngine::newSetIteratorObject(Object *o) { return memoryManager->allocate(o->d(), this); } Heap::Object *ExecutionEngine::newArrayIteratorObject(Object *o) { return memoryManager->allocate(o->d(), this); } Heap::QmlContext *ExecutionEngine::qmlContext() const { return currentStackFrame ? static_cast(qmlContext(currentContext()->d())) : nullptr; } QObject *ExecutionEngine::qmlScopeObject() const { Heap::QmlContext *ctx = qmlContext(); if (!ctx) return nullptr; return ctx->qml()->scopeObject; } QQmlRefPointer ExecutionEngine::callingQmlContext() const { Heap::QmlContext *ctx = qmlContext(); if (!ctx) return nullptr; return ctx->qml()->context; } StackTrace ExecutionEngine::stackTrace(int frameLimit) const { StackTrace stack; CppStackFrame *f = currentStackFrame; while (f && frameLimit) { QV4::StackFrame frame; frame.source = f->source(); frame.function = f->function(); frame.line = f->lineNumber(); stack.append(frame); if (f->isJSTypesFrame()) { if (static_cast(f)->isTailCalling()) { QV4::StackFrame frame; frame.function = QStringLiteral("[elided tail calls]"); stack.append(frame); } } --frameLimit; f = f->parentFrame(); } return stack; } /* Helper and "C" linkage exported function to format a GDBMI stacktrace for * invocation by a debugger. * Sample GDB invocation: print qt_v4StackTrace((void*)0x7fffffffb290) * Sample CDB invocation: .call Qt5Qmld!qt_v4StackTrace(0x7fffffffb290) ; gh * Note: The helper is there to suppress MSVC warning 4190 about anything * with UDT return types in a "C" linkage function. */ static inline char *v4StackTrace(const ExecutionContext *context) { QString result; QTextStream str(&result); str << "stack=["; if (context && context->engine()) { const QVector stackTrace = context->engine()->stackTrace(20); for (int i = 0; i < stackTrace.size(); ++i) { if (i) str << ','; const QUrl url(stackTrace.at(i).source); const QString fileName = url.isLocalFile() ? url.toLocalFile() : url.toString(); str << "frame={level=\"" << i << "\",func=\"" << stackTrace.at(i).function << "\",file=\"" << fileName << "\",fullname=\"" << fileName << "\",line=\"" << qAbs(stackTrace.at(i).line) << "\",language=\"js\"}"; } } str << ']'; return qstrdup(result.toLocal8Bit().constData()); } extern "C" Q_QML_EXPORT char *qt_v4StackTrace(void *executionContext) { return v4StackTrace(reinterpret_cast(executionContext)); } extern "C" Q_QML_EXPORT char *qt_v4StackTraceForEngine(void *executionEngine) { auto engine = (reinterpret_cast(executionEngine)); return v4StackTrace(engine->currentContext()); } QUrl ExecutionEngine::resolvedUrl(const QString &file) { QUrl src(file); if (!src.isRelative()) return src; QUrl base; CppStackFrame *f = currentStackFrame; while (f) { if (f->v4Function) { base = f->v4Function->finalUrl(); break; } f = f->parentFrame(); } if (base.isEmpty() && globalCode) base = globalCode->finalUrl(); if (base.isEmpty()) return src; return base.resolved(src); } void ExecutionEngine::markObjects(MarkStack *markStack) { for (int i = 0; i < NClasses; ++i) { if (Heap::InternalClass *c = classes[i]) c->mark(markStack); } identifierTable->markObjects(markStack); for (const auto &compilationUnit : std::as_const(m_compilationUnits)) compilationUnit->markObjects(markStack); } ReturnedValue ExecutionEngine::throwError(const Value &value) { // we can get in here with an exception already set, as the runtime // doesn't check after every operation that can throw. // in this case preserve the first exception to give correct error // information if (hasException) return Encode::undefined(); hasException = true; *exceptionValue = value; QV4::Scope scope(this); QV4::Scoped error(scope, value); if (!!error) exceptionStackTrace = *error->d()->stackTrace; else exceptionStackTrace = stackTrace(); if (QV4::Debugging::Debugger *debug = debugger()) debug->aboutToThrow(); return Encode::undefined(); } ReturnedValue ExecutionEngine::catchException(StackTrace *trace) { Q_ASSERT(hasException); if (trace) *trace = exceptionStackTrace; exceptionStackTrace.clear(); hasException = false; ReturnedValue res = exceptionValue->asReturnedValue(); *exceptionValue = Value::emptyValue(); return res; } ReturnedValue ExecutionEngine::throwError(const QString &message) { Scope scope(this); ScopedValue v(scope, newString(message)); v = newErrorObject(v); return throwError(v); } ReturnedValue ExecutionEngine::throwSyntaxError(const QString &message, const QString &fileName, int line, int column) { Scope scope(this); ScopedObject error(scope, newSyntaxErrorObject(message, fileName, line, column)); return throwError(error); } ReturnedValue ExecutionEngine::throwSyntaxError(const QString &message) { Scope scope(this); ScopedObject error(scope, newSyntaxErrorObject(message)); return throwError(error); } ReturnedValue ExecutionEngine::throwTypeError() { Scope scope(this); ScopedObject error(scope, newTypeErrorObject(QStringLiteral("Type error"))); return throwError(error); } ReturnedValue ExecutionEngine::throwTypeError(const QString &message) { Scope scope(this); ScopedObject error(scope, newTypeErrorObject(message)); return throwError(error); } ReturnedValue ExecutionEngine::throwReferenceError(const QString &name) { Scope scope(this); QString msg = name + QLatin1String(" is not defined"); ScopedObject error(scope, newReferenceErrorObject(msg)); return throwError(error); } ReturnedValue ExecutionEngine::throwReferenceError(const Value &value) { Scope scope(this); ScopedString s(scope, value.toString(this)); QString msg = s->toQString() + QLatin1String(" is not defined"); ScopedObject error(scope, newReferenceErrorObject(msg)); return throwError(error); } ReturnedValue ExecutionEngine::throwReferenceError(const QString &message, const QString &fileName, int line, int column) { Scope scope(this); QString msg = message; ScopedObject error(scope, newReferenceErrorObject(msg, fileName, line, column)); return throwError(error); } ReturnedValue ExecutionEngine::throwRangeError(const QString &message) { Scope scope(this); ScopedObject error(scope, newRangeErrorObject(message)); return throwError(error); } ReturnedValue ExecutionEngine::throwRangeError(const Value &value) { Scope scope(this); ScopedString s(scope, value.toString(this)); QString msg = s->toQString() + QLatin1String(" out of range"); ScopedObject error(scope, newRangeErrorObject(msg)); return throwError(error); } ReturnedValue ExecutionEngine::throwURIError(const Value &msg) { Scope scope(this); ScopedObject error(scope, newURIErrorObject(msg)); return throwError(error); } ReturnedValue ExecutionEngine::throwUnimplemented(const QString &message) { Scope scope(this); ScopedValue v(scope, newString(QLatin1String("Unimplemented ") + message)); v = newErrorObject(v); return throwError(v); } QQmlError ExecutionEngine::catchExceptionAsQmlError() { QV4::StackTrace trace; QV4::Scope scope(this); QV4::ScopedValue exception(scope, catchException(&trace)); QQmlError error; if (!trace.isEmpty()) { QV4::StackFrame frame = trace.constFirst(); error.setUrl(QUrl(frame.source)); error.setLine(qAbs(frame.line)); error.setColumn(frame.column); } QV4::Scoped errorObj(scope, exception); error.setDescription(exception->toQStringNoThrow()); return error; } // Variant conversion code typedef QSet V4ObjectSet; 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.metaType(), value.constData()); } static QVariant toVariant(const QV4::Value &value, QMetaType metaType, JSToQVariantConversionBehavior conversionBehavior, V4ObjectSet *visitedObjects) { Q_ASSERT (!value.isEmpty()); if (const QV4::VariantObject *v = value.as()) return v->d()->data(); if (metaType == QMetaType::fromType()) return QVariant(value.toBoolean()); if (metaType == QMetaType::fromType()) return QVariant(value.toNumber()); if (metaType == QMetaType::fromType()) return QVariant(float(value.toNumber())); if (metaType == QMetaType::fromType()) return QVariant::fromValue(QV4::JsonObject::toJsonValue(value)); if (metaType == QMetaType::fromType()) return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(value.asReturnedValue())); if (const QV4::Object *o = value.as()) { QV4::Scope scope(o->engine()); QV4::ScopedObject object(scope, o); if (metaType == QMetaType::fromType() && !value.as() && !value.as()) { return QVariant::fromValue(QV4::JsonObject::toJsonObject(object)); } else if (QV4::QObjectWrapper *wrapper = object->as()) { return QVariant::fromValue(wrapper->object()); } else if (object->as()) { return QVariant(); } else if (QV4::QQmlTypeWrapper *w = object->as()) { return w->toVariant(); } else if (QV4::QQmlValueTypeWrapper *v = object->as()) { return v->toVariant(); } else if (QV4::QmlListWrapper *l = object->as()) { return l->toVariant(); } else if (QV4::Sequence *s = object->as()) { return QV4::SequencePrototype::toVariant(s); } } if (const QV4::ArrayObject *o = value.as()) { QV4::Scope scope(o->engine()); QV4::ScopedArrayObject a(scope, o); if (metaType == QMetaType::fromType>()) { QList list; uint length = a->getLength(); QV4::Scoped qobjectWrapper(scope); for (uint ii = 0; ii < length; ++ii) { qobjectWrapper = a->get(ii); if (!!qobjectWrapper) { list << qobjectWrapper->object(); } else { list << 0; } } return QVariant::fromValue >(list); } else if (metaType == QMetaType::fromType()) { return QVariant::fromValue(QV4::JsonObject::toJsonArray(a)); } QVariant retn = QV4::SequencePrototype::toVariant(value, metaType); if (retn.isValid()) return retn; if (metaType.isValid()) { retn = QVariant(metaType, nullptr); auto retnAsIterable = retn.value(); 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(), 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()) { 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()) return QVariant(); if (value.isNull()) return QVariant::fromValue(nullptr); if (value.isBoolean()) return value.booleanValue(); if (value.isInteger()) return value.integerValue(); if (value.isNumber()) return value.asDouble(); if (String *s = value.stringValue()) { const QString &str = s->toQString(); // QChars are stored as a strings if (metaType == QMetaType::fromType() && str.size() == 1) return str.at(0); return str; } if (const QV4::DateObject *d = value.as()) { // NOTE: since we convert QTime to JS Date, // round trip will change the variant type (to QDateTime)! if (metaType == QMetaType::fromType()) return DateObject::dateTimeToDate(d->toQDateTime()); if (metaType == QMetaType::fromType()) return d->toQDateTime().time(); if (metaType == QMetaType::fromType()) return d->toString(); return d->toQDateTime(); } if (const QV4::UrlObject *d = value.as()) return d->toQUrl(); if (const ArrayBuffer *d = value.as()) return d->asByteArray(); if (const Symbol *symbol = value.as()) { return conversionBehavior == JSToQVariantConversionBehavior::Never ? QVariant::fromValue(QJSValuePrivate::fromReturnedValue(symbol->asReturnedValue())) : symbol->descriptiveString(); } const QV4::Object *object = value.as(); Q_ASSERT(object); QV4::Scope scope(object->engine()); QV4::ScopedObject o(scope, object); #if QT_CONFIG(regularexpression) if (QV4::RegExpObject *re = o->as()) return re->toQRegularExpression(); #endif if (metaType.isValid() && !(metaType.flags() & QMetaType::PointerToQObject)) { const QVariant result = QQmlValueTypeProvider::createValueType(value, metaType); if (result.isValid()) return result; } if (conversionBehavior == JSToQVariantConversionBehavior::Never) return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue())); return objectToVariant(o, visitedObjects, conversionBehavior); } QVariant ExecutionEngine::toVariantLossy(const Value &value) { return ::toVariant(value, QMetaType(), JSToQVariantConversionBehavior::Aggressive, nullptr); } QVariant ExecutionEngine::toVariant( const Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols) { auto behavior = createJSValueForObjectsAndSymbols ? JSToQVariantConversionBehavior::Never : JSToQVariantConversionBehavior::Safish; return ::toVariant(value, typeHint, behavior, nullptr); } static QVariantMap objectToVariantMap(const QV4::Object *o, V4ObjectSet *visitedObjects, JSToQVariantConversionBehavior conversionBehvior) { QVariantMap map; QV4::Scope scope(o->engine()); QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); QV4::ScopedValue name(scope); QV4::ScopedValue val(scope); while (1) { name = it.nextPropertyNameAsString(val); if (name->isNull()) break; 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); V4ObjectSet recursionGuardSet; if (!visitedObjects) { visitedObjects = &recursionGuardSet; } else if (visitedObjects->contains(o->d())) { // Avoid recursion. // For compatibility with QVariant{List,Map} conversion, we return an // empty object (and no error is thrown). if (o->as()) return QVariantList(); return QVariantMap(); } visitedObjects->insert(o->d()); QVariant result; if (o->as()) { QV4::Scope scope(o->engine()); QV4::ScopedArrayObject a(scope, o->asReturnedValue()); QV4::ScopedValue v(scope); QVariantList list; int length = a->getLength(); for (int ii = 0; ii < length; ++ii) { v = a->get(ii); list << ::toVariant(v, QMetaType {}, conversionBehvior, visitedObjects); } result = list; } else if (o->getPrototypeOf() == o->engine()->objectPrototype()->d() || (conversionBehvior == JSToQVariantConversionBehavior::Aggressive && !o->as())) { /* 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; } /*! \internal Transform the given \a metaType and \a ptr into a JavaScript representation. */ QV4::ReturnedValue ExecutionEngine::fromData( QMetaType metaType, const void *ptr, QV4::Heap::Object *container, int property, uint flags) { const auto createSequence = [&](const QMetaSequence metaSequence) { QV4::Scope scope(this); QV4::Scoped 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: 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(ptr)); case QMetaType::Int: return QV4::Encode(*reinterpret_cast(ptr)); case QMetaType::UInt: return QV4::Encode(*reinterpret_cast(ptr)); case QMetaType::Long: return QV4::Encode((double)*reinterpret_cast(ptr)); case QMetaType::ULong: return QV4::Encode((double)*reinterpret_cast(ptr)); case QMetaType::LongLong: return QV4::Encode((double)*reinterpret_cast(ptr)); case QMetaType::ULongLong: return QV4::Encode((double)*reinterpret_cast(ptr)); case QMetaType::Double: return QV4::Encode(*reinterpret_cast(ptr)); case QMetaType::QString: return newString(*reinterpret_cast(ptr))->asReturnedValue(); case QMetaType::QByteArray: return newArrayBuffer(*reinterpret_cast(ptr))->asReturnedValue(); case QMetaType::Float: return QV4::Encode(*reinterpret_cast(ptr)); case QMetaType::Short: return QV4::Encode((int)*reinterpret_cast(ptr)); case QMetaType::UShort: return QV4::Encode((int)*reinterpret_cast(ptr)); case QMetaType::Char: return QV4::Encode((int)*reinterpret_cast(ptr)); case QMetaType::UChar: return QV4::Encode((int)*reinterpret_cast(ptr)); case QMetaType::SChar: return QV4::Encode((int)*reinterpret_cast(ptr)); case QMetaType::QChar: return newString(*reinterpret_cast(ptr))->asReturnedValue(); case QMetaType::Char16: return newString(QChar(*reinterpret_cast(ptr)))->asReturnedValue(); case QMetaType::QDateTime: return QV4::Encode(newDateObject( *reinterpret_cast(ptr), container, property, flags)); case QMetaType::QDate: return QV4::Encode(newDateObject( *reinterpret_cast(ptr), container, property, flags)); case QMetaType::QTime: return QV4::Encode(newDateObject( *reinterpret_cast(ptr), container, property, flags)); #if QT_CONFIG(regularexpression) case QMetaType::QRegularExpression: return QV4::Encode(newRegExpObject(*reinterpret_cast(ptr))); #endif case QMetaType::QObjectStar: return QV4::QObjectWrapper::wrap(this, *reinterpret_cast(ptr)); case QMetaType::QStringList: return createSequence(QMetaSequence::fromContainer()); case QMetaType::QVariantList: return createSequence(QMetaSequence::fromContainer()); case QMetaType::QVariantMap: return variantMapToJS(this, *reinterpret_cast(ptr)); case QMetaType::QJsonValue: return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast(ptr)); case QMetaType::QJsonObject: return QV4::JsonObject::fromJsonObject(this, *reinterpret_cast(ptr)); case QMetaType::QJsonArray: return QV4::JsonObject::fromJsonArray(this, *reinterpret_cast(ptr)); case QMetaType::QPixmap: case QMetaType::QImage: // Scarce value types return QV4::Encode(newVariantObject(metaType, ptr)); default: break; } } if (metaType.flags() & QMetaType::IsEnumeration) return fromData(metaType.underlyingType(), ptr, container, property, flags); QV4::Scope scope(this); if (metaType == QMetaType::fromType()) { typedef QQmlListReferencePrivate QDLRP; QDLRP *p = QDLRP::get((QQmlListReference*)const_cast(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 is slightly nasty, but it's the // same QQmlListReference does. const auto *p = static_cast *>(ptr); if (p->object) return QV4::QmlListWrapper::create(scope.engine, *p, metaType); else return QV4::Encode::null(); } else if (metaType == QMetaType::fromType()) { return QJSValuePrivate::convertToReturnedValue( this, *reinterpret_cast(ptr)); } else if (metaType == QMetaType::fromType >()) { // XXX Can this be made more by using Array as a prototype and implementing // directly against QList? const QList &list = *(const QList*)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(ptr)); else return QV4::QObjectWrapper::wrap(this, *reinterpret_cast(ptr)); } else if (metaType == QMetaType::fromType()) { const QJSPrimitiveValue *primitive = static_cast(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()); } } 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); } } const QQmlType listType = QQmlMetaType::qmlListType(metaType); if (listType.isSequentialContainer()) return createSequence(listType.listMetaSequence()); QSequentialIterable iterable; if (QMetaType::convert(metaType, ptr, QMetaType::fromType(), &iterable)) { // 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); // 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)); } QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) { 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); } QVariantMap ExecutionEngine::variantMapFromJS(const Object *o) { Q_ASSERT(o); V4ObjectSet visitedObjects; visitedObjects.insert(o->d()); return objectToVariantMap(o, &visitedObjects, JSToQVariantConversionBehavior::Safish); } // Converts a QVariantMap to JS. // The result is a new Object object with property names being // the keys of the QVariantMap, and values being the values of // the QVariantMap converted to JS, recursively. static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVariantMap &vmap) { QV4::Scope scope(v4); QV4::ScopedObject o(scope, v4->newObject()); QV4::ScopedString s(scope); QV4::ScopedPropertyKey key(scope); QV4::ScopedValue v(scope); for (QVariantMap::const_iterator it = vmap.constBegin(), cend = vmap.constEnd(); it != cend; ++it) { s = v4->newIdentifier(it.key()); key = s->propertyKey(); v = variantToJS(v4, it.value()); if (key->isArrayIndex()) o->arraySet(key->asArrayIndex(), v); else o->insertMember(s, v); } return o.asReturnedValue(); } // 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(QMetaType type, const void *data) { Q_ASSERT(data != nullptr); if (type == QMetaType::fromType()) { // unwrap it: this is tested in QJSEngine, and makes the most sense for // end-user code too. return fromVariant(*reinterpret_cast(data)); } else if (type == QMetaType::fromType()) { // Create a proper URL object here, rather than a variant. return newUrlObject(*reinterpret_cast(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::max())) { throwRangeError(QStringLiteral("Invalid array length.")); return 0; } if (len64 > qint64(this->jsStackLimit - this->jsStackTop)) { throwRangeError(QStringLiteral("Array too large for apply().")); return 0; } return len64; } ReturnedValue ExecutionEngine::global() { return globalObject->asReturnedValue(); } QQmlRefPointer 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( 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())); return nullptr; } const QDateTime timeStamp = QFileInfo(f).lastModified(); const QString sourceCode = QString::fromUtf8(f.readAll()); f.close(); return compileModule(url, sourceCode, timeStamp); } QQmlRefPointer ExecutionEngine::compileModule( const QUrl &url, const QString &sourceCode, const QDateTime &sourceTimeStamp) { QList 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); return nullptr; } else { qWarning() << url << ':' << m.loc.startLine << ':' << m.loc.startColumn << ": warning: " << m.message; } } return insertCompilationUnit(std::move(unit)); } QQmlRefPointer 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 ExecutionEngine::executableCompilationUnit( QQmlRefPointer &&unit) { const QUrl url = unit->finalUrl(); auto [begin, end] = std::as_const(m_compilationUnits).equal_range(url); for (auto it = begin; it != end; ++it) { if ((*it)->baseCompilationUnit() == unit) return *it; } auto executableUnit = m_compilationUnits.insert( url, ExecutableCompilationUnit::create(std::move(unit), this)); // runtime data should not be initialized yet, so we don't need to mark the CU Q_ASSERT(!(*executableUnit)->runtimeStrings); return *executableUnit; } QQmlRefPointer ExecutionEngine::insertCompilationUnit(QQmlRefPointer &&unit) { QUrl url = unit->finalUrl(); auto executableUnit = ExecutableCompilationUnit::create(std::move(unit), this); /* Compilation Units stored in the engine are part of the gc roots, so we don't trigger any write-barrier when they are added. Use markCustom to make sure they are still marked when we insert them */ QV4::WriteBarrier::markCustom(this, [&executableUnit](QV4::MarkStack *ms) { executableUnit->markObjects(ms); }); return *m_compilationUnits.insert(std::move(url), std::move(executableUnit)); } void ExecutionEngine::trimCompilationUnits() { for (auto it = m_compilationUnits.begin(); it != m_compilationUnits.end();) { if ((*it)->count() == 1) it = m_compilationUnits.erase(it); else ++it; } } ExecutionEngine::Module ExecutionEngine::moduleForUrl( const QUrl &url, const ExecutableCompilationUnit *referrer) const { const auto nativeModule = nativeModules.find(url); if (nativeModule != nativeModules.end()) return Module { nullptr, *nativeModule }; const QUrl resolved = referrer ? referrer->finalUrl().resolved(QQmlTypeLoader::normalize(url)) : QQmlTypeLoader::normalize(url); auto existingModule = m_compilationUnits.find(resolved); if (existingModule == m_compilationUnits.end()) return Module { nullptr, nullptr }; return Module { *existingModule, nullptr }; } ExecutionEngine::Module ExecutionEngine::loadModule(const QUrl &url, const ExecutableCompilationUnit *referrer) { const auto nativeModule = nativeModules.constFind(url); if (nativeModule != nativeModules.cend()) return Module { nullptr, *nativeModule }; 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; 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; } static ExecutionEngine::DiskCacheOptions transFormDiskCache(const char *v) { using DiskCache = ExecutionEngine::DiskCache; if (v == nullptr) return DiskCache::Enabled; ExecutionEngine::DiskCacheOptions result = DiskCache::Disabled; const QList 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; } ExecutionEngine::DiskCacheOptions ExecutionEngine::diskCacheOptions() const { if (forceDiskCache()) return DiskCache::Enabled; if (disableDiskCache() || debugger()) return DiskCache::Disabled; static const DiskCacheOptions options = qmlGetConfigOption< DiskCacheOptions, transFormDiskCache>("QML_DISK_CACHE"); return options; } void ExecutionEngine::callInContext(QV4::Function *function, QObject *self, QV4::ExecutionContext *context, int argc, void **args, QMetaType *types) { if (!args) { Q_ASSERT(argc == 0); void *dummyArgs[] = { nullptr }; QMetaType dummyTypes[] = { QMetaType::fromType() }; 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); } 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 &ExecutionEngine::illegalNames() const { return m_illegalNames; } void ExecutionEngine::setQmlEngine(QQmlEngine *engine) { // Second stage of initialization. We're updating some more prototypes here. isInitialized = false; m_qmlEngine = engine; initQmlGlobalObject(); isInitialized = true; } static void freeze_recursive(QV4::ExecutionEngine *v4, QV4::Object *object) { if (object->as() || 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); } } void ExecutionEngine::freezeObject(const QV4::Value &value) { QV4::Scope scope(this); QV4::ScopedObject o(scope, value); freeze_recursive(this, o); } void ExecutionEngine::lockObject(const QV4::Value &value) { QV4::Scope scope(this); ScopedObject object(scope, value); if (!object) return; std::vector 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() || object->internalClass()->isLocked()) continue; Scoped 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 nameMap = locked->d()->nameMap; for (uint i = 0, end = locked->d()->size; i < end; ++i) { const PropertyKey key = nameMap.at(i); if (!key.isStringOrSymbol()) continue; if ((member = *object->propertyData(i))) { stack.push_back(member->d()); if (std::find(writableBegin, writableEnd, key) == writableEnd) { PropertyAttributes attributes = locked->d()->find(key).attributes; attributes.setConfigurable(false); attributes.setWritable(false); locked = locked->changeMember(key, attributes); } } } object->setInternalClass(locked->d()); } } void ExecutionEngine::startTimer(const QString &timerName) { if (!m_time.isValid()) 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 bool convertToIterable(QMetaType metaType, void *data, Source *sequence) { QSequentialIterable iterable; if (!QMetaType::view(metaType, data, QMetaType::fromType(), &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, QMetaType metaType, void *data) { // check if it's one of the types we know switch (metaType.id()) { case QMetaType::Bool: *reinterpret_cast(data) = value.toBoolean(); return true; case QMetaType::Int: *reinterpret_cast(data) = value.toInt32(); return true; case QMetaType::UInt: *reinterpret_cast(data) = value.toUInt32(); return true; case QMetaType::Long: *reinterpret_cast(data) = long(value.toInteger()); return true; case QMetaType::ULong: *reinterpret_cast(data) = ulong(value.toInteger()); return true; case QMetaType::LongLong: *reinterpret_cast(data) = qlonglong(value.toInteger()); return true; case QMetaType::ULongLong: *reinterpret_cast(data) = qulonglong(value.toInteger()); return true; case QMetaType::Double: *reinterpret_cast(data) = value.toNumber(); return true; case QMetaType::QString: if (value.isUndefined()) *reinterpret_cast(data) = QStringLiteral("undefined"); else if (value.isNull()) *reinterpret_cast(data) = QStringLiteral("null"); else *reinterpret_cast(data) = value.toQString(); return true; case QMetaType::QByteArray: if (const ArrayBuffer *ab = value.as()) { *reinterpret_cast(data) = ab->asByteArray(); } else if (const String *string = value.as()) { *reinterpret_cast(data) = string->toQString().toUtf8(); } else if (const ArrayObject *ao = value.as()) { // 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(), &value); result.push_back(value); } *reinterpret_cast(data) = std::move(result); } else { *reinterpret_cast(data) = QByteArray(); } return true; case QMetaType::Float: *reinterpret_cast(data) = value.toNumber(); return true; case QMetaType::Short: *reinterpret_cast(data) = short(value.toInt32()); return true; case QMetaType::UShort: *reinterpret_cast(data) = value.toUInt16(); return true; case QMetaType::Char: *reinterpret_cast(data) = char(value.toInt32()); return true; case QMetaType::UChar: *reinterpret_cast(data) = (unsigned char)(value.toInt32()); return true; case QMetaType::SChar: *reinterpret_cast(data) = (signed char)(value.toInt32()); return true; case QMetaType::QChar: if (String *s = value.stringValue()) { QString str = s->toQString(); *reinterpret_cast(data) = str.isEmpty() ? QChar() : str.at(0); } else { *reinterpret_cast(data) = QChar(ushort(value.toUInt16())); } return true; case QMetaType::QDateTime: if (const QV4::DateObject *d = value.as()) { *reinterpret_cast(data) = d->toQDateTime(); return true; } break; case QMetaType::QDate: if (const QV4::DateObject *d = value.as()) { *reinterpret_cast(data) = DateObject::dateTimeToDate(d->toQDateTime()); return true; } break; case QMetaType::QTime: if (const QV4::DateObject *d = value.as()) { *reinterpret_cast(data) = d->toQDateTime().time(); return true; } break; case QMetaType::QUrl: if (String *s = value.stringValue()) { *reinterpret_cast(data) = QUrl(s->toQString()); return true; } else if (const QV4::UrlObject *d = value.as()) { *reinterpret_cast(data) = d->toQUrl(); return true; } else if (const QV4::VariantObject *d = value.as()) { const QVariant *variant = &d->d()->data(); if (variant->metaType() == QMetaType::fromType()) { *reinterpret_cast(data) = *reinterpret_cast(variant->constData()); return true; } } break; #if QT_CONFIG(regularexpression) case QMetaType::QRegularExpression: if (const QV4::RegExpObject *r = value.as()) { *reinterpret_cast(data) = r->toQRegularExpression(); return true; } break; #endif case QMetaType::QObjectStar: { if (value.isNull()) { *reinterpret_cast(data) = nullptr; return true; } if (value.as()) { *reinterpret_cast(data) = qtObjectFromJS(value); return true; } break; } case QMetaType::QStringList: { const QV4::ArrayObject *a = value.as(); if (a) { *reinterpret_cast(data) = a->toQStringList(); return true; } break; } case QMetaType::QVariantList: { const QV4::ArrayObject *a = value.as(); if (a) { *reinterpret_cast(data) = ExecutionEngine::toVariant( *a, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false) .toList(); return true; } break; } case QMetaType::QVariantMap: { const QV4::Object *o = value.as(); if (o) { *reinterpret_cast(data) = o->engine()->variantMapFromJS(o); return true; } break; } case QMetaType::QVariant: if (value.as()) { *reinterpret_cast(data) = ExecutionEngine::toVariant( value, /*typeHint*/QMetaType{}, /*createJSValueForObjectsAndSymbols*/false); } else if (value.isNull()) { *reinterpret_cast(data) = QVariant::fromValue(nullptr); } else if (value.isUndefined()) { *reinterpret_cast(data) = QVariant(); } else if (value.isBoolean()) { *reinterpret_cast(data) = QVariant(value.booleanValue()); } else if (value.isInteger()) { *reinterpret_cast(data) = QVariant(value.integerValue()); } else if (value.isDouble()) { *reinterpret_cast(data) = QVariant(value.doubleValue()); } return true; case QMetaType::QJsonValue: *reinterpret_cast(data) = QV4::JsonObject::toJsonValue(value); return true; case QMetaType::QJsonObject: { *reinterpret_cast(data) = QV4::JsonObject::toJsonObject(value.as()); return true; } case QMetaType::QJsonArray: { const QV4::ArrayObject *a = value.as(); if (a) { *reinterpret_cast(data) = JsonObject::toJsonArray(a); return true; } break; } default: break; } if (metaType.flags() & QMetaType::IsEnumeration) { *reinterpret_cast(data) = value.toInt32(); return true; } if (const QV4::QmlListWrapper *wrapper = value.as()) { if (metaType == QMetaType::fromType()) { *reinterpret_cast(data) = wrapper->toListReference(); return true; } const auto wrapperPrivate = wrapper->d(); if (wrapperPrivate->propertyType() == metaType) { *reinterpret_cast *>(data) = *wrapperPrivate->property(); return true; } } if (const QQmlValueTypeWrapper *vtw = value.as()) { 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); } } // Try to use magic; for compatibility with qjsvalue_cast. if (convertToNativeQObject(value, metaType, reinterpret_cast(data))) return true; const bool isPointer = (metaType.flags() & QMetaType::IsPointer); const QV4::VariantObject *variantObject = value.as(); 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; } 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(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()) { const QVariant &v = vo->d()->data(); canCast = (metaType == v.metaType()); } else if (proto->as()) { 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(data) = *reinterpret_cast(var.data()); } else { *reinterpret_cast(data) = var.data(); } return true; } proto = proto->getPrototypeOf(); } } } else if (QQmlValueTypeProvider::populateValueType( metaType, data, var.metaType(), var.data())) { return true; } } else if (value.isNull() && isPointer) { *reinterpret_cast(data) = nullptr; return true; } else if (metaType == QMetaType::fromType()) { QJSValuePrivate::setValue(reinterpret_cast(data), value.asReturnedValue()); return true; } else if (metaType == QMetaType::fromType()) { *reinterpret_cast(data) = createPrimitive(&value); return true; } else if (!isPointer) { if (QQmlValueTypeProvider::populateValueType(metaType, data, value)) return true; } if (const QV4::Sequence *sequence = value.as()) { 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()) { if (convertToIterable(metaType, data, array)) return true; } return false; } static bool convertToNativeQObject(const QV4::Value &value, QMetaType targetType, void **result) { if (!(targetType.flags() & QMetaType::IsPointer)) return false; 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; } } return false; } static QObject *qtObjectFromJS(const QV4::Value &value) { if (!value.isObject()) return nullptr; QV4::Scope scope(value.as()->engine()); QV4::Scoped v(scope, value); if (v) { QVariant variant = v->d()->data(); int type = variant.userType(); if (type == QMetaType::QObjectStar) return *reinterpret_cast(variant.constData()); } QV4::Scoped wrapper(scope, value); if (wrapper) return wrapper->object(); QV4::Scoped typeWrapper(scope, value); if (typeWrapper) return typeWrapper->object(); return nullptr; } 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