/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtQml module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "qv4function_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "qv4debugging_p.h" #include "qv4profiling_p.h" #include "qv4executableallocator_p.h" #include "qv4sequenceobject_p.h" #include "qv4qobjectwrapper_p.h" #include "qv4memberdata_p.h" #include "qv4arraybuffer_p.h" #include "qv4dataview_p.h" #include "qv4typedarray_p.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef V4_ENABLE_JIT #include "qv4isel_masm_p.h" #endif // V4_ENABLE_JIT #include "qv4isel_moth_p.h" #if USE(PTHREADS) # include # include #if HAVE(PTHREAD_NP_H) # include #endif #endif #ifdef V4_USE_VALGRIND #include #endif QT_BEGIN_NAMESPACE using namespace QV4; static QBasicAtomicInt engineSerial = Q_BASIC_ATOMIC_INITIALIZER(1); static ReturnedValue throwTypeError(CallContext *ctx) { return ctx->engine()->throwTypeError(); } const int MinimumStackSize = 256; // in kbytes QT_WARNING_PUSH QT_WARNING_DISABLE_MSVC(4172) // MSVC 2015: warning C4172: returning address of local variable or temporary: dummy quintptr getStackLimit() { quintptr stackLimit; #if USE(PTHREADS) && !OS(QNX) # if OS(DARWIN) pthread_t thread_self = pthread_self(); void *stackTop = pthread_get_stackaddr_np(thread_self); stackLimit = reinterpret_cast(stackTop); quintptr size = 0; if (pthread_main_np()) { rlimit limit; getrlimit(RLIMIT_STACK, &limit); size = limit.rlim_cur; } else size = pthread_get_stacksize_np(thread_self); stackLimit -= size; # elif defined(__hppa) // On some architectures the stack grows upwards. All of these are rather exotic, so simply assume // everything is fine there. // Known examples: // -HP PA-RISC stackLimit = 0; # else pthread_attr_t attr; #if HAVE(PTHREAD_NP_H) && OS(FREEBSD) // on FreeBSD pthread_attr_init() must be called otherwise getting the attrs crashes if (pthread_attr_init(&attr) == 0 && pthread_attr_get_np(pthread_self(), &attr) == 0) { #else if (pthread_getattr_np(pthread_self(), &attr) == 0) { #endif void *stackBottom = Q_NULLPTR; size_t stackSize = 0; pthread_attr_getstack(&attr, &stackBottom, &stackSize); pthread_attr_destroy(&attr); # if defined(Q_OS_ANDROID) // Bionic pretends that the main thread has a tiny stack; work around it if (gettid() == getpid()) { rlimit limit; getrlimit(RLIMIT_STACK, &limit); stackBottom = reinterpret_cast(reinterpret_cast(stackBottom) + stackSize - limit.rlim_cur); } # endif stackLimit = reinterpret_cast(stackBottom); } else { int dummy; // this is inexact, as part of the stack is used when being called here, // but let's simply default to 1MB from where the stack is right now stackLimit = reinterpret_cast(&dummy) - 1024*1024; } # endif // This is wrong. StackLimit is the currently committed stack size, not the real end. // only way to get that limit is apparently by using VirtualQuery (Yuck) //#elif OS(WINDOWS) // PNT_TIB tib = (PNT_TIB)NtCurrentTeb(); // stackLimit = static_cast(tib->StackLimit); #else int dummy; // this is inexact, as part of the stack is used when being called here, // but let's simply default to 1MB from where the stack is right now // (Note: triggers warning C4172 as of MSVC 2015, returning address of local variable) stackLimit = reinterpret_cast(&dummy) - 1024*1024; #endif // 256k slack return stackLimit + MinimumStackSize*1024; } QT_WARNING_POP QJSEngine *ExecutionEngine::jsEngine() const { return v8Engine->publicEngine(); } QQmlEngine *ExecutionEngine::qmlEngine() const { return v8Engine->engine(); } ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) : current(0) , hasException(false) , memoryManager(new QV4::MemoryManager(this)) , executableAllocator(new QV4::ExecutableAllocator) , regExpAllocator(new QV4::ExecutableAllocator) , bumperPointerAllocator(new WTF::BumpPointerAllocator) , jsStack(new WTF::PageAllocation) , debugger(0) , profiler(0) , globalCode(0) , v8Engine(0) , argumentsAccessors(0) , nArgumentsAccessors(0) , m_engineId(engineSerial.fetchAndAddOrdered(1)) , regExpCache(0) , m_multiplyWrappedQObjects(0) { MemoryManager::GCBlocker gcBlocker(memoryManager); if (!factory) { #ifdef V4_ENABLE_JIT static const bool forceMoth = !qgetenv("QV4_FORCE_INTERPRETER").isEmpty(); if (forceMoth) factory = new Moth::ISelFactory; else factory = new JIT::ISelFactory; #else // !V4_ENABLE_JIT factory = new Moth::ISelFactory; #endif // V4_ENABLE_JIT } iselFactory.reset(factory); // reserve space for the JS stack // we allow it to grow to 2 times JSStackLimit, as we can overshoot due to garbage collection // and ScopedValues allocated outside of JIT'ed methods. *jsStack = WTF::PageAllocation::allocate(2 * JSStackLimit, WTF::OSAllocator::JSVMStackPages, /* writable */ true, /* executable */ false, /* includesGuardPages */ true); jsStackBase = (Value *)jsStack->base(); jsStackTop = jsStackBase; exceptionValue = jsAlloca(1); globalObject = static_cast(jsAlloca(1)); jsObjects = jsAlloca(NJSObjects); typedArrayPrototype = static_cast(jsAlloca(NTypedArrayTypes)); typedArrayCtors = static_cast(jsAlloca(NTypedArrayTypes)); jsStrings = jsAlloca(NJSStrings); #ifdef V4_USE_VALGRIND VALGRIND_MAKE_MEM_UNDEFINED(jsStackBase, 2*JSStackLimit); #endif // set up stack limits jsStackLimit = jsStackBase + JSStackLimit/sizeof(Value); cStackLimit = getStackLimit(); if (!recheckCStackLimits()) qFatal("Fatal: Not enough stack space available for QML. Please increase the process stack size to more than %d KBytes.", MinimumStackSize); identifierTable = new IdentifierTable(this); classPool = new InternalClassPool; emptyClass = new (classPool) InternalClass(this); 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_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_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")); jsObjects[ObjectProto] = memoryManager->alloc(emptyClass, (QV4::Object *)0); arrayClass = emptyClass->addMember(id_length(), Attr_NotConfigurable|Attr_NotEnumerable); jsObjects[ArrayProto] = memoryManager->alloc(arrayClass, objectPrototype()); InternalClass *argsClass = emptyClass->addMember(id_length(), Attr_NotEnumerable); argumentsObjectClass = argsClass->addMember(id_callee(), Attr_Data|Attr_NotEnumerable); strictArgumentsObjectClass = argsClass->addMember(id_callee(), Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable); strictArgumentsObjectClass = strictArgumentsObjectClass->addMember(id_caller(), Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable); *static_cast(globalObject) = newObject(); Q_ASSERT(globalObject->d()->vtable()); initRootContext(); jsObjects[StringProto] = memoryManager->alloc(emptyClass, objectPrototype()); jsObjects[NumberProto] = memoryManager->alloc(emptyClass, objectPrototype()); jsObjects[BooleanProto] = memoryManager->alloc(emptyClass, objectPrototype()); jsObjects[DateProto] = memoryManager->alloc(emptyClass, objectPrototype()); uint index; InternalClass *functionProtoClass = emptyClass->addMember(id_prototype(), Attr_NotEnumerable, &index); Q_ASSERT(index == Heap::FunctionObject::Index_Prototype); jsObjects[FunctionProto] = memoryManager->alloc(functionProtoClass, objectPrototype()); functionClass = emptyClass->addMember(id_prototype(), Attr_NotEnumerable|Attr_NotConfigurable, &index); Q_ASSERT(index == Heap::FunctionObject::Index_Prototype); simpleScriptFunctionClass = functionClass->addMember(id_name(), Attr_ReadOnly, &index); Q_ASSERT(index == Heap::SimpleScriptFunction::Index_Name); simpleScriptFunctionClass = simpleScriptFunctionClass->addMember(id_length(), Attr_ReadOnly, &index); Q_ASSERT(index == Heap::SimpleScriptFunction::Index_Length); protoClass = emptyClass->addMember(id_constructor(), Attr_NotEnumerable, &index); Q_ASSERT(index == Heap::FunctionObject::Index_ProtoConstructor); jsObjects[RegExpProto] = memoryManager->alloc(this); regExpExecArrayClass = arrayClass->addMember(id_index(), Attr_Data, &index); Q_ASSERT(index == RegExpObject::Index_ArrayIndex); regExpExecArrayClass = regExpExecArrayClass->addMember(id_input(), Attr_Data, &index); Q_ASSERT(index == RegExpObject::Index_ArrayInput); jsObjects[ErrorProto] = memoryManager->alloc(emptyClass, objectPrototype()); jsObjects[EvalErrorProto] = memoryManager->alloc(emptyClass, errorPrototype()); jsObjects[RangeErrorProto] = memoryManager->alloc(emptyClass, errorPrototype()); jsObjects[ReferenceErrorProto] = memoryManager->alloc(emptyClass, errorPrototype()); jsObjects[SyntaxErrorProto] = memoryManager->alloc(emptyClass, errorPrototype()); jsObjects[TypeErrorProto] = memoryManager->alloc(emptyClass, errorPrototype()); jsObjects[URIErrorProto] = memoryManager->alloc(emptyClass, errorPrototype()); jsObjects[VariantProto] = memoryManager->alloc(emptyClass, objectPrototype()); Q_ASSERT(variantPrototype()->prototype() == objectPrototype()->d()); Scope scope(this); jsObjects[SequenceProto] = ScopedValue(scope, memoryManager->alloc(arrayClass, arrayPrototype())); ScopedContext global(scope, rootContext()); jsObjects[Object_Ctor] = memoryManager->alloc(global); jsObjects[String_Ctor] = memoryManager->alloc(global); jsObjects[Number_Ctor] = memoryManager->alloc(global); jsObjects[Boolean_Ctor] = memoryManager->alloc(global); jsObjects[Array_Ctor] = memoryManager->alloc(global); jsObjects[Function_Ctor] = memoryManager->alloc(global); jsObjects[Date_Ctor] = memoryManager->alloc(global); jsObjects[RegExp_Ctor] = memoryManager->alloc(global); jsObjects[Error_Ctor] = memoryManager->alloc(global); jsObjects[EvalError_Ctor] = memoryManager->alloc(global); jsObjects[RangeError_Ctor] = memoryManager->alloc(global); jsObjects[ReferenceError_Ctor] = memoryManager->alloc(global); jsObjects[SyntaxError_Ctor] = memoryManager->alloc(global); jsObjects[TypeError_Ctor] = memoryManager->alloc(global); jsObjects[URIError_Ctor] = memoryManager->alloc(global); static_cast(objectPrototype())->init(this, objectCtor()); static_cast(stringPrototype())->init(this, stringCtor()); static_cast(numberPrototype())->init(this, numberCtor()); static_cast(booleanPrototype())->init(this, booleanCtor()); static_cast(arrayPrototype())->init(this, arrayCtor()); static_cast(datePrototype())->init(this, dateCtor()); static_cast(functionPrototype())->init(this, functionCtor()); 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(variantPrototype())->init(); sequencePrototype()->cast()->init(); // typed arrays jsObjects[ArrayBuffer_Ctor] = memoryManager->alloc(global); jsObjects[ArrayBufferProto] = memoryManager->alloc(emptyClass, objectPrototype()); static_cast(arrayBufferPrototype())->init(this, arrayBufferCtor()); jsObjects[DataView_Ctor] = memoryManager->alloc(global); jsObjects[DataViewProto] = memoryManager->alloc(emptyClass, objectPrototype()); static_cast(dataViewPrototype())->init(this, dataViewCtor()); jsObjects[ValueTypeProto] = (Heap::Base *) 0; for (int i = 0; i < Heap::TypedArray::NTypes; ++i) { static_cast(typedArrayCtors[i]) = memoryManager->alloc(global, Heap::TypedArray::Type(i)); static_cast(typedArrayPrototype[i]) = memoryManager->alloc(this, Heap::TypedArray::Type(i)); typedArrayPrototype[i].as()->init(this, static_cast(typedArrayCtors[i].as())); } // // set up the global object // rootContext()->d()->global = globalObject->d(); rootContext()->d()->callData->thisObject = globalObject; Q_ASSERT(globalObject->d()->vtable()); globalObject->defineDefaultProperty(QStringLiteral("Object"), *objectCtor()); globalObject->defineDefaultProperty(QStringLiteral("String"), *stringCtor()); globalObject->defineDefaultProperty(QStringLiteral("Number"), *numberCtor()); 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("ArrayBuffer"), *arrayBufferCtor()); globalObject->defineDefaultProperty(QStringLiteral("DataView"), *dataViewCtor()); ScopedString str(scope); for (int i = 0; i < Heap::TypedArray::NTypes; ++i) globalObject->defineDefaultProperty((str = typedArrayCtors[i].as()->name())->toQString(), typedArrayCtors[i]); ScopedObject o(scope); globalObject->defineDefaultProperty(QStringLiteral("Math"), (o = memoryManager->alloc(this))); globalObject->defineDefaultProperty(QStringLiteral("JSON"), (o = memoryManager->alloc(this))); globalObject->defineReadonlyProperty(QStringLiteral("undefined"), Primitive::undefinedValue()); globalObject->defineReadonlyProperty(QStringLiteral("NaN"), Primitive::fromDouble(std::numeric_limits::quiet_NaN())); globalObject->defineReadonlyProperty(QStringLiteral("Infinity"), Primitive::fromDouble(Q_INFINITY)); jsObjects[Eval_Function] = memoryManager->alloc(global); globalObject->defineDefaultProperty(QStringLiteral("eval"), *evalFunction()); globalObject->defineDefaultProperty(QStringLiteral("parseInt"), GlobalFunctions::method_parseInt, 2); globalObject->defineDefaultProperty(QStringLiteral("parseFloat"), GlobalFunctions::method_parseFloat, 1); 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); ScopedString name(scope, newString(QStringLiteral("thrower"))); jsObjects[ThrowerObject] = BuiltinFunction::create(global, name, ::throwTypeError); } ExecutionEngine::~ExecutionEngine() { delete debugger; debugger = 0; delete profiler; profiler = 0; delete m_multiplyWrappedQObjects; m_multiplyWrappedQObjects = 0; delete identifierTable; delete memoryManager; QSet remainingUnits; qSwap(compilationUnits, remainingUnits); foreach (QV4::CompiledData::CompilationUnit *unit, remainingUnits) unit->unlink(); emptyClass->destroy(); delete classPool; delete bumperPointerAllocator; delete regExpCache; delete regExpAllocator; delete executableAllocator; jsStack->deallocate(); delete jsStack; delete [] argumentsAccessors; } void ExecutionEngine::enableDebugger() { Q_ASSERT(!debugger); debugger = new Debugging::Debugger(this); iselFactory.reset(new Moth::ISelFactory); } void ExecutionEngine::enableProfiler() { Q_ASSERT(!profiler); profiler = new QV4::Profiling::Profiler(this); } void ExecutionEngine::initRootContext() { Scope scope(this); Scoped r(scope, memoryManager->allocManaged(sizeof(GlobalContext::Data) + sizeof(CallData))); new (r->d()) GlobalContext::Data(this); r->d()->callData = reinterpret_cast(r->d() + 1); r->d()->callData->tag = QV4::Value::_Integer_Type; r->d()->callData->argc = 0; r->d()->callData->thisObject = globalObject; r->d()->callData->args[0] = Encode::undefined(); jsObjects[RootContect] = r; } InternalClass *ExecutionEngine::newClass(const InternalClass &other) { return new (classPool) InternalClass(other); } Heap::ExecutionContext *ExecutionEngine::pushGlobalContext() { Scope scope(this); Scoped g(scope, memoryManager->alloc(this)); g->d()->callData = rootContext()->d()->callData; Q_ASSERT(currentContext() == g->d()); return g->d(); } Heap::Object *ExecutionEngine::newObject() { Scope scope(this); ScopedObject object(scope, memoryManager->alloc(this)); return object->d(); } Heap::Object *ExecutionEngine::newObject(InternalClass *internalClass, QV4::Object *prototype) { Scope scope(this); ScopedObject object(scope, memoryManager->alloc(internalClass, prototype)); return object->d(); } Heap::String *ExecutionEngine::newString(const QString &s) { Scope scope(this); return ScopedString(scope, memoryManager->allocWithStringData(s.length() * sizeof(QChar), s))->d(); } Heap::String *ExecutionEngine::newIdentifier(const QString &text) { return identifierTable->insertString(text); } Heap::Object *ExecutionEngine::newStringObject(const String *string) { Scope scope(this); Scoped object(scope, memoryManager->alloc(this, string)); return object->d(); } Heap::Object *ExecutionEngine::newNumberObject(double value) { Scope scope(this); Scoped object(scope, memoryManager->alloc(this, value)); return object->d(); } Heap::Object *ExecutionEngine::newBooleanObject(bool b) { Scope scope(this); ScopedObject object(scope, memoryManager->alloc(this, b)); return object->d(); } Heap::ArrayObject *ExecutionEngine::newArrayObject(int count) { Scope scope(this); ScopedArrayObject object(scope, memoryManager->alloc(this)); if (count) { if (count < 0x1000) object->arrayReserve(count); object->setArrayLengthUnchecked(count); } return object->d(); } Heap::ArrayObject *ExecutionEngine::newArrayObject(const QStringList &list) { Scope scope(this); ScopedArrayObject object(scope, memoryManager->alloc(this, list)); return object->d(); } Heap::ArrayObject *ExecutionEngine::newArrayObject(InternalClass *ic, Object *prototype) { Scope scope(this); ScopedArrayObject object(scope, memoryManager->alloc(ic, prototype)); return object->d(); } Heap::ArrayBuffer *ExecutionEngine::newArrayBuffer(const QByteArray &array) { Scope scope(this); Scoped object(scope, memoryManager->alloc(this, array)); return object->d(); } Heap::DateObject *ExecutionEngine::newDateObject(const Value &value) { Scope scope(this); Scoped object(scope, memoryManager->alloc(this, value)); return object->d(); } Heap::DateObject *ExecutionEngine::newDateObject(const QDateTime &dt) { Scope scope(this); Scoped object(scope, memoryManager->alloc(this, dt)); return object->d(); } Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QString &pattern, int flags) { bool global = (flags & IR::RegExp::RegExp_Global); bool ignoreCase = false; bool multiline = false; if (flags & IR::RegExp::RegExp_IgnoreCase) ignoreCase = true; if (flags & IR::RegExp::RegExp_Multiline) multiline = true; Scope scope(this); Scoped re(scope, RegExp::create(this, pattern, ignoreCase, multiline)); return newRegExpObject(re, global); } Heap::RegExpObject *ExecutionEngine::newRegExpObject(RegExp *re, bool global) { Scope scope(this); Scoped object(scope, memoryManager->alloc(this, re, global)); return object->d(); } Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegExp &re) { Scope scope(this); Scoped object(scope, memoryManager->alloc(this, re)); return object->d(); } Heap::Object *ExecutionEngine::newErrorObject(const Value &value) { Scope scope(this); ScopedObject object(scope, memoryManager->alloc(emptyClass, errorPrototype(), value)); return object->d(); } Heap::Object *ExecutionEngine::newSyntaxErrorObject(const QString &message) { Scope scope(this); ScopedString s(scope, newString(message)); ScopedObject error(scope, memoryManager->alloc(this, s)); return error->d(); } Heap::Object *ExecutionEngine::newSyntaxErrorObject(const QString &message, const QString &fileName, int line, int column) { Scope scope(this); ScopedObject error(scope, memoryManager->alloc(this, message, fileName, line, column)); return error->d(); } Heap::Object *ExecutionEngine::newReferenceErrorObject(const QString &message) { Scope scope(this); ScopedObject o(scope, memoryManager->alloc(this, message)); return o->d(); } Heap::Object *ExecutionEngine::newReferenceErrorObject(const QString &message, const QString &fileName, int lineNumber, int columnNumber) { Scope scope(this); ScopedObject o(scope, memoryManager->alloc(this, message, fileName, lineNumber, columnNumber)); return o->d(); } Heap::Object *ExecutionEngine::newTypeErrorObject(const QString &message) { Scope scope(this); ScopedObject o(scope, memoryManager->alloc(this, message)); return o->d(); } Heap::Object *ExecutionEngine::newRangeErrorObject(const QString &message) { Scope scope(this); ScopedObject o(scope, memoryManager->alloc(this, message)); return o->d(); } Heap::Object *ExecutionEngine::newURIErrorObject(const Value &message) { Scope scope(this); ScopedObject o(scope, memoryManager->alloc(this, message)); return o->d(); } Heap::Object *ExecutionEngine::newVariantObject(const QVariant &v) { Scope scope(this); ScopedObject o(scope, memoryManager->alloc(this, v)); return o->d(); } Heap::Object *ExecutionEngine::newForEachIteratorObject(Object *o) { Scope scope(this); ScopedObject obj(scope, memoryManager->alloc(this, o)); return obj->d(); } Heap::QmlContext *ExecutionEngine::qmlContext() const { Heap::ExecutionContext *ctx = currentContext(); if (ctx->type == Heap::ExecutionContext::Type_SimpleCallContext && !ctx->outer) ctx = ctx->parent; if (!ctx->outer) return 0; while (ctx->outer && ctx->outer->type != Heap::ExecutionContext::Type_GlobalContext) ctx = ctx->outer; Q_ASSERT(ctx); if (ctx->type != Heap::ExecutionContext::Type_QmlContext) return 0; return static_cast(ctx); } Heap::QmlContextWrapper *ExecutionEngine::qmlContextObject() const { Heap::QmlContext *ctx = qmlContext(); if (!ctx) return 0; Q_ASSERT(ctx->qml); return ctx->qml; } QObject *ExecutionEngine::qmlScopeObject() const { return qmlContextObject()->scopeObject; } ReturnedValue ExecutionEngine::qmlSingletonWrapper(String *name) { QQmlContextData *ctx = callingQmlContext(); if (!ctx->imports) return Encode::undefined(); // Search for attached properties, enums and imported scripts QQmlTypeNameCache::Result r = ctx->imports->query(name); Q_ASSERT(r.isValid()); Q_ASSERT(r.type); Q_ASSERT(r.type->isSingleton()); QQmlType::SingletonInstanceInfo *siinfo = r.type->singletonInstanceInfo(); QQmlEngine *e = qmlEngine(); siinfo->init(e); if (QObject *qobjectSingleton = siinfo->qobjectApi(e)) return QV4::QObjectWrapper::wrap(this, qobjectSingleton); return QJSValuePrivate::convertedToValue(this, siinfo->scriptApi(e)); } QQmlContextData *ExecutionEngine::callingQmlContext() const { Heap::QmlContextWrapper *w = qmlContextObject(); return w ? w->context.contextData() : 0; } QVector ExecutionEngine::stackTrace(int frameLimit) const { Scope scope(const_cast(this)); ScopedString name(scope); QVector stack; ScopedContext c(scope, currentContext()); ScopedFunctionObject function(scope); while (c && frameLimit) { function = c->getFunctionObject(); if (function) { StackFrame frame; if (const Function *f = function->function()) frame.source = f->sourceFile(); name = function->name(); frame.function = name->toQString(); frame.line = -1; frame.column = -1; if (function->function()) // line numbers can be negative for places where you can't set a real breakpoint frame.line = qAbs(c->d()->lineNumber); stack.append(frame); --frameLimit; } c = c->d()->parent; } if (frameLimit && globalCode) { StackFrame frame; frame.source = globalCode->sourceFile(); frame.function = globalCode->name()->toQString(); frame.line = rootContext()->d()->lineNumber; frame.column = -1; stack.append(frame); } return stack; } StackFrame ExecutionEngine::currentStackFrame() const { StackFrame frame; frame.line = -1; frame.column = -1; QVector trace = stackTrace(/*limit*/ 1); if (!trace.isEmpty()) frame = trace.first(); return frame; } /* 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->d()->engine) { const QVector stackTrace = context->d()->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=\"" << 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)); } QUrl ExecutionEngine::resolvedUrl(const QString &file) { QUrl src(file); if (!src.isRelative()) return src; QUrl base; Scope scope(this); ScopedContext c(scope, currentContext()); while (c) { CallContext *callCtx = c->asCallContext(); if (callCtx && callCtx->d()->function) { if (callCtx->d()->function->function) base.setUrl(callCtx->d()->function->function->sourceFile()); break; } c = c->d()->parent; } if (base.isEmpty() && globalCode) base.setUrl(globalCode->sourceFile()); if (base.isEmpty()) return src; return base.resolved(src); } void ExecutionEngine::requireArgumentsAccessors(int n) { if (n <= nArgumentsAccessors) return; Scope scope(this); ScopedFunctionObject get(scope); ScopedFunctionObject set(scope); if (n >= nArgumentsAccessors) { Property *oldAccessors = argumentsAccessors; int oldSize = nArgumentsAccessors; nArgumentsAccessors = qMax(8, n); argumentsAccessors = new Property[nArgumentsAccessors]; if (oldAccessors) { memcpy(argumentsAccessors, oldAccessors, oldSize*sizeof(Property)); delete [] oldAccessors; } ScopedContext global(scope, scope.engine->rootContext()); for (int i = oldSize; i < nArgumentsAccessors; ++i) { argumentsAccessors[i].value = ScopedValue(scope, memoryManager->alloc(global, i)); argumentsAccessors[i].set = ScopedValue(scope, memoryManager->alloc(global, i)); } } } void ExecutionEngine::markObjects() { identifierTable->mark(this); for (int i = 0; i < nArgumentsAccessors; ++i) { const Property &pd = argumentsAccessors[i]; if (Heap::FunctionObject *getter = pd.getter()) getter->mark(this); if (Heap::FunctionObject *setter = pd.setter()) setter->mark(this); } Heap::ExecutionContext *c = currentContext(); while (c) { Q_ASSERT(c->inUse()); if (!c->isMarked()) { c->setMarkBit(); c->vtable()->markObjects(c, this); } c = c->parent; } classPool->markObjects(this); for (QSet::ConstIterator it = compilationUnits.constBegin(), end = compilationUnits.constEnd(); it != end; ++it) (*it)->markObjects(this); } 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 (debugger) debugger->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 = Primitive::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 Value &value) { Scope scope(this); ScopedString s(scope, value.toString(this)); QString msg = s->toQString() + QStringLiteral(" 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() + QStringLiteral(" 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(QStringLiteral("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.first(); error.setUrl(QUrl(frame.source)); error.setLine(frame.line); error.setColumn(frame.column); } QV4::Scoped errorObj(scope, exception); if (!!errorObj && errorObj->asSyntaxError()) { QV4::ScopedString m(scope, newString(QStringLiteral("message"))); QV4::ScopedValue v(scope, errorObj->get(m)); error.setDescription(v->toQStringNoThrow()); } else error.setDescription(exception->toQStringNoThrow()); return error; } bool ExecutionEngine::recheckCStackLimits() { int dummy; #ifdef Q_OS_WIN // ### this is only required on windows, where we currently use heuristics to get the stack limit if (cStackLimit - reinterpret_cast(&dummy) > 128*1024) // we're more then 128k away from our stack limit, assume the thread has changed, and // call getStackLimit #endif // this can happen after a thread change cStackLimit = getStackLimit(); return (reinterpret_cast(&dummy) >= cStackLimit); } // Variant conversion code typedef QSet V4ObjectSet; static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects); static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &value); static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects = 0); static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &value, const QByteArray &targetType, void **result); static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVariantList &lst); static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVariantMap &vmap); static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant &value) { return v4->metaTypeToJS(value.userType(), value.constData()); } QVariant ExecutionEngine::toVariant(const Value &value, int typeHint, bool createJSValueForObjects) { return ::toVariant(this, value, typeHint, createJSValueForObjects, 0); } static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects) { Q_ASSERT (!value.isEmpty()); QV4::Scope scope(e); if (const QV4::VariantObject *v = value.as()) return v->d()->data; if (typeHint == QVariant::Bool) return QVariant(value.toBoolean()); if (typeHint == QMetaType::QJsonValue) return QVariant::fromValue(QV4::JsonObject::toJsonValue(value)); if (typeHint == qMetaTypeId()) return QVariant::fromValue(QJSValue(e, value.asReturnedValue())); if (value.as()) { QV4::ScopedObject object(scope, value); if (typeHint == QMetaType::QJsonObject && !value.as() && !value.as()) { return QVariant::fromValue(QV4::JsonObject::toJsonObject(object)); } else if (QV4::QObjectWrapper *wrapper = object->as()) { return qVariantFromValue(wrapper->object()); } else if (object->as()) { return QVariant(); } else if (QV4::QmlTypeWrapper *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 (object->isListType()) return QV4::SequencePrototype::toVariant(object); } if (value.as()) { QV4::ScopedArrayObject a(scope, value); if (typeHint == qMetaTypeId >()) { QList list; uint length = a->getLength(); QV4::Scoped qobjectWrapper(scope); for (uint ii = 0; ii < length; ++ii) { qobjectWrapper = a->getIndexed(ii); if (!!qobjectWrapper) { list << qobjectWrapper->object(); } else { list << 0; } } return qVariantFromValue >(list); } else if (typeHint == QMetaType::QJsonArray) { return QVariant::fromValue(QV4::JsonObject::toJsonArray(a)); } bool succeeded = false; QVariant retn = QV4::SequencePrototype::toVariant(value, typeHint, &succeeded); if (succeeded) return retn; } if (value.isUndefined()) return QVariant(); if (value.isNull()) return QVariant(QMetaType::VoidStar, (void *)0); if (value.isBoolean()) return value.booleanValue(); if (value.isInteger()) return value.integerValue(); if (value.isNumber()) return value.asDouble(); if (value.isString()) return value.stringValue()->toQString(); if (const QV4::QQmlLocaleData *ld = value.as()) return ld->d()->locale; if (const QV4::DateObject *d = value.as()) return d->toQDateTime(); if (const QV4::ArrayBuffer *d = value.as()) return d->asByteArray(); // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)! QV4::ScopedObject o(scope, value); Q_ASSERT(o); if (QV4::RegExpObject *re = o->as()) return re->toQRegExp(); if (createJSValueForObjects) return QVariant::fromValue(QJSValue(scope.engine, o->asReturnedValue())); return objectToVariant(e, o, visitedObjects); } static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects) { 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(e); 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->getIndexed(ii); list << ::toVariant(e, v, -1, /*createJSValueForObjects*/false, visitedObjects); } result = list; } else if (!o->as()) { QVariantMap map; QV4::Scope scope(e); QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); QV4::ScopedValue name(scope); QV4::ScopedValue val(scope); while (1) { name = it.nextPropertyNameAsString(val); if (name->isNull()) break; QString key = name->toQStringNoThrow(); map.insert(key, ::toVariant(e, val, /*type hint*/-1, /*createJSValueForObjects*/false, visitedObjects)); } result = map; } visitedObjects->remove(o->d()); return result; } static QV4::ReturnedValue arrayFromVariantList(QV4::ExecutionEngine *e, const QVariantList &list) { QV4::Scope scope(e); QV4::ScopedArrayObject a(scope, e->newArrayObject()); int len = list.count(); a->arrayReserve(len); QV4::ScopedValue v(scope); for (int ii = 0; ii < len; ++ii) a->arrayPut(ii, (v = scope.engine->fromVariant(list.at(ii)))); a->setArrayLengthUnchecked(len); return a.asReturnedValue(); } static QV4::ReturnedValue objectFromVariantMap(QV4::ExecutionEngine *e, const QVariantMap &map) { QV4::Scope scope(e); QV4::ScopedObject o(scope, e->newObject()); QV4::ScopedString s(scope); QV4::ScopedValue v(scope); for (QVariantMap::const_iterator iter = map.begin(), cend = map.end(); iter != cend; ++iter) { s = e->newString(iter.key()); uint idx = s->asArrayIndex(); if (idx > 16 && (!o->arrayData() || idx > o->arrayData()->length() * 2)) o->initSparseArray(); o->put(s, (v = e->fromVariant(iter.value()))); } return o.asReturnedValue(); } Q_CORE_EXPORT QString qt_regexp_toCanonical(const QString &, QRegExp::PatternSyntax); QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) { int type = variant.userType(); const void *ptr = variant.constData(); if (type < QMetaType::User) { switch (QMetaType::Type(type)) { case QMetaType::UnknownType: case QMetaType::Void: return QV4::Encode::undefined(); 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::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::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 newString(QChar::fromLatin1(*reinterpret_cast(ptr)))->asReturnedValue(); case QMetaType::UChar: return newString(QChar::fromLatin1(*reinterpret_cast(ptr)))->asReturnedValue(); case QMetaType::QChar: return newString(*reinterpret_cast(ptr))->asReturnedValue(); case QMetaType::QDateTime: return QV4::Encode(newDateObject(*reinterpret_cast(ptr))); case QMetaType::QDate: return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast(ptr)))); case QMetaType::QTime: return QV4::Encode(newDateObject(QDateTime(QDate(1970,1,1), *reinterpret_cast(ptr)))); case QMetaType::QRegExp: return QV4::Encode(newRegExpObject(*reinterpret_cast(ptr))); case QMetaType::QObjectStar: return QV4::QObjectWrapper::wrap(this, *reinterpret_cast(ptr)); case QMetaType::QStringList: { bool succeeded = false; QV4::Scope scope(this); QV4::ScopedValue retn(scope, QV4::SequencePrototype::fromVariant(this, variant, &succeeded)); if (succeeded) return retn->asReturnedValue(); return QV4::Encode(newArrayObject(*reinterpret_cast(ptr))); } case QMetaType::QVariantList: return arrayFromVariantList(this, *reinterpret_cast(ptr)); case QMetaType::QVariantMap: return objectFromVariantMap(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::QLocale: return QQmlLocale::wrap(this, *reinterpret_cast(ptr)); default: break; } if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(type)) return QV4::QQmlValueTypeWrapper::create(this, variant, vtmo, type); } else { QV4::Scope scope(this); if (type == qMetaTypeId()) { 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 (type == qMetaTypeId()) { const QJSValue *value = reinterpret_cast(ptr); return QJSValuePrivate::convertedToValue(this, *value); } else if (type == qMetaTypeId >()) { // 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.count()); QV4::ScopedValue v(scope); for (int ii = 0; ii < list.count(); ++ii) a->arrayPut(ii, (v = QV4::QObjectWrapper::wrap(this, list.at(ii)))); a->setArrayLengthUnchecked(list.count()); return a.asReturnedValue(); } else if (QMetaType::typeFlags(type) & QMetaType::PointerToQObject) { return QV4::QObjectWrapper::wrap(this, *reinterpret_cast(ptr)); } bool objOk; QObject *obj = QQmlMetaType::toQObject(variant, &objOk); if (objOk) return QV4::QObjectWrapper::wrap(this, obj); bool succeeded = false; QV4::ScopedValue retn(scope, QV4::SequencePrototype::fromVariant(this, variant, &succeeded)); if (succeeded) return retn->asReturnedValue(); if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(type)) return QV4::QQmlValueTypeWrapper::create(this, variant, vtmo, type); } // XXX TODO: To be compatible, we still need to handle: // + QObjectList // + QList return QV4::Encode(newVariantObject(variant)); } QVariantMap ExecutionEngine::variantMapFromJS(const Object *o) { return objectToVariant(this, o).toMap(); } // Converts a QVariantList to JS. // The result is a new Array object with length equal to the length // of the QVariantList, and the elements being the QVariantList's // elements converted to JS, recursively. static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVariantList &lst) { QV4::Scope scope(v4); QV4::ScopedArrayObject a(scope, v4->newArrayObject()); a->arrayReserve(lst.size()); QV4::ScopedValue v(scope); for (int i = 0; i < lst.size(); i++) a->arrayPut(i, (v = variantToJS(v4, lst.at(i)))); a->setArrayLengthUnchecked(lst.size()); return a.asReturnedValue(); } // 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::ScopedValue v(scope); for (QVariantMap::const_iterator it = vmap.constBegin(), cend = vmap.constEnd(); it != cend; ++it) { s = v4->newIdentifier(it.key()); v = variantToJS(v4, it.value()); uint idx = s->asArrayIndex(); if (idx < UINT_MAX) o->arraySet(idx, 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(int type, const void *data) { Q_ASSERT(data != 0); // check if it's one of the types we know switch (QMetaType::Type(type)) { case QMetaType::UnknownType: case QMetaType::Void: return QV4::Encode::undefined(); case QMetaType::VoidStar: return QV4::Encode::null(); case QMetaType::Bool: return QV4::Encode(*reinterpret_cast(data)); case QMetaType::Int: return QV4::Encode(*reinterpret_cast(data)); case QMetaType::UInt: return QV4::Encode(*reinterpret_cast(data)); case QMetaType::LongLong: return QV4::Encode(double(*reinterpret_cast(data))); case QMetaType::ULongLong: #if defined(Q_OS_WIN) && defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 12008804 #pragma message("** NOTE: You need the Visual Studio Processor Pack to compile support for 64bit unsigned integers.") return QV4::Encode(double((qlonglong)*reinterpret_cast(data))); #elif defined(Q_CC_MSVC) && !defined(Q_CC_MSVC_NET) return QV4::Encode(double((qlonglong)*reinterpret_cast(data))); #else return QV4::Encode(double(*reinterpret_cast(data))); #endif case QMetaType::Double: return QV4::Encode(*reinterpret_cast(data)); case QMetaType::QString: return newString(*reinterpret_cast(data))->asReturnedValue(); case QMetaType::Float: return QV4::Encode(*reinterpret_cast(data)); case QMetaType::Short: return QV4::Encode((int)*reinterpret_cast(data)); case QMetaType::UShort: return QV4::Encode((int)*reinterpret_cast(data)); case QMetaType::Char: return QV4::Encode((int)*reinterpret_cast(data)); case QMetaType::UChar: return QV4::Encode((int)*reinterpret_cast(data)); case QMetaType::QChar: return QV4::Encode((int)(*reinterpret_cast(data)).unicode()); case QMetaType::QStringList: return QV4::Encode(newArrayObject(*reinterpret_cast(data))); case QMetaType::QVariantList: return variantListToJS(this, *reinterpret_cast(data)); case QMetaType::QVariantMap: return variantMapToJS(this, *reinterpret_cast(data)); case QMetaType::QDateTime: return QV4::Encode(newDateObject(*reinterpret_cast(data))); case QMetaType::QDate: return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast(data)))); case QMetaType::QRegExp: return QV4::Encode(newRegExpObject(*reinterpret_cast(data))); case QMetaType::QObjectStar: return QV4::QObjectWrapper::wrap(this, *reinterpret_cast(data)); case QMetaType::QVariant: return variantToJS(this, *reinterpret_cast(data)); case QMetaType::QJsonValue: return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast(data)); case QMetaType::QJsonObject: return QV4::JsonObject::fromJsonObject(this, *reinterpret_cast(data)); case QMetaType::QJsonArray: return QV4::JsonObject::fromJsonArray(this, *reinterpret_cast(data)); default: if (type == qMetaTypeId()) { return QJSValuePrivate::convertedToValue(this, *reinterpret_cast(data)); } else { QByteArray typeName = QMetaType::typeName(type); if (typeName.endsWith('*') && !*reinterpret_cast(data)) { return QV4::Encode::null(); } QMetaType mt(type); if (mt.flags() & QMetaType::IsGadget) { Q_ASSERT(mt.metaObject()); return QV4::QQmlValueTypeWrapper::create(this, QVariant(type, data), mt.metaObject(), type); } // Fall back to wrapping in a QVariant. return QV4::Encode(newVariantObject(QVariant(type, data))); } } Q_UNREACHABLE(); return 0; } void ExecutionEngine::assertObjectBelongsToEngine(const Heap::Base &baseObject) { Q_ASSERT(!baseObject.vtable()->isObject || static_cast(baseObject).internalClass->engine == this); Q_UNUSED(baseObject); } // Converts a JS value to a meta-type. // data must point to a place that can store a value of the given type. // Returns true if conversion succeeded, false otherwise. bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) { // check if it's one of the types we know switch (QMetaType::Type(type)) { 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::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() || value->isNull()) *reinterpret_cast(data) = QString(); else *reinterpret_cast(data) = value->toQString(); 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::QChar: if (value->isString()) { QString str = value->stringValue()->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) = d->toQDateTime().date(); return true; } break; case QMetaType::QRegExp: if (const QV4::RegExpObject *r = value->as()) { *reinterpret_cast(data) = r->toQRegExp(); return true; } break; case QMetaType::QObjectStar: { const QV4::QObjectWrapper *qobjectWrapper = value->as(); if (qobjectWrapper || value->isNull()) { *reinterpret_cast(data) = qtObjectFromJS(this, *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) = toVariant(*a, /*typeHint*/-1, /*createJSValueForObjects*/false).toList(); return true; } break; } case QMetaType::QVariantMap: { const QV4::Object *o = value->as(); if (o) { *reinterpret_cast(data) = variantMapFromJS(o); return true; } break; } case QMetaType::QVariant: *reinterpret_cast(data) = toVariant(*value, /*typeHint*/-1, /*createJSValueForObjects*/false); 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: ; } { const QQmlValueTypeWrapper *vtw = value->as(); if (vtw && vtw->typeId() == type) { return vtw->toGadget(data); } } #if 0 if (isQtVariant(value)) { const QVariant &var = variantValue(value); // ### Enable once constructInPlace() is in qt master. if (var.userType() == type) { QMetaType::constructInPlace(type, data, var.constData()); return true; } if (var.canConvert(type)) { QVariant vv = var; vv.convert(type); Q_ASSERT(vv.userType() == type); QMetaType::constructInPlace(type, data, vv.constData()); return true; } } #endif // Try to use magic; for compatibility with qjsvalue_cast. QByteArray name = QMetaType::typeName(type); if (convertToNativeQObject(this, *value, name, reinterpret_cast(data))) return true; if (value->as() && name.endsWith('*')) { int valueType = QMetaType::type(name.left(name.size()-1)); QVariant &var = value->as()->d()->data; if (valueType == var.userType()) { // We have T t, T* is requested, so return &t. *reinterpret_cast(data) = var.data(); return true; } else if (value->isObject()) { // Look in the prototype chain. QV4::Scope scope(this); QV4::ScopedObject proto(scope, value->objectValue()->prototype()); while (proto) { bool canCast = false; if (QV4::VariantObject *vo = proto->as()) { const QVariant &v = vo->d()->data; canCast = (type == v.userType()) || (valueType && (valueType == v.userType())); } else if (proto->as()) { QByteArray className = name.left(name.size()-1); QV4::ScopedObject p(scope, proto.getPointer()); if (QObject *qobject = qtObjectFromJS(this, p)) canCast = qobject->qt_metacast(className) != 0; } if (canCast) { QByteArray varTypeName = QMetaType::typeName(var.userType()); if (varTypeName.endsWith('*')) *reinterpret_cast(data) = *reinterpret_cast(var.data()); else *reinterpret_cast(data) = var.data(); return true; } proto = proto->prototype(); } } } else if (value->isNull() && name.endsWith('*')) { *reinterpret_cast(data) = 0; return true; } else if (type == qMetaTypeId()) { *reinterpret_cast(data) = QJSValue(this, value->asReturnedValue()); return true; } return false; } static bool convertToNativeQObject(QV4::ExecutionEngine *e, const Value &value, const QByteArray &targetType, void **result) { if (!targetType.endsWith('*')) return false; if (QObject *qobject = qtObjectFromJS(e, value)) { int start = targetType.startsWith("const ") ? 6 : 0; QByteArray className = targetType.mid(start, targetType.size()-start-1); if (void *instance = qobject->qt_metacast(className)) { *result = instance; return true; } } return false; } static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const Value &value) { if (!value.isObject()) return 0; QV4::Scope scope(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 0; return wrapper->object(); } QT_END_NAMESPACE