diff options
Diffstat (limited to 'src/qml/jsruntime')
139 files changed, 23630 insertions, 10080 deletions
diff --git a/src/qml/jsruntime/jsruntime.pri b/src/qml/jsruntime/jsruntime.pri index 9938f60aea..a24ee0a188 100644 --- a/src/qml/jsruntime/jsruntime.pri +++ b/src/qml/jsruntime/jsruntime.pri @@ -3,7 +3,6 @@ INCLUDEPATH += $$OUT_PWD !qmldevtools_build { SOURCES += \ - $$PWD/qv4engine.cpp \ $$PWD/qv4context.cpp \ $$PWD/qv4persistent.cpp \ $$PWD/qv4lookup.cpp \ @@ -12,37 +11,55 @@ SOURCES += \ $$PWD/qv4managed.cpp \ $$PWD/qv4internalclass.cpp \ $$PWD/qv4sparsearray.cpp \ + $$PWD/qv4atomics.cpp \ $$PWD/qv4arraydata.cpp \ $$PWD/qv4arrayobject.cpp \ + $$PWD/qv4arrayiterator.cpp \ $$PWD/qv4argumentsobject.cpp \ $$PWD/qv4booleanobject.cpp \ $$PWD/qv4dateobject.cpp \ $$PWD/qv4errorobject.cpp \ $$PWD/qv4function.cpp \ $$PWD/qv4functionobject.cpp \ + $$PWD/qv4generatorobject.cpp \ $$PWD/qv4globalobject.cpp \ + $$PWD/qv4iterator.cpp \ $$PWD/qv4jsonobject.cpp \ $$PWD/qv4mathobject.cpp \ $$PWD/qv4memberdata.cpp \ $$PWD/qv4numberobject.cpp \ $$PWD/qv4object.cpp \ $$PWD/qv4objectproto.cpp \ + $$PWD/qv4propertykey.cpp \ + $$PWD/qv4proxy.cpp \ $$PWD/qv4qmlcontext.cpp \ + $$PWD/qv4reflect.cpp \ $$PWD/qv4regexpobject.cpp \ + $$PWD/qv4stackframe.cpp \ + $$PWD/qv4stringiterator.cpp \ $$PWD/qv4stringobject.cpp \ $$PWD/qv4variantobject.cpp \ $$PWD/qv4objectiterator.cpp \ $$PWD/qv4regexp.cpp \ + $$PWD/qv4runtimecodegen.cpp \ $$PWD/qv4serialize.cpp \ $$PWD/qv4script.cpp \ - $$PWD/qv4sequenceobject.cpp \ + $$PWD/qv4symbol.cpp \ + $$PWD/qv4setobject.cpp \ + $$PWD/qv4setiterator.cpp \ $$PWD/qv4include.cpp \ $$PWD/qv4qobjectwrapper.cpp \ $$PWD/qv4arraybuffer.cpp \ $$PWD/qv4typedarray.cpp \ - $$PWD/qv4dataview.cpp + $$PWD/qv4dataview.cpp \ + $$PWD/qv4vme_moth.cpp \ + $$PWD/qv4mapobject.cpp \ + $$PWD/qv4mapiterator.cpp \ + $$PWD/qv4estable.cpp \ + $$PWD/qv4module.cpp \ + $$PWD/qv4promiseobject.cpp -!contains(QT_CONFIG, no-qml-debug): SOURCES += $$PWD/qv4profiling.cpp +qtConfig(qml-debug): SOURCES += $$PWD/qv4profiling.cpp HEADERS += \ $$PWD/qv4global_p.h \ @@ -58,24 +75,35 @@ HEADERS += \ $$PWD/qv4identifiertable_p.h \ $$PWD/qv4managed_p.h \ $$PWD/qv4internalclass_p.h \ + $$PWD/qv4jscall_p.h \ $$PWD/qv4sparsearray_p.h \ + $$PWD/qv4atomics_p.h \ $$PWD/qv4arraydata_p.h \ $$PWD/qv4arrayobject_p.h \ + $$PWD/qv4arrayiterator_p.h \ $$PWD/qv4argumentsobject_p.h \ $$PWD/qv4booleanobject_p.h \ $$PWD/qv4dateobject_p.h \ $$PWD/qv4errorobject_p.h \ $$PWD/qv4function_p.h \ $$PWD/qv4functionobject_p.h \ + $$PWD/qv4generatorobject_p.h \ $$PWD/qv4globalobject_p.h \ + $$PWD/qv4iterator_p.h \ $$PWD/qv4jsonobject_p.h \ $$PWD/qv4mathobject_p.h \ $$PWD/qv4memberdata_p.h \ $$PWD/qv4numberobject_p.h \ $$PWD/qv4object_p.h \ $$PWD/qv4objectproto_p.h \ + $$PWD/qv4propertykey_p.h \ + $$PWD/qv4proxy_p.h \ $$PWD/qv4qmlcontext_p.h \ + $$PWD/qv4reflect_p.h \ $$PWD/qv4regexpobject_p.h \ + $$PWD/qv4runtimecodegen_p.h \ + $$PWD/qv4stackframe_p.h \ + $$PWD/qv4stringiterator_p.h \ $$PWD/qv4stringobject_p.h \ $$PWD/qv4variantobject_p.h \ $$PWD/qv4property_p.h \ @@ -83,21 +111,31 @@ HEADERS += \ $$PWD/qv4regexp_p.h \ $$PWD/qv4serialize_p.h \ $$PWD/qv4script_p.h \ + $$PWD/qv4symbol_p.h \ + $$PWD/qv4setobject_p.h \ + $$PWD/qv4setiterator_p.h \ $$PWD/qv4scopedvalue_p.h \ $$PWD/qv4executableallocator_p.h \ - $$PWD/qv4sequenceobject_p.h \ $$PWD/qv4include_p.h \ $$PWD/qv4qobjectwrapper_p.h \ $$PWD/qv4profiling_p.h \ $$PWD/qv4arraybuffer_p.h \ $$PWD/qv4typedarray_p.h \ - $$PWD/qv4dataview_p.h + $$PWD/qv4dataview_p.h \ + $$PWD/qv4vme_moth_p.h \ + $$PWD/qv4mapobject_p.h \ + $$PWD/qv4mapiterator_p.h \ + $$PWD/qv4estable_p.h \ + $$PWD/qv4vtable_p.h \ + $$PWD/qv4module_p.h \ + $$PWD/qv4promiseobject_p.h -qtConfig(qml-interpreter) { +qtConfig(qml-sequence-object) { HEADERS += \ - $$PWD/qv4vme_moth_p.h + $$PWD/qv4sequenceobject_p.h + SOURCES += \ - $$PWD/qv4vme_moth.cpp + $$PWD/qv4sequenceobject.cpp } } @@ -109,14 +147,33 @@ HEADERS += \ $$PWD/qv4value_p.h \ $$PWD/qv4string_p.h \ $$PWD/qv4util_p.h \ - $$PWD/qv4value_p.h + $$PWD/qv4value_p.h \ + $$PWD/qv4functiontable_p.h SOURCES += \ + $$PWD/qv4engine.cpp \ $$PWD/qv4runtime.cpp \ $$PWD/qv4string.cpp \ $$PWD/qv4value.cpp \ $$PWD/qv4executableallocator.cpp +qmldevtools_build { + SOURCES += \ + $$PWD/qv4functiontable_noop.cpp +} else:win32 { + !winrt:equals(QT_ARCH, x86_64) { + SOURCES += \ + $$PWD/qv4functiontable_win64.cpp + } else { + SOURCES += \ + $$PWD/qv4functiontable_noop.cpp + } +} else { + SOURCES += \ + $$PWD/qv4functiontable_unix.cpp +} + + valgrind { DEFINES += V4_USE_VALGRIND } diff --git a/src/qml/jsruntime/qv4alloca_p.h b/src/qml/jsruntime/qv4alloca_p.h index 1e9f83a90e..65c3e4d65a 100644 --- a/src/qml/jsruntime/qv4alloca_p.h +++ b/src/qml/jsruntime/qv4alloca_p.h @@ -89,7 +89,7 @@ public: Qt_AllocaWrapper() { m_data = 0; } ~Qt_AllocaWrapper() { free(m_data); } void *data() { return m_data; } - void allocate(int size) { m_data = malloc(size); } + void allocate(int size) { m_data = malloc(size); memset(m_data, 0, size); } private: void *m_data; }; diff --git a/src/qml/jsruntime/qv4argumentsobject.cpp b/src/qml/jsruntime/qv4argumentsobject.cpp index 0905c2828a..4a21f62cf2 100644 --- a/src/qml/jsruntime/qv4argumentsobject.cpp +++ b/src/qml/jsruntime/qv4argumentsobject.cpp @@ -37,209 +37,187 @@ ** ****************************************************************************/ #include <qv4argumentsobject_p.h> +#include <qv4arrayobject_p.h> #include <qv4alloca_p.h> #include <qv4scopedvalue_p.h> #include <qv4string_p.h> #include <qv4function_p.h> +#include <qv4jscall_p.h> +#include <qv4symbol_p.h> using namespace QV4; DEFINE_OBJECT_VTABLE(ArgumentsObject); +DEFINE_OBJECT_VTABLE(StrictArgumentsObject); + +void Heap::StrictArgumentsObject::init(QV4::CppStackFrame *frame) -void Heap::ArgumentsObject::init(QV4::CallContext *context) { + Q_ASSERT(vtable() == QV4::StrictArgumentsObject::staticVTable()); ExecutionEngine *v4 = internalClass->engine; Object::init(); - fullyCreated = false; + + Q_ASSERT(internalClass->verifyIndex(v4->id_callee()->propertyKey(), CalleePropertyIndex)); + Q_ASSERT(internalClass->findValueOrSetter(v4->id_callee()->propertyKey()).index == CalleeSetterPropertyIndex); + Q_ASSERT(internalClass->verifyIndex(v4->symbol_iterator()->propertyKey(), SymbolIteratorPropertyIndex)); + setProperty(v4, SymbolIteratorPropertyIndex, *v4->arrayProtoValues()); + setProperty(v4, CalleePropertyIndex, *v4->thrower()); + setProperty(v4, CalleeSetterPropertyIndex, *v4->thrower()); + + Scope scope(v4); + Scoped<QV4::StrictArgumentsObject> args(scope, this); + args->arrayReserve(frame->originalArgumentsCount); + args->arrayPut(0, frame->originalArguments, frame->originalArgumentsCount); + + Q_ASSERT(args->internalClass()->verifyIndex(v4->id_length()->propertyKey(), LengthPropertyIndex)); + setProperty(v4, LengthPropertyIndex, Value::fromInt32(frame->originalArgumentsCount)); +} + +void Heap::ArgumentsObject::init(QV4::CppStackFrame *frame) +{ + ExecutionEngine *v4 = internalClass->engine; + + QV4::CallContext *context = static_cast<QV4::CallContext *>(frame->context()); + + Object::init(); this->context.set(v4, context->d()); Q_ASSERT(vtable() == QV4::ArgumentsObject::staticVTable()); - Scope scope(v4); - Scoped<QV4::ArgumentsObject> args(scope, this); - - if (context->d()->strictMode) { - Q_ASSERT(CalleePropertyIndex == args->internalClass()->find(v4->id_callee())); - Q_ASSERT(CallerPropertyIndex == args->internalClass()->find(v4->id_caller())); - args->setProperty(CalleePropertyIndex + QV4::Object::GetterOffset, *v4->thrower()); - args->setProperty(CalleePropertyIndex + QV4::Object::SetterOffset, *v4->thrower()); - args->setProperty(CallerPropertyIndex + QV4::Object::GetterOffset, *v4->thrower()); - args->setProperty(CallerPropertyIndex + QV4::Object::SetterOffset, *v4->thrower()); - - args->arrayReserve(context->argc()); - args->arrayPut(0, context->args(), context->argc()); - args->d()->fullyCreated = true; - } else { - Q_ASSERT(CalleePropertyIndex == args->internalClass()->find(v4->id_callee())); - args->setProperty(CalleePropertyIndex, context->d()->function); - } - Q_ASSERT(LengthPropertyIndex == args->internalClass()->find(v4->id_length())); - args->setProperty(LengthPropertyIndex, Primitive::fromInt32(context->d()->callData->argc)); + Q_ASSERT(internalClass->verifyIndex(v4->id_callee()->propertyKey(), CalleePropertyIndex)); + setProperty(v4, CalleePropertyIndex, context->d()->function); + Q_ASSERT(internalClass->verifyIndex(v4->id_length()->propertyKey(), LengthPropertyIndex)); + setProperty(v4, LengthPropertyIndex, Value::fromInt32(context->argc())); + Q_ASSERT(internalClass->verifyIndex(v4->symbol_iterator()->propertyKey(), SymbolIteratorPropertyIndex)); + setProperty(v4, SymbolIteratorPropertyIndex, *v4->arrayProtoValues()); + + fullyCreated = false; + argCount = frame->originalArgumentsCount; + uint nFormals = frame->v4Function->nFormals; + mapped = nFormals > 63 ? std::numeric_limits<quint64>::max() : (1ull << nFormals) - 1; } void ArgumentsObject::fullyCreate() { - if (fullyCreated()) + if (d()->fullyCreated) return; Scope scope(engine()); - uint argCount = context()->callData->argc; - uint numAccessors = qMin(context()->formalParameterCount(), argCount); - ArrayData::realloc(this, Heap::ArrayData::Sparse, argCount, true); - scope.engine->requireArgumentsAccessors(numAccessors); - - Scoped<MemberData> md(scope, d()->mappedArguments); - if (numAccessors) { - d()->mappedArguments.set(scope.engine, md->allocate(scope.engine, numAccessors)); - for (uint i = 0; i < numAccessors; ++i) { - d()->mappedArguments->values.set(scope.engine, i, context()->callData->args[i]); - arraySet(i, scope.engine->argumentsAccessors + i, Attr_Accessor); - } - } - arrayPut(numAccessors, context()->callData->args + numAccessors, argCount - numAccessors); - for (uint i = numAccessors; i < argCount; ++i) - setArrayAttributes(i, Attr_Data); + arrayReserve(d()->argCount); + arrayPut(0, context()->args(), d()->argCount); + // Use a sparse array, so that method_getElement() doesn't shortcut + initSparseArray(); d()->fullyCreated = true; } -bool ArgumentsObject::defineOwnProperty(ExecutionEngine *engine, uint index, const Property *desc, PropertyAttributes attrs) +bool ArgumentsObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *desc, PropertyAttributes attrs) { - fullyCreate(); - - Scope scope(engine); - ScopedProperty map(scope); - PropertyAttributes mapAttrs; - uint numAccessors = qMin(context()->formalParameterCount(), static_cast<uint>(context()->callData->argc)); - bool isMapped = false; - if (arrayData() && index < numAccessors && - arrayData()->attributes(index).isAccessor() && - arrayData()->get(index) == scope.engine->argumentsAccessors[index].getter()->asReturnedValue()) - isMapped = true; - - if (isMapped) { - Q_ASSERT(arrayData()); - mapAttrs = arrayData()->attributes(index); - arrayData()->getProperty(index, map, &mapAttrs); - setArrayAttributes(index, Attr_Data); - ArrayData::Index arrayIndex{ arrayData(), arrayData()->mappedIndex(index) }; - arrayIndex.set(scope.engine, d()->mappedArguments->values[index]); - } + ArgumentsObject *args = static_cast<ArgumentsObject *>(m); + args->fullyCreate(); + uint index = id.asArrayIndex(); + + if (!args->isMapped(index)) + return Object::virtualDefineOwnProperty(m, id, desc, attrs); + + Scope scope(args); + PropertyAttributes cAttrs = attrs; + ScopedProperty cDesc(scope); + cDesc->copy(desc, attrs); - bool strict = engine->current->strictMode; - engine->current->strictMode = false; - bool result = Object::defineOwnProperty2(scope.engine, index, desc, attrs); - engine->current->strictMode = strict; - - if (isMapped && attrs.isData()) { - Q_ASSERT(arrayData()); - ScopedFunctionObject setter(scope, map->setter()); - ScopedCallData callData(scope, 1); - callData->thisObject = this->asReturnedValue(); - callData->args[0] = desc->value; - setter->call(scope, callData); - - if (attrs.isWritable()) { - setArrayAttributes(index, mapAttrs); - arrayData()->setProperty(engine, index, map); - } + if (attrs.isData() && desc->value.isEmpty() && attrs.hasWritable() && !attrs.isWritable()) { + cDesc->value = args->context()->args()[index]; + cAttrs.setType(PropertyAttributes::Data); } - if (engine->current->strictMode && !result) - return engine->throwTypeError(); - return result; + bool allowed = Object::virtualDefineOwnProperty(m, id, cDesc, cAttrs); + if (!allowed) + return false; + + if (attrs.isAccessor()) { + args->removeMapping(index); + } else { + if (!desc->value.isEmpty()) + args->context()->setArg(index, desc->value); + if (attrs.hasWritable() && !attrs.isWritable()) + args->removeMapping(index); + } + return true; } -ReturnedValue ArgumentsObject::getIndexed(const Managed *m, uint index, bool *hasProperty) +ReturnedValue ArgumentsObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { const ArgumentsObject *args = static_cast<const ArgumentsObject *>(m); - if (args->fullyCreated()) - return Object::getIndexed(m, index, hasProperty); - - if (index < static_cast<uint>(args->context()->callData->argc)) { + uint index = id.asArrayIndex(); + if (index < args->d()->argCount && !args->d()->fullyCreated) { if (hasProperty) *hasProperty = true; - return args->context()->callData->args[index].asReturnedValue(); + return args->context()->args()[index].asReturnedValue(); } + + if (!args->isMapped(index)) + return Object::virtualGet(m, id, receiver, hasProperty); + Q_ASSERT(index < static_cast<uint>(args->context()->function->formalParameterCount())); if (hasProperty) - *hasProperty = false; - return Encode::undefined(); + *hasProperty = true; + return args->context()->args()[index].asReturnedValue(); } -bool ArgumentsObject::putIndexed(Managed *m, uint index, const Value &value) +bool ArgumentsObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { ArgumentsObject *args = static_cast<ArgumentsObject *>(m); - if (!args->fullyCreated() && index >= static_cast<uint>(args->context()->callData->argc)) - args->fullyCreate(); + uint index = id.asArrayIndex(); - if (args->fullyCreated()) - return Object::putIndexed(m, index, value); + if (args == receiver && index < args->d()->argCount && !args->d()->fullyCreated) { + args->context()->setArg(index, value); + return true; + } - args->context()->callData->args[index] = value; - return true; + bool isMapped = (args == receiver && args->isMapped(index)); + if (isMapped) + args->context()->setArg(index, value); + + return Object::virtualPut(m, id, value, receiver); } -bool ArgumentsObject::deleteIndexedProperty(Managed *m, uint index) +bool ArgumentsObject::virtualDeleteProperty(Managed *m, PropertyKey id) { ArgumentsObject *args = static_cast<ArgumentsObject *>(m); - if (!args->fullyCreated()) - args->fullyCreate(); - return Object::deleteIndexedProperty(m, index); + args->fullyCreate(); + bool result = Object::virtualDeleteProperty(m, id); + if (result) + args->removeMapping(id.asArrayIndex()); + return result; } -PropertyAttributes ArgumentsObject::queryIndexed(const Managed *m, uint index) +PropertyAttributes ArgumentsObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { const ArgumentsObject *args = static_cast<const ArgumentsObject *>(m); - if (args->fullyCreated()) - return Object::queryIndexed(m, index); - - uint numAccessors = qMin((int)args->context()->formalParameterCount(), args->context()->callData->argc); - uint argCount = args->context()->callData->argc; - if (index >= argCount) - return PropertyAttributes(); - if (index >= numAccessors) + uint index = id.asArrayIndex(); + if (index < args->d()->argCount && !args->d()->fullyCreated) { + p->value = args->context()->args()[index]; return Attr_Data; - return Attr_Accessor; -} - -DEFINE_OBJECT_VTABLE(ArgumentsGetterFunction); - -void ArgumentsGetterFunction::call(const Managed *getter, Scope &scope, CallData *callData) -{ - ExecutionEngine *v4 = static_cast<const ArgumentsGetterFunction *>(getter)->engine(); - Scoped<ArgumentsGetterFunction> g(scope, static_cast<const ArgumentsGetterFunction *>(getter)); - Scoped<ArgumentsObject> o(scope, callData->thisObject.as<ArgumentsObject>()); - if (!o) { - scope.result = v4->throwTypeError(); - return; } - Q_ASSERT(g->index() < static_cast<unsigned>(o->context()->callData->argc)); - scope.result = o->context()->callData->args[g->index()]; -} + PropertyAttributes attrs = Object::virtualGetOwnProperty(m, id, p); + if (attrs.isEmpty() || !args->isMapped(index)) + return attrs; -DEFINE_OBJECT_VTABLE(ArgumentsSetterFunction); + Q_ASSERT(index < static_cast<uint>(args->context()->function->formalParameterCount())); + if (p) + p->value = args->context()->args()[index]; + return attrs; +} -void ArgumentsSetterFunction::call(const Managed *setter, Scope &scope, CallData *callData) +qint64 ArgumentsObject::virtualGetLength(const Managed *m) { - ExecutionEngine *v4 = static_cast<const ArgumentsSetterFunction *>(setter)->engine(); - Scoped<ArgumentsSetterFunction> s(scope, static_cast<const ArgumentsSetterFunction *>(setter)); - Scoped<ArgumentsObject> o(scope, callData->thisObject.as<ArgumentsObject>()); - if (!o) { - scope.result = v4->throwTypeError(); - return; - } - - Q_ASSERT(s->index() < static_cast<unsigned>(o->context()->callData->argc)); - o->context()->callData->args[s->index()] = callData->argc ? callData->args[0].asReturnedValue() : Encode::undefined(); - scope.result = Encode::undefined(); + const ArgumentsObject *a = static_cast<const ArgumentsObject *>(m); + return a->propertyData(Heap::ArgumentsObject::LengthPropertyIndex)->toLength(); } -uint ArgumentsObject::getLength(const Managed *m) +OwnPropertyKeyIterator *ArgumentsObject::virtualOwnPropertyKeys(const Object *m, Value *target) { - const ArgumentsObject *a = static_cast<const ArgumentsObject *>(m); - if (a->propertyData(Heap::ArgumentsObject::LengthPropertyIndex)->isInteger()) - return a->propertyData(Heap::ArgumentsObject::LengthPropertyIndex)->integerValue(); - return Primitive::toUInt32(a->propertyData(Heap::ArgumentsObject::LengthPropertyIndex)->doubleValue()); + static_cast<ArgumentsObject *>(const_cast<Object *>(m))->fullyCreate(); + return Object::virtualOwnPropertyKeys(m, target); } diff --git a/src/qml/jsruntime/qv4argumentsobject_p.h b/src/qml/jsruntime/qv4argumentsobject_p.h index 46e1f884e8..f0e2192c7e 100644 --- a/src/qml/jsruntime/qv4argumentsobject_p.h +++ b/src/qml/jsruntime/qv4argumentsobject_p.h @@ -59,70 +59,36 @@ namespace QV4 { namespace Heap { -#define ArgumentsGetterFunctionMembers(class, Member) \ - Member(class, NoMark, uint, index) - -DECLARE_HEAP_OBJECT(ArgumentsGetterFunction, FunctionObject) { - DECLARE_MARK_TABLE(ArgumentsGetterFunction); - inline void init(QV4::ExecutionContext *scope, uint index); -}; - -#define ArgumentsSetterFunctionMembers(class, Member) \ - Member(class, NoMark, uint, index) - -DECLARE_HEAP_OBJECT(ArgumentsSetterFunction, FunctionObject) { - DECLARE_MARK_TABLE(ArgumentsSetterFunction); - inline void init(QV4::ExecutionContext *scope, uint index); -}; - #define ArgumentsObjectMembers(class, Member) \ Member(class, Pointer, CallContext *, context) \ - Member(class, Pointer, MemberData *, mappedArguments) \ - Member(class, NoMark, bool, fullyCreated) + Member(class, NoMark, bool, fullyCreated) \ + Member(class, NoMark, uint, argCount) \ + Member(class, NoMark, quint64, mapped) DECLARE_HEAP_OBJECT(ArgumentsObject, Object) { - DECLARE_MARK_TABLE(ArgumentsObject); + DECLARE_MARKOBJECTS(ArgumentsObject); enum { LengthPropertyIndex = 0, - CalleePropertyIndex = 1, - CallerPropertyIndex = 3 + SymbolIteratorPropertyIndex = 1, + CalleePropertyIndex = 2 }; - void init(QV4::CallContext *context); + void init(CppStackFrame *frame); }; -} - -struct ArgumentsGetterFunction: FunctionObject -{ - V4_OBJECT2(ArgumentsGetterFunction, FunctionObject) - - uint index() const { return d()->index; } - static void call(const Managed *that, Scope &scope, CallData *d); -}; - -inline void -Heap::ArgumentsGetterFunction::init(QV4::ExecutionContext *scope, uint index) -{ - Heap::FunctionObject::init(scope); - this->index = index; -} - -struct ArgumentsSetterFunction: FunctionObject -{ - V4_OBJECT2(ArgumentsSetterFunction, FunctionObject) +#define StrictArgumentsObjectMembers(class, Member) - uint index() const { return d()->index; } - static void call(const Managed *that, Scope &scope, CallData *callData); +DECLARE_HEAP_OBJECT(StrictArgumentsObject, Object) { + enum { + LengthPropertyIndex = 0, + SymbolIteratorPropertyIndex = 1, + CalleePropertyIndex = 2, + CalleeSetterPropertyIndex = 3 + }; + void init(CppStackFrame *frame); }; -inline void -Heap::ArgumentsSetterFunction::init(QV4::ExecutionContext *scope, uint index) -{ - Heap::FunctionObject::init(scope); - this->index = index; } - struct ArgumentsObject: Object { V4_OBJECT2(ArgumentsObject, Object) Q_MANAGED_TYPE(ArgumentsObject) @@ -131,19 +97,35 @@ struct ArgumentsObject: Object { bool fullyCreated() const { return d()->fullyCreated; } static bool isNonStrictArgumentsObject(Managed *m) { - return m->d()->vtable()->type == Type_ArgumentsObject && - !static_cast<ArgumentsObject *>(m)->context()->strictMode; + return m->vtable() == staticVTable(); } - bool defineOwnProperty(ExecutionEngine *engine, uint index, const Property *desc, PropertyAttributes attrs); - static ReturnedValue getIndexed(const Managed *m, uint index, bool *hasProperty); - static bool putIndexed(Managed *m, uint index, const Value &value); - static bool deleteIndexedProperty(Managed *m, uint index); - static PropertyAttributes queryIndexed(const Managed *m, uint index); - static uint getLength(const Managed *m); + static bool virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *desc, PropertyAttributes attrs); + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); + static bool virtualDeleteProperty(Managed *m, PropertyKey id); + static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); + static qint64 virtualGetLength(const Managed *m); + static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); void fullyCreate(); + // There's a slight hack here, as this limits the amount of mapped arguments to 64, but that should be + // more than enough for all practical uses of arguments + bool isMapped(uint arg) const { + return arg < 64 && (d()->mapped & (1ull << arg)); + } + + void removeMapping(uint arg) { + if (arg < 64) + (d()->mapped &= ~(1ull << arg)); + } + +}; + +struct StrictArgumentsObject : Object { + V4_OBJECT2(StrictArgumentsObject, Object) + Q_MANAGED_TYPE(ArgumentsObject) }; } diff --git a/src/qml/jsruntime/qv4arraybuffer.cpp b/src/qml/jsruntime/qv4arraybuffer.cpp index ffe9aa846f..a99ec16943 100644 --- a/src/qml/jsruntime/qv4arraybuffer.cpp +++ b/src/qml/jsruntime/qv4arraybuffer.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQml module of the Qt Toolkit. @@ -40,86 +40,115 @@ #include "qv4typedarray_p.h" #include "qv4dataview_p.h" #include "qv4string_p.h" +#include "qv4jscall_p.h" +#include "qv4symbol_p.h" using namespace QV4; +DEFINE_OBJECT_VTABLE(SharedArrayBufferCtor); DEFINE_OBJECT_VTABLE(ArrayBufferCtor); +DEFINE_OBJECT_VTABLE(SharedArrayBuffer); DEFINE_OBJECT_VTABLE(ArrayBuffer); +void Heap::SharedArrayBufferCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("SharedArrayBuffer")); +} + void Heap::ArrayBufferCtor::init(QV4::ExecutionContext *scope) { Heap::FunctionObject::init(scope, QStringLiteral("ArrayBuffer")); } -void ArrayBufferCtor::construct(const Managed *m, Scope &scope, CallData *callData) +ReturnedValue SharedArrayBufferCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + Scope scope(f); + if (newTarget->isUndefined()) + return scope.engine->throwTypeError(); + + qint64 len = argc ? argv[0].toIndex() : 0; + if (scope.engine->hasException) + return Encode::undefined(); + if (len < 0 || len >= INT_MAX) + return scope.engine->throwRangeError(QStringLiteral("SharedArrayBuffer: Invalid length.")); + + Scoped<SharedArrayBuffer> a(scope, scope.engine->memoryManager->allocate<SharedArrayBuffer>(len)); + if (scope.engine->hasException) + return Encode::undefined(); + + return a->asReturnedValue(); +} + +ReturnedValue SharedArrayBufferCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) { - ExecutionEngine *v4 = static_cast<const Object *>(m)->engine(); + return f->engine()->throwTypeError(); +} + - ScopedValue l(scope, callData->argument(0)); +ReturnedValue ArrayBufferCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + ExecutionEngine *v4 = f->engine(); + Scope scope(v4); + + ScopedValue l(scope, argc ? argv[0] : Value::undefinedValue()); double dl = l->toInteger(); - if (v4->hasException) { - scope.result = Encode::undefined(); - return; - } + if (v4->hasException) + return Encode::undefined(); uint len = (uint)qBound(0., dl, (double)UINT_MAX); - if (len != dl) { - scope.result = v4->throwRangeError(QLatin1String("ArrayBuffer constructor: invalid length")); - return; - } + if (len != dl) + return v4->throwRangeError(QLatin1String("ArrayBuffer constructor: invalid length")); Scoped<ArrayBuffer> a(scope, v4->newArrayBuffer(len)); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - } else { - scope.result = a->asReturnedValue(); + if (newTarget->heapObject() != f->heapObject() && newTarget->isFunctionObject()) { + const FunctionObject *nt = static_cast<const FunctionObject *>(newTarget); + ScopedObject o(scope, nt->protoProperty()); + if (o) + a->setPrototypeOf(o); } -} + if (scope.engine->hasException) + return Encode::undefined(); - -void ArrayBufferCtor::call(const Managed *that, Scope &scope, CallData *callData) -{ - construct(that, scope, callData); + return a->asReturnedValue(); } -void ArrayBufferCtor::method_isView(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayBufferCtor::method_isView(const FunctionObject *, const Value *, const Value *argv, int argc) { - QV4::Scoped<TypedArray> a(scope, callData->argument(0)); - if (!!a) { - scope.result = Encode(true); - return; - } - QV4::Scoped<DataView> v(scope, callData->argument(0)); - if (!!v) { - scope.result = Encode(true); - return; - } - scope.result = Encode(false); + if (argc < 1) + return Encode(false); + + if (argv[0].as<TypedArray>() || + argv[0].as<DataView>()) + return Encode(true); + + return Encode(false); } -void Heap::ArrayBuffer::init(size_t length) +void Heap::SharedArrayBuffer::init(size_t length) { Object::init(); - data = QTypedArrayData<char>::allocate(length + 1); + if (length < UINT_MAX) + data = QTypedArrayData<char>::allocate(length + 1); if (!data) { - data = 0; internalClass->engine->throwRangeError(QStringLiteral("ArrayBuffer: out of memory")); return; } data->size = int(length); memset(data->data(), 0, length + 1); + isShared = true; } -void Heap::ArrayBuffer::init(const QByteArray& array) +void Heap::SharedArrayBuffer::init(const QByteArray& array) { Object::init(); data = const_cast<QByteArray&>(array).data_ptr(); data->ref.ref(); + isShared = true; } -void Heap::ArrayBuffer::destroy() +void Heap::SharedArrayBuffer::destroy() { - if (!data->ref.deref()) + if (data && !data->ref.deref()) QTypedArrayData<char>::deallocate(data); Object::destroy(); } @@ -150,61 +179,105 @@ void ArrayBuffer::detach() { } -void ArrayBufferPrototype::init(ExecutionEngine *engine, Object *ctor) +void SharedArrayBufferPrototype::init(ExecutionEngine *engine, Object *ctor) { Scope scope(engine); ScopedObject o(scope); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); - ctor->defineDefaultProperty(QStringLiteral("isView"), ArrayBufferCtor::method_isView, 1); + ctor->addSymbolSpecies(); + defineDefaultProperty(engine->id_constructor(), (o = ctor)); - defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, 0); + defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, nullptr); defineDefaultProperty(QStringLiteral("slice"), method_slice, 2); - defineDefaultProperty(QStringLiteral("toString"), method_toString, 0); + ScopedString name(scope, engine->newString(QStringLiteral("SharedArrayBuffer"))); + defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), name); } -void ArrayBufferPrototype::method_get_byteLength(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue SharedArrayBufferPrototype::method_get_byteLength(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<ArrayBuffer> v(scope, callData->thisObject); - if (!v) - THROW_TYPE_ERROR(); + const SharedArrayBuffer *a = thisObject->as<SharedArrayBuffer>(); + if (!a || a->isDetachedBuffer() || !a->isSharedArrayBuffer()) + return b->engine()->throwTypeError(); - scope.result = Encode(v->d()->data->size); + return Encode(a->d()->data->size); } -void ArrayBufferPrototype::method_slice(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue SharedArrayBufferPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<ArrayBuffer> a(scope, callData->thisObject); - if (!a) - THROW_TYPE_ERROR(); + return slice(b, thisObject, argv, argc, true); +} + +ReturnedValue SharedArrayBufferPrototype::slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc, bool shared) +{ + Scope scope(b); + const SharedArrayBuffer *a = thisObject->as<SharedArrayBuffer>(); + if (!a || a->isDetachedBuffer() || (a->isSharedArrayBuffer() != shared)) + return scope.engine->throwTypeError(); - double start = callData->argc > 0 ? callData->args[0].toInteger() : 0; - double end = (callData->argc < 2 || callData->args[1].isUndefined()) ? - a->d()->data->size : callData->args[1].toInteger(); - CHECK_EXCEPTION(); + double start = argc > 0 ? argv[0].toInteger() : 0; + double end = (argc < 2 || argv[1].isUndefined()) ? + a->d()->data->size : argv[1].toInteger(); + if (scope.hasException()) + return QV4::Encode::undefined(); double first = (start < 0) ? qMax(a->d()->data->size + start, 0.) : qMin(start, (double)a->d()->data->size); double final = (end < 0) ? qMax(a->d()->data->size + end, 0.) : qMin(end, (double)a->d()->data->size); - ScopedFunctionObject constructor(scope, a->get(scope.engine->id_constructor())); + const FunctionObject *constructor = a->speciesConstructor(scope, shared ? scope.engine->sharedArrayBufferCtor() : scope.engine->arrayBufferCtor()); if (!constructor) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); - ScopedCallData cData(scope, 1); double newLen = qMax(final - first, 0.); - cData->args[0] = QV4::Encode(newLen); - constructor->construct(scope, cData); - QV4::Scoped<ArrayBuffer> newBuffer(scope, scope.result); - if (!newBuffer || newBuffer->d()->data->size < (int)newLen) - THROW_TYPE_ERROR(); + ScopedValue argument(scope, QV4::Encode(newLen)); + QV4::Scoped<SharedArrayBuffer> newBuffer(scope, constructor->callAsConstructor(argument, 1)); + if (!newBuffer || newBuffer->d()->data->size < (int)newLen || + newBuffer->isDetachedBuffer() || (newBuffer->isSharedArrayBuffer() != shared) || + newBuffer->sameValue(*a) || + a->isDetachedBuffer()) + return scope.engine->throwTypeError(); memcpy(newBuffer->d()->data->data(), a->d()->data->data() + (uint)first, newLen); + return newBuffer->asReturnedValue(); +} + + +void ArrayBufferPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedObject o(scope); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); + ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); + ctor->defineDefaultProperty(QStringLiteral("isView"), ArrayBufferCtor::method_isView, 1); + ctor->addSymbolSpecies(); + + defineDefaultProperty(engine->id_constructor(), (o = ctor)); + defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, nullptr); + defineDefaultProperty(QStringLiteral("slice"), method_slice, 2); + defineDefaultProperty(QStringLiteral("toString"), method_toString, 0); + ScopedString name(scope, engine->newString(QStringLiteral("ArrayBuffer"))); + defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), name); +} + +ReturnedValue ArrayBufferPrototype::method_get_byteLength(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + const ArrayBuffer *a = thisObject->as<ArrayBuffer>(); + if (!a || a->isDetachedBuffer() || a->isSharedArrayBuffer()) + return f->engine()->throwTypeError(); + + return Encode(a->d()->data->size); +} + +ReturnedValue ArrayBufferPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + return slice(b, thisObject, argv, argc, false); } -void ArrayBufferPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayBufferPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<ArrayBuffer> a(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + const ArrayBuffer *a = thisObject->as<ArrayBuffer>(); if (!a) RETURN_UNDEFINED(); - scope.result = scope.engine->newString(QString::fromUtf8(a->asByteArray())); + return Encode(v4->newString(QString::fromUtf8(a->asByteArray()))); } diff --git a/src/qml/jsruntime/qv4arraybuffer_p.h b/src/qml/jsruntime/qv4arraybuffer_p.h index 4f7926d3dc..8344fa2554 100644 --- a/src/qml/jsruntime/qv4arraybuffer_p.h +++ b/src/qml/jsruntime/qv4arraybuffer_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQml module of the Qt Toolkit. @@ -59,54 +59,113 @@ namespace QV4 { namespace Heap { -struct ArrayBufferCtor : FunctionObject { +struct SharedArrayBufferCtor : FunctionObject { void init(QV4::ExecutionContext *scope); }; -struct Q_QML_PRIVATE_EXPORT ArrayBuffer : Object { +struct ArrayBufferCtor : SharedArrayBufferCtor { + void init(QV4::ExecutionContext *scope); +}; + +struct Q_QML_PRIVATE_EXPORT SharedArrayBuffer : Object { void init(size_t length); void init(const QByteArray& array); void destroy(); QTypedArrayData<char> *data; + bool isShared; + + uint byteLength() const { return data ? data->size : 0; } + + bool isDetachedBuffer() const { return !data; } + bool isSharedArrayBuffer() const { return isShared; } +}; - uint byteLength() const { return data->size; } +struct Q_QML_PRIVATE_EXPORT ArrayBuffer : SharedArrayBuffer { + void init(size_t length) { + SharedArrayBuffer::init(length); + isShared = false; + } + void init(const QByteArray& array) { + SharedArrayBuffer::init(array); + isShared = false; + } + void detachArrayBuffer() { + if (data && !data->ref.deref()) + QTypedArrayData<char>::deallocate(data); + data = nullptr; + } }; + } -struct ArrayBufferCtor: FunctionObject +struct SharedArrayBufferCtor : FunctionObject +{ + V4_OBJECT2(SharedArrayBufferCtor, FunctionObject) + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct ArrayBufferCtor : SharedArrayBufferCtor { - V4_OBJECT2(ArrayBufferCtor, FunctionObject) + V4_OBJECT2(ArrayBufferCtor, SharedArrayBufferCtor) + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); - static void construct(const Managed *m, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue method_isView(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + +struct Q_QML_PRIVATE_EXPORT SharedArrayBuffer : Object +{ + V4_OBJECT2(SharedArrayBuffer, Object) + V4_NEEDS_DESTROY + V4_PROTOTYPE(sharedArrayBufferPrototype) - static void method_isView(const BuiltinFunction *, Scope &scope, CallData *callData); + QByteArray asByteArray() const; + uint byteLength() const { return d()->byteLength(); } + char *data() { Q_ASSERT(d()->data); return d()->data->data(); } + const char *constData() { Q_ASSERT(d()->data); return d()->data->data(); } + bool isShared() { return d()->data->ref.isShared(); } + bool isDetachedBuffer() const { return !d()->data; } + bool isSharedArrayBuffer() const { return d()->isShared; } }; -struct Q_QML_PRIVATE_EXPORT ArrayBuffer : Object +struct Q_QML_PRIVATE_EXPORT ArrayBuffer : SharedArrayBuffer { - V4_OBJECT2(ArrayBuffer, Object) + V4_OBJECT2(ArrayBuffer, SharedArrayBuffer) V4_NEEDS_DESTROY V4_PROTOTYPE(arrayBufferPrototype) QByteArray asByteArray() const; uint byteLength() const { return d()->byteLength(); } - char *data() { detach(); return d()->data ? d()->data->data() : 0; } - const char *constData() { detach(); return d()->data ? d()->data->data() : 0; } + char *data() { detach(); return d()->data ? d()->data->data() : nullptr; } + // ### is that detach needed? + const char *constData() { detach(); return d()->data ? d()->data->data() : nullptr; } -private: + bool isShared() { return d()->data && d()->data->ref.isShared(); } void detach(); + void detachArrayBuffer() { d()->detachArrayBuffer(); } +}; + +struct SharedArrayBufferPrototype : Object +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_get_byteLength(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_slice(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc, bool shared); }; -struct ArrayBufferPrototype: Object +struct ArrayBufferPrototype : SharedArrayBufferPrototype { void init(ExecutionEngine *engine, Object *ctor); - static void method_get_byteLength(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_slice(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_get_byteLength(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_slice(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; diff --git a/src/qml/jsruntime/qv4arraydata.cpp b/src/qml/jsruntime/qv4arraydata.cpp index df9884d84a..654d33b8d1 100644 --- a/src/qml/jsruntime/qv4arraydata.cpp +++ b/src/qml/jsruntime/qv4arraydata.cpp @@ -43,33 +43,15 @@ #include "qv4runtime_p.h" #include "qv4argumentsobject_p.h" #include "qv4string_p.h" +#include "qv4jscall_p.h" using namespace QV4; -QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_ON - -const QV4::VTable QV4::ArrayData::static_vtbl = { - 0, - 0, - 0, - 0, - QV4::ArrayData::IsExecutionContext, - QV4::ArrayData::IsString, - QV4::ArrayData::IsObject, - QV4::ArrayData::IsFunctionObject, - QV4::ArrayData::IsErrorObject, - QV4::ArrayData::IsArrayData, - 0, - QV4::ArrayData::MyType, - "ArrayData", - Q_VTABLE_FUNCTION(QV4::ArrayData, destroy), - 0, - isEqualTo -}; +DEFINE_MANAGED_VTABLE(ArrayData); const ArrayVTable SimpleArrayData::static_vtbl = { - DEFINE_MANAGED_VTABLE_INT(SimpleArrayData, 0), + DEFINE_MANAGED_VTABLE_INT(SimpleArrayData, nullptr), Heap::ArrayData::Simple, SimpleArrayData::reallocate, SimpleArrayData::get, @@ -85,7 +67,7 @@ const ArrayVTable SimpleArrayData::static_vtbl = const ArrayVTable SparseArrayData::static_vtbl = { - DEFINE_MANAGED_VTABLE_INT(SparseArrayData, 0), + DEFINE_MANAGED_VTABLE_INT(SparseArrayData, nullptr), Heap::ArrayData::Sparse, SparseArrayData::reallocate, SparseArrayData::get, @@ -99,18 +81,16 @@ const ArrayVTable SparseArrayData::static_vtbl = SparseArrayData::length }; -QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_OFF - Q_STATIC_ASSERT(sizeof(Heap::ArrayData) == sizeof(Heap::SimpleArrayData)); Q_STATIC_ASSERT(sizeof(Heap::ArrayData) == sizeof(Heap::SparseArrayData)); -static Q_ALWAYS_INLINE void storeValue(ReturnedValue *target, uint value) +void Heap::ArrayData::markObjects(Heap::Base *base, MarkStack *stack) { - Value v; - v.setEmpty(value); - *target = v.asReturnedValue(); + ArrayData *a = static_cast<ArrayData *>(base); + a->values.mark(stack); } + void ArrayData::realloc(Object *o, Type newType, uint requested, bool enforceAttributes) { Scope scope(o->engine()); @@ -138,8 +118,6 @@ void ArrayData::realloc(Object *o, Type newType, uint requested, bool enforceAtt if (d->type() > newType) newType = d->type(); } - if (enforceAttributes && newType == Heap::ArrayData::Simple) - newType = Heap::ArrayData::Complex; while (alloc < requested) alloc *= 2; @@ -161,7 +139,7 @@ void ArrayData::realloc(Object *o, Type newType, uint requested, bool enforceAtt } newData->setAlloc(alloc); newData->setType(newType); - newData->setAttrs(enforceAttributes ? reinterpret_cast<PropertyAttributes *>(newData->d()->values.values + alloc) : 0); + newData->setAttrs(enforceAttributes ? reinterpret_cast<PropertyAttributes *>(newData->d()->values.values + alloc) : nullptr); o->setArrayData(newData); if (d) { @@ -188,39 +166,38 @@ void ArrayData::realloc(Object *o, Type newType, uint requested, bool enforceAtt Heap::SparseArrayData *sparse = static_cast<Heap::SparseArrayData *>(newData->d()); - ReturnedValue *lastFree; + Value *lastFree; if (d && d->type() == Heap::ArrayData::Sparse) { Heap::SparseArrayData *old = static_cast<Heap::SparseArrayData *>(d->d()); sparse->sparse = old->sparse; - old->sparse = 0; - sparse->freeList = old->freeList; - lastFree = &sparse->freeList; + old->sparse = nullptr; + lastFree = &sparse->sparse->freeList; } else { sparse->sparse = new SparseArray; - lastFree = &sparse->freeList; - storeValue(lastFree, 0); + lastFree = &sparse->sparse->freeList; + *lastFree = Encode(0); for (uint i = 0; i < toCopy; ++i) { if (!sparse->values[i].isEmpty()) { SparseArrayNode *n = sparse->sparse->insert(i); n->value = i; } else { - storeValue(lastFree, i); + *lastFree = Encode(i); sparse->values.values[i].setEmpty(); - lastFree = &sparse->values.values[i].rawValueRef(); + lastFree = &sparse->values.values[i]; } } } if (toCopy < sparse->values.alloc) { for (uint i = toCopy; i < sparse->values.alloc; ++i) { - storeValue(lastFree, i); + *lastFree = Encode(i); sparse->values.values[i].setEmpty(); - lastFree = &sparse->values.values[i].rawValueRef(); + lastFree = &sparse->values.values[i]; } - storeValue(lastFree, UINT_MAX); } + *lastFree = Encode(-1); - Q_ASSERT(Value::fromReturnedValue(sparse->freeList).isEmpty()); + Q_ASSERT(sparse->sparse->freeList.isInteger()); // ### Could explicitly free the old data } @@ -242,7 +219,7 @@ ReturnedValue SimpleArrayData::get(const Heap::ArrayData *d, uint index) { const Heap::SimpleArrayData *dd = static_cast<const Heap::SimpleArrayData *>(d); if (index >= dd->values.size) - return Primitive::emptyValue().asReturnedValue(); + return Value::emptyValue().asReturnedValue(); return dd->data(index).asReturnedValue(); } @@ -267,7 +244,7 @@ bool SimpleArrayData::del(Object *o, uint index) return true; if (!dd->attrs || dd->attrs[index].isConfigurable()) { - dd->setData(o->engine(), index, Primitive::emptyValue()); + dd->setData(o->engine(), index, Value::emptyValue()); if (dd->attrs) dd->attrs[index] = Attr_Data; return true; @@ -349,7 +326,7 @@ bool SimpleArrayData::putArray(Object *o, uint index, const Value *values, uint } QV4::ExecutionEngine *e = o->engine(); for (uint i = dd->values.size; i < index; ++i) - dd->setData(e, i, Primitive::emptyValue()); + dd->setData(e, i, Value::emptyValue()); for (uint i = 0; i < n; ++i) dd->setData(e, index + i, values[i]); dd->values.size = qMax(dd->values.size, index + n); @@ -362,12 +339,12 @@ void SparseArrayData::free(Heap::ArrayData *d, uint idx) Value *v = d->values.values + idx; if (d->attrs && d->attrs[idx].isAccessor()) { // double slot, free both. Order is important, so we have a double slot for allocation again afterwards. - v[1].setEmpty(Value::fromReturnedValue(d->freeList).emptyValue()); - v[0].setEmpty(idx + 1); + v[1] = d->sparse->freeList; + v[0] = Encode(idx + 1); } else { - v->setEmpty(Value::fromReturnedValue(d->freeList).emptyValue()); + *v = d->sparse->freeList; } - d->freeList = Primitive::emptyValue(idx).asReturnedValue(); + d->sparse->freeList = Encode(idx); if (d->attrs) d->attrs[idx].clear(); } @@ -384,36 +361,34 @@ uint SparseArrayData::allocate(Object *o, bool doubleSlot) Q_ASSERT(o->d()->arrayData->type == Heap::ArrayData::Sparse); Heap::SimpleArrayData *dd = o->d()->arrayData.cast<Heap::SimpleArrayData>(); if (doubleSlot) { - ReturnedValue *last = &dd->freeList; + Value *last = &dd->sparse->freeList; while (1) { - if (Value::fromReturnedValue(*last).value() == UINT_MAX) { + if (last->int_32() == -1) { reallocate(o, dd->values.alloc + 2, true); dd = o->d()->arrayData.cast<Heap::SimpleArrayData>(); - last = &dd->freeList; - Q_ASSERT(Value::fromReturnedValue(*last).value() != UINT_MAX); + last = &dd->sparse->freeList; + Q_ASSERT(last->int_32() != -1); } - Q_ASSERT(dd->values[Value::fromReturnedValue(*last).value()].value() != Value::fromReturnedValue(*last).value()); - if (dd->values[Value::fromReturnedValue(*last).value()].value() == (Value::fromReturnedValue(*last).value() + 1)) { + Q_ASSERT(dd->values[static_cast<uint>(last->int_32())].int_32() != last->int_32()); + if (dd->values[static_cast<uint>(last->int_32())].int_32() == last->int_32() + 1) { // found two slots in a row - uint idx = Value::fromReturnedValue(*last).emptyValue(); - Value lastV = Value::fromReturnedValue(*last); - lastV.setEmpty(dd->values[lastV.emptyValue() + 1].value()); - *last = lastV.rawValue(); + uint idx = static_cast<uint>(last->int_32()); + *last = Encode(dd->values[static_cast<uint>(last->int_32()) + 1].int_32()); dd->attrs[idx] = Attr_Accessor; return idx; } - last = &dd->values.values[Value::fromReturnedValue(*last).value()].rawValueRef(); + last = &dd->values.values[last->int_32()]; } } else { - if (Value::fromReturnedValue(dd->freeList).value() == UINT_MAX) { + if (dd->sparse->freeList.int_32() == -1) { reallocate(o, dd->values.alloc + 1, false); dd = o->d()->arrayData.cast<Heap::SimpleArrayData>(); } - uint idx = Value::fromReturnedValue(dd->freeList).value(); - Q_ASSERT(idx != UINT_MAX); - dd->freeList = dd->values[idx].asReturnedValue(); - Q_ASSERT(Value::fromReturnedValue(dd->freeList).isEmpty()); + Q_ASSERT(dd->sparse->freeList.int_32() != -1); + uint idx = static_cast<uint>(dd->sparse->freeList.int_32()); + dd->sparse->freeList = dd->values[idx]; + Q_ASSERT(dd->sparse->freeList.isInteger()); if (dd->attrs) dd->attrs[idx] = Attr_Data; return idx; @@ -425,7 +400,7 @@ ReturnedValue SparseArrayData::get(const Heap::ArrayData *d, uint index) const Heap::SparseArrayData *s = static_cast<const Heap::SparseArrayData *>(d); index = s->mappedIndex(index); if (index == UINT_MAX) - return Primitive::emptyValue().asReturnedValue(); + return Value::emptyValue().asReturnedValue(); return s->values[index].asReturnedValue(); } @@ -468,14 +443,14 @@ bool SparseArrayData::del(Object *o, uint index) if (isAccessor) { // free up both indices - dd->values.values[pidx + 1].setEmpty(Value::fromReturnedValue(dd->freeList).emptyValue()); - dd->values.values[pidx].setEmpty(pidx + 1); + dd->values.values[pidx + 1] = dd->sparse->freeList; + dd->values.values[pidx] = Encode(pidx + 1); } else { Q_ASSERT(dd->type == Heap::ArrayData::Sparse); - dd->values.values[pidx].setEmpty(Value::fromReturnedValue(dd->freeList).emptyValue()); + dd->values.values[pidx] = dd->sparse->freeList; } - dd->freeList = Primitive::emptyValue(pidx).asReturnedValue(); + dd->sparse->freeList = Encode(pidx); dd->sparse->erase(n); return true; } @@ -587,7 +562,7 @@ uint ArrayData::append(Object *obj, ArrayObject *otherObj, uint n) if (!other || ArgumentsObject::isNonStrictArgumentsObject(otherObj)) { ScopedValue v(scope); for (uint i = 0; i < n; ++i) - obj->arraySet(oldSize + i, (v = otherObj->getIndexed(i))); + obj->arraySet(oldSize + i, (v = otherObj->get(i))); } else if (other && other->isSparse()) { Heap::SparseArrayData *os = static_cast<Heap::SparseArrayData *>(other->d()); if (other->hasAttributes()) { @@ -607,7 +582,7 @@ uint ArrayData::append(Object *obj, ArrayObject *otherObj, uint n) uint toCopy = n; uint chunk = toCopy; if (chunk > os->values.alloc - os->offset) - chunk -= os->values.alloc - os->offset; + chunk = os->values.alloc - os->offset; obj->arrayPut(oldSize, os->values.data() + os->offset, chunk); toCopy -= chunk; if (toCopy) @@ -629,7 +604,7 @@ void ArrayData::insert(Object *o, uint index, const Value *v, bool isAccessor) if (index >= d->values.size) { // mark possible hole in the array for (uint i = d->values.size; i < index; ++i) - d->setData(o->engine(), i, Primitive::emptyValue()); + d->setData(o->engine(), i, Value::emptyValue()); d->values.size = index + 1; } d->setData(o->engine(), index, *v); @@ -652,14 +627,13 @@ void ArrayData::insert(Object *o, uint index, const Value *v, bool isAccessor) class ArrayElementLessThan { public: - inline ArrayElementLessThan(ExecutionEngine *engine, Object *thisObject, const Value &comparefn) - : m_engine(engine), thisObject(thisObject), m_comparefn(comparefn) {} + inline ArrayElementLessThan(ExecutionEngine *engine, const Value &comparefn) + : m_engine(engine), m_comparefn(comparefn) {} bool operator()(Value v1, Value v2) const; private: ExecutionEngine *m_engine; - Object *thisObject; const Value &m_comparefn; }; @@ -672,15 +646,14 @@ bool ArrayElementLessThan::operator()(Value v1, Value v2) const return false; if (v2.isUndefined() || v2.isEmpty()) return true; - ScopedObject o(scope, m_comparefn); + ScopedFunctionObject o(scope, m_comparefn); if (o) { Scope scope(o->engine()); ScopedValue result(scope); - ScopedCallData callData(scope, 2); - callData->thisObject = Primitive::undefinedValue(); - callData->args[0] = v1; - callData->args[1] = v2; - result = QV4::Runtime::method_callValue(scope.engine, m_comparefn, callData); + JSCallData jsCallData(scope, 2); + jsCallData->args[0] = v1; + jsCallData->args[1] = v2; + result = o->call(jsCallData); return result->toNumber() < 0; } @@ -754,7 +727,7 @@ void ArrayData::sort(ExecutionEngine *engine, Object *thisObject, const Value &c if (!arrayData || !arrayData->length()) return; - if (!(comparefn.isUndefined() || comparefn.as<Object>())) { + if (!comparefn.isUndefined() && !comparefn.isFunctionObject()) { engine->throwTypeError(); return; } @@ -770,7 +743,7 @@ void ArrayData::sort(ExecutionEngine *engine, Object *thisObject, const Value &c if (!sparse->sparse()->nEntries()) return; - thisObject->setArrayData(0); + thisObject->setArrayData(nullptr); ArrayData::realloc(thisObject, Heap::ArrayData::Simple, sparse->sparse()->nEntries(), sparse->attrs() ? true : false); Heap::SimpleArrayData *d = thisObject->d()->arrayData.cast<Heap::SimpleArrayData>(); @@ -824,7 +797,7 @@ void ArrayData::sort(ExecutionEngine *engine, Object *thisObject, const Value &c break; Q_ASSERT(!d->attrs || !d->attrs[len].isAccessor()); d->setData(engine, i, d->data(len)); - d->setData(engine, len, Primitive::emptyValue()); + d->setData(engine, len, Value::emptyValue()); } } @@ -833,7 +806,7 @@ void ArrayData::sort(ExecutionEngine *engine, Object *thisObject, const Value &c } - ArrayElementLessThan lessThan(engine, thisObject, comparefn); + ArrayElementLessThan lessThan(engine, static_cast<const FunctionObject &>(comparefn)); Value *begin = thisObject->arrayData()->values.values; sortHelper(begin, begin + len, *begin, lessThan); diff --git a/src/qml/jsruntime/qv4arraydata_p.h b/src/qml/jsruntime/qv4arraydata_p.h index 1d895c90c5..64b2161ef5 100644 --- a/src/qml/jsruntime/qv4arraydata_p.h +++ b/src/qml/jsruntime/qv4arraydata_p.h @@ -91,62 +91,50 @@ struct ArrayVTable namespace Heap { #define ArrayDataMembers(class, Member) \ - Member(class, NoMark, uint, type) \ + Member(class, NoMark, ushort, type) \ + Member(class, NoMark, ushort, unused) \ Member(class, NoMark, uint, offset) \ Member(class, NoMark, PropertyAttributes *, attrs) \ - Member(class, NoMark, ReturnedValue, freeList) \ Member(class, NoMark, SparseArray *, sparse) \ Member(class, ValueArray, ValueArray, values) DECLARE_HEAP_OBJECT(ArrayData, Base) { - DECLARE_MARK_TABLE(ArrayData); + static void markObjects(Heap::Base *base, MarkStack *stack); - enum Type { Simple = 0, Complex = 1, Sparse = 2, Custom = 3 }; - - struct Index { - Heap::ArrayData *arrayData; - uint index; - - void set(ExecutionEngine *e, Value newVal) { - arrayData->values.set(e, index, newVal); - } - const Value *operator->() const { return &arrayData->values[index]; } - const Value &operator*() const { return arrayData->values[index]; } - bool isNull() const { return !arrayData; } - }; + enum Type { Simple = 0, Sparse = 1, Custom = 2 }; bool isSparse() const { return type == Sparse; } - const ArrayVTable *vtable() const { return reinterpret_cast<const ArrayVTable *>(Base::vtable()); } + const ArrayVTable *vtable() const { return reinterpret_cast<const ArrayVTable *>(internalClass->vtable); } inline ReturnedValue get(uint i) const { return vtable()->get(this, i); } inline bool getProperty(uint index, Property *p, PropertyAttributes *attrs); - inline void setProperty(ExecutionEngine *e, uint index, const Property *p); - inline Index getValueOrSetter(uint index, PropertyAttributes *attrs); + inline void setProperty(EngineBase *e, uint index, const Property *p); + inline PropertyIndex getValueOrSetter(uint index, PropertyAttributes *attrs); inline PropertyAttributes attributes(uint i) const; bool isEmpty(uint i) const { - return get(i) == Primitive::emptyValue().asReturnedValue(); + return get(i) == Value::emptyValue().asReturnedValue(); } inline uint length() const { return vtable()->length(this); } - void setArrayData(ExecutionEngine *e, uint index, Value newVal) { + void setArrayData(EngineBase *e, uint index, Value newVal) { values.set(e, index, newVal); } uint mappedIndex(uint index) const; }; -V4_ASSERT_IS_TRIVIAL(ArrayData) +Q_STATIC_ASSERT(std::is_trivial< ArrayData >::value); struct SimpleArrayData : public ArrayData { - uint mappedIndex(uint index) const { return (index + offset) % values.alloc; } + uint mappedIndex(uint index) const { index += offset; if (index >= values.alloc) index -= values.alloc; return index; } const Value &data(uint index) const { return values[mappedIndex(index)]; } - void setData(ExecutionEngine *e, uint index, Value newVal) { + void setData(EngineBase *e, uint index, Value newVal) { values.set(e, mappedIndex(index), newVal); } @@ -154,7 +142,7 @@ struct SimpleArrayData : public ArrayData { return attrs ? attrs[i] : Attr_Data; } }; -V4_ASSERT_IS_TRIVIAL(SimpleArrayData) +Q_STATIC_ASSERT(std::is_trivial< SimpleArrayData >::value); struct SparseArrayData : public ArrayData { void destroy() { @@ -187,8 +175,6 @@ struct Q_QML_EXPORT ArrayData : public Managed IsArrayData = true }; - typedef Heap::ArrayData::Index Index; - uint alloc() const { return d()->values.alloc; } uint &alloc() { return d()->values.alloc; } void setAlloc(uint a) { d()->values.alloc = a; } @@ -197,7 +183,7 @@ struct Q_QML_EXPORT ArrayData : public Managed PropertyAttributes *attrs() const { return d()->attrs; } void setAttrs(PropertyAttributes *a) { d()->attrs = a; } const Value *arrayData() const { return d()->values.data(); } - void setArrayData(ExecutionEngine *e, uint index, Value newVal) { + void setArrayData(EngineBase *e, uint index, Value newVal) { d()->setArrayData(e, index, newVal); } @@ -261,8 +247,6 @@ struct Q_QML_EXPORT SparseArrayData : public ArrayData V4_INTERNALCLASS(SparseArrayData) V4_NEEDS_DESTROY - ReturnedValue &freeList() { return d()->freeList; } - ReturnedValue freeList() const { return d()->freeList; } SparseArray *sparse() const { return d()->sparse; } void setSparse(SparseArray *s) { d()->sparse = s; } @@ -304,13 +288,15 @@ bool ArrayData::getProperty(uint index, Property *p, PropertyAttributes *attrs) } *attrs = attributes(index); - p->value = *(Index{ this, mapped }); - if (attrs->isAccessor()) - p->set = *(Index{ this, mapped + 1 /*Object::SetterOffset*/ }); + if (p) { + p->value = *(PropertyIndex{ this, values.values + mapped }); + if (attrs->isAccessor()) + p->set = *(PropertyIndex{ this, values.values + mapped + 1 /*Object::SetterOffset*/ }); + } return true; } -void ArrayData::setProperty(QV4::ExecutionEngine *e, uint index, const Property *p) +void ArrayData::setProperty(QV4::EngineBase *e, uint index, const Property *p) { uint mapped = mappedIndex(index); Q_ASSERT(mapped != UINT_MAX); @@ -326,16 +312,18 @@ inline PropertyAttributes ArrayData::attributes(uint i) const return static_cast<const SimpleArrayData *>(this)->attributes(i); } -ArrayData::Index ArrayData::getValueOrSetter(uint index, PropertyAttributes *attrs) +PropertyIndex ArrayData::getValueOrSetter(uint index, PropertyAttributes *attrs) { uint idx = mappedIndex(index); if (idx == UINT_MAX) { *attrs = Attr_Invalid; - return { 0, 0 }; + return { nullptr, nullptr }; } *attrs = attributes(index); - return { this, attrs->isAccessor() ? idx + 1 /* QV4::Object::SetterOffset*/ : idx }; + if (attrs->isAccessor()) + ++idx; + return { this, values.values + idx }; } diff --git a/src/qml/jsruntime/qv4arrayiterator.cpp b/src/qml/jsruntime/qv4arrayiterator.cpp new file mode 100644 index 0000000000..199b1a728a --- /dev/null +++ b/src/qml/jsruntime/qv4arrayiterator.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Crimson AS <info@crimson.no> +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qv4iterator_p.h> +#include <private/qv4arrayiterator_p.h> +#include <private/qv4typedarray_p.h> +#include <private/qv4symbol_p.h> + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(ArrayIteratorObject); + +void ArrayIteratorPrototype::init(ExecutionEngine *e) +{ + defineDefaultProperty(QStringLiteral("next"), method_next, 0); + + Scope scope(e); + ScopedString val(scope, e->newString(QLatin1String("Array Iterator"))); + defineReadonlyConfigurableProperty(e->symbol_toStringTag(), val); +} + +ReturnedValue ArrayIteratorPrototype::method_next(const FunctionObject *b, const Value *that, const Value *, int) +{ + Scope scope(b); + const ArrayIteratorObject *thisObject = that->as<ArrayIteratorObject>(); + if (!thisObject) + return scope.engine->throwTypeError(QLatin1String("Not an Array Iterator instance")); + + ScopedObject a(scope, thisObject->d()->iteratedObject); + if (!a) { + QV4::Value undefined = Value::undefinedValue(); + return IteratorPrototype::createIterResultObject(scope.engine, undefined, true); + } + + quint32 index = thisObject->d()->nextIndex; + IteratorKind itemKind = thisObject->d()->iterationKind; + + Scoped<TypedArray> ta(scope, a->as<TypedArray>()); + quint32 len = a->getLength(); + + if (index >= len) { + thisObject->d()->iteratedObject.set(scope.engine, nullptr); + QV4::Value undefined = Value::undefinedValue(); + return IteratorPrototype::createIterResultObject(scope.engine, undefined, true); + } + + thisObject->d()->nextIndex = index + 1; + if (itemKind == KeyIteratorKind) { + return IteratorPrototype::createIterResultObject(scope.engine, Value::fromInt32(index), false); + } + + ReturnedValue elementValue = a->get(index); + CHECK_EXCEPTION(); + + if (itemKind == ValueIteratorKind) { + return IteratorPrototype::createIterResultObject(scope.engine, Value::fromReturnedValue(elementValue), false); + } else { + Q_ASSERT(itemKind == KeyValueIteratorKind); + + ScopedArrayObject resultArray(scope, scope.engine->newArrayObject()); + resultArray->arrayReserve(2); + resultArray->arrayPut(0, Value::fromInt32(index)); + resultArray->arrayPut(1, Value::fromReturnedValue(elementValue)); + resultArray->setArrayLengthUnchecked(2); + + return IteratorPrototype::createIterResultObject(scope.engine, resultArray, false); + } +} + diff --git a/src/qml/jsruntime/qv4arrayiterator_p.h b/src/qml/jsruntime/qv4arrayiterator_p.h new file mode 100644 index 0000000000..6d6bb466f1 --- /dev/null +++ b/src/qml/jsruntime/qv4arrayiterator_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Crimson AS <info@crimson.no> +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4ARRAYITERATOR_P_H +#define QV4ARRAYITERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4iterator_p.h" +#include "qv4arraydata_p.h" + +QT_BEGIN_NAMESPACE + + +namespace QV4 { + +namespace Heap { + +#define ArrayIteratorObjectMembers(class, Member) \ + Member(class, Pointer, Object *, iteratedObject) \ + Member(class, NoMark, IteratorKind, iterationKind) \ + Member(class, NoMark, quint32, nextIndex) + +DECLARE_HEAP_OBJECT(ArrayIteratorObject, Object) { + DECLARE_MARKOBJECTS(ArrayIteratorObject); + void init(Object *obj, QV4::ExecutionEngine *engine) + { + Object::init(); + this->iteratedObject.set(engine, obj); + this->nextIndex = 0; + } +}; + +} + +struct ArrayIteratorPrototype : Object +{ + V4_PROTOTYPE(iteratorPrototype) + void init(ExecutionEngine *engine); + + static ReturnedValue method_next(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); +}; + +struct ArrayIteratorObject : Object +{ + V4_OBJECT2(ArrayIteratorObject, Object) + Q_MANAGED_TYPE(ArrayIteratorObject) + V4_PROTOTYPE(arrayIteratorPrototype) + + void init(ExecutionEngine *engine); +}; + + +} + +QT_END_NAMESPACE + +#endif // QV4ARRAYITERATOR_P_H + diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp index a2c19e1f2d..b975d02d9d 100644 --- a/src/qml/jsruntime/qv4arrayobject.cpp +++ b/src/qml/jsruntime/qv4arrayobject.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2018 Crimson AS <info@crimson.no> ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** @@ -38,13 +39,17 @@ ****************************************************************************/ #include "qv4arrayobject_p.h" +#include "qv4objectiterator_p.h" +#include "qv4arrayiterator_p.h" #include "qv4sparsearray_p.h" #include "qv4objectproto_p.h" -#include "qv4scopedvalue_p.h" +#include "qv4jscall_p.h" #include "qv4argumentsobject_p.h" #include "qv4runtime_p.h" #include "qv4string_p.h" +#include "qv4symbol_p.h" #include <QtCore/qscopedvaluerollback.h> +#include "qv4proxy_p.h" using namespace QV4; @@ -55,51 +60,77 @@ void Heap::ArrayCtor::init(QV4::ExecutionContext *scope) Heap::FunctionObject::init(scope, QStringLiteral("Array")); } -void ArrayCtor::construct(const Managed *m, Scope &scope, CallData *callData) +ReturnedValue ArrayCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ExecutionEngine *v4 = static_cast<const ArrayCtor *>(m)->engine(); + ExecutionEngine *v4 = static_cast<const ArrayCtor *>(f)->engine(); + Scope scope(v4); ScopedArrayObject a(scope, v4->newArrayObject()); + if (newTarget) + a->setProtoFromNewTarget(newTarget); uint len; - if (callData->argc == 1 && callData->args[0].isNumber()) { + if (argc == 1 && argv[0].isNumber()) { bool ok; - len = callData->args[0].asArrayLength(&ok); + len = argv[0].asArrayLength(&ok); - if (!ok) { - scope.result = v4->throwRangeError(callData->args[0]); - return; - } + if (!ok) + return v4->throwRangeError(argv[0]); if (len < 0x1000) a->arrayReserve(len); } else { - len = callData->argc; + len = argc; a->arrayReserve(len); - a->arrayPut(0, callData->args, len); + a->arrayPut(0, argv, len); } a->setArrayLengthUnchecked(len); - scope.result = a.asReturnedValue(); + return a.asReturnedValue(); } -void ArrayCtor::call(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue ArrayCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc) { - construct(that, scope, callData); + return virtualCallAsConstructor(f, argv, argc, f); } void ArrayPrototype::init(ExecutionEngine *engine, Object *ctor) { Scope scope(engine); ScopedObject o(scope); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); ctor->defineDefaultProperty(QStringLiteral("isArray"), method_isArray, 1); + ctor->defineDefaultProperty(QStringLiteral("of"), method_of, 0); + ctor->defineDefaultProperty(QStringLiteral("from"), method_from, 1); + ctor->addSymbolSpecies(); + + ScopedObject unscopables(scope, engine->newObject(engine->classes[EngineBase::Class_Empty]->changeVTable(QV4::Object::staticVTable()))); + ScopedString name(scope); defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); defineDefaultProperty(engine->id_toString(), method_toString, 0); - defineDefaultProperty(QStringLiteral("toLocaleString"), method_toLocaleString, 0); + defineDefaultProperty(engine->id_toLocaleString(), method_toLocaleString, 0); defineDefaultProperty(QStringLiteral("concat"), method_concat, 1); - defineDefaultProperty(QStringLiteral("find"), method_find, 1); - defineDefaultProperty(QStringLiteral("findIndex"), method_findIndex, 1); + name = engine->newIdentifier(QStringLiteral("copyWithin")); + unscopables->put(name, Value::fromBoolean(true)); + defineDefaultProperty(name, method_copyWithin, 2); + name = engine->newIdentifier(QStringLiteral("entries")); + unscopables->put(name, Value::fromBoolean(true)); + defineDefaultProperty(name, method_entries, 0); + name = engine->newIdentifier(QStringLiteral("fill")); + unscopables->put(name, Value::fromBoolean(true)); + defineDefaultProperty(name, method_fill, 1); + name = engine->newIdentifier(QStringLiteral("find")); + unscopables->put(name, Value::fromBoolean(true)); + defineDefaultProperty(name, method_find, 1); + name = engine->newIdentifier(QStringLiteral("findIndex")); + unscopables->put(name, Value::fromBoolean(true)); + defineDefaultProperty(name, method_findIndex, 1); + name = engine->newIdentifier(QStringLiteral("includes")); + unscopables->put(name, Value::fromBoolean(true)); + defineDefaultProperty(name, method_includes, 1); defineDefaultProperty(QStringLiteral("join"), method_join, 1); + name = engine->newIdentifier(QStringLiteral("keys")); + unscopables->put(name, Value::fromBoolean(true)); + defineDefaultProperty(name, method_keys, 0); defineDefaultProperty(QStringLiteral("pop"), method_pop, 0); defineDefaultProperty(QStringLiteral("push"), method_push, 1); defineDefaultProperty(QStringLiteral("reverse"), method_reverse, 0); @@ -117,149 +148,461 @@ void ArrayPrototype::init(ExecutionEngine *engine, Object *ctor) defineDefaultProperty(QStringLiteral("filter"), method_filter, 1); defineDefaultProperty(QStringLiteral("reduce"), method_reduce, 1); defineDefaultProperty(QStringLiteral("reduceRight"), method_reduceRight, 1); + ScopedString valuesString(scope, engine->newIdentifier(QStringLiteral("values"))); + ScopedObject values(scope, FunctionObject::createBuiltinFunction(engine, valuesString, method_values, 0)); + engine->jsObjects[ExecutionEngine::ArrayProtoValues] = values; + unscopables->put(valuesString, Value::fromBoolean(true)); + defineDefaultProperty(valuesString, values); + defineDefaultProperty(engine->symbol_iterator(), values); + + defineReadonlyConfigurableProperty(engine->symbol_unscopables(), unscopables); +} + +ReturnedValue ArrayPrototype::method_isArray(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + if (!argc || !argv->objectValue()) + return Encode(false); + return Encode(argv->objectValue()->isArray()); +} + +static ScopedObject createObjectFromCtorOrArray(Scope &scope, ScopedFunctionObject ctor, bool useLen, int len) +{ + ScopedObject a(scope, Value::undefinedValue()); + + if (ctor && ctor->isConstructor()) { + // this isn't completely kosher. for instance: + // Array.from.call(Object, []).constructor == Object + // is expected by the tests, but naturally, we get Number. + ScopedValue argument(scope, useLen ? QV4::Encode(len) : Value::undefinedValue()); + a = ctor->callAsConstructor(argument, useLen ? 1 : 0); + } else { + a = scope.engine->newArrayObject(len); + } + + return a; } -void ArrayPrototype::method_isArray(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_from(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) { - bool isArray = callData->argc && callData->args[0].as<ArrayObject>(); - scope.result = Encode(isArray); + Scope scope(builtin); + ScopedFunctionObject thatCtor(scope, thisObject); + ScopedObject itemsObject(scope, argv[0]); + bool usingIterator = false; + + if (itemsObject) { + // If the object claims to support iterators, then let's try use them. + ScopedValue it(scope, itemsObject->get(scope.engine->symbol_iterator())); + if (!it->isNullOrUndefined()) { + ScopedFunctionObject itfunc(scope, it); + if (!itfunc) + return scope.engine->throwTypeError(); + usingIterator = true; + } + } + + ScopedFunctionObject mapfn(scope, Value::undefinedValue()); + Value *mapArguments = nullptr; + if (argc > 1) { + mapfn = ScopedFunctionObject(scope, argv[1]); + if (!mapfn) + return scope.engine->throwTypeError(QString::fromLatin1("%1 is not a function").arg(argv[1].toQStringNoThrow())); + mapArguments = scope.alloc(2); + } + + ScopedValue thisArg(scope); + if (argc > 2) + thisArg = argv[2]; + + if (usingIterator) { + // Item iteration supported, so let's go ahead and try use that. + ScopedObject a(createObjectFromCtorOrArray(scope, thatCtor, false, 0)); + CHECK_EXCEPTION(); + ScopedObject iterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true)); + CHECK_EXCEPTION(); // symbol_iterator threw; whoops. + if (!iterator) { + return scope.engine->throwTypeError(); // symbol_iterator wasn't an object. + } + + qint64 k = 0; + ScopedValue mappedValue(scope); + Value *nextValue = scope.alloc(1); + ScopedValue done(scope); + + // The loop below pulls out all the properties using the iterator, and + // sets them into the created array. + forever { + if (k > (static_cast<qint64>(1) << 53) - 1) { + ScopedValue falsey(scope, Encode(false)); + ScopedValue error(scope, scope.engine->throwTypeError()); + return Runtime::IteratorClose::call(scope.engine, iterator, falsey); + } + + // Retrieve the next value. If the iteration ends, we're done here. + done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, iterator, nextValue)); + CHECK_EXCEPTION(); + if (done->toBoolean()) { + if (ArrayObject *ao = a->as<ArrayObject>()) { + ao->setArrayLengthUnchecked(k); + } else { + a->set(scope.engine->id_length(), Value::fromDouble(k), QV4::Object::DoThrowOnRejection); + CHECK_EXCEPTION(); + } + return a.asReturnedValue(); + } + + if (mapfn) { + mapArguments[0] = *nextValue; + mapArguments[1] = Value::fromDouble(k); + mappedValue = mapfn->call(thisArg, mapArguments, 2); + if (scope.engine->hasException) + return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(false)); + } else { + mappedValue = *nextValue; + } + + if (a->getOwnProperty(PropertyKey::fromArrayIndex(k)) == Attr_Invalid) { + a->arraySet(k, mappedValue); + } else { + // Don't return: we need to close the iterator. + scope.engine->throwTypeError(QString::fromLatin1("Cannot redefine property: %1").arg(k)); + } + + if (scope.engine->hasException) { + ScopedValue falsey(scope, Encode(false)); + return Runtime::IteratorClose::call(scope.engine, iterator, falsey); + } + + k++; + } + + // the return is hidden up in the loop above, when iteration finishes. + } else { + // Array-like fallback. We request properties by index, and set them on + // the return object. + ScopedObject arrayLike(scope, argv[0].toObject(scope.engine)); + if (!arrayLike) + return scope.engine->throwTypeError(QString::fromLatin1("Cannot convert %1 to object").arg(argv[0].toQStringNoThrow())); + qint64 len = arrayLike->getLength(); + ScopedObject a(createObjectFromCtorOrArray(scope, thatCtor, true, len)); + CHECK_EXCEPTION(); + + qint64 k = 0; + ScopedValue mappedValue(scope, Value::undefinedValue()); + ScopedValue kValue(scope); + while (k < len) { + kValue = arrayLike->get(k); + CHECK_EXCEPTION(); + + if (mapfn) { + mapArguments[0] = kValue; + mapArguments[1] = Value::fromDouble(k); + mappedValue = mapfn->call(thisArg, mapArguments, 2); + CHECK_EXCEPTION(); + } else { + mappedValue = kValue; + } + + if (a->getOwnProperty(PropertyKey::fromArrayIndex(k)) != Attr_Invalid) + return scope.engine->throwTypeError(QString::fromLatin1("Cannot redefine property: %1").arg(k)); + + a->arraySet(k, mappedValue); + CHECK_EXCEPTION(); + + k++; + } + + if (ArrayObject *ao = a->as<ArrayObject>()) { + ao->setArrayLengthUnchecked(k); + } else { + a->set(scope.engine->id_length(), Value::fromDouble(k), QV4::Object::DoThrowOnRejection); + CHECK_EXCEPTION(); + } + return a.asReturnedValue(); + } + } -void ArrayPrototype::method_toString(const BuiltinFunction *builtin, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_of(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) { - ScopedObject o(scope, callData->thisObject, ScopedObject::Convert); + Scope scope(builtin); + ScopedFunctionObject that(scope, thisObject); + ScopedObject a(createObjectFromCtorOrArray(scope, that, true, argc)); CHECK_EXCEPTION(); - ScopedString s(scope, scope.engine->newString(QStringLiteral("join"))); - ScopedFunctionObject f(scope, o->get(s)); - if (!!f) { - ScopedCallData d(scope, 0); - d->thisObject = callData->thisObject; - f->call(scope, d); - return; + + int k = 0; + while (k < argc) { + if (a->getOwnProperty(PropertyKey::fromArrayIndex(k)) != Attr_Invalid) { + return scope.engine->throwTypeError(QString::fromLatin1("Cannot redefine property: %1").arg(k)); + } + a->arraySet(k, argv[k]); + CHECK_EXCEPTION(); + + k++; } - ObjectPrototype::method_toString(builtin, scope, callData); + + // ArrayObject updates its own length, and will throw if we try touch it. + if (!a->as<ArrayObject>()) { + a->set(scope.engine->id_length(), Value::fromDouble(argc), QV4::Object::DoThrowOnRejection); + CHECK_EXCEPTION(); + } + + return a.asReturnedValue(); } -void ArrayPrototype::method_toLocaleString(const BuiltinFunction *builtin, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_toString(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) { - return method_toString(builtin, scope, callData); + Scope scope(builtin); + ScopedObject that(scope, thisObject->toObject(scope.engine)); + if (scope.hasException()) + return QV4::Encode::undefined(); + + ScopedString string(scope, scope.engine->newString(QStringLiteral("join"))); + ScopedFunctionObject f(scope, that->get(string)); + if (f) + return f->call(that, argv, argc); + return ObjectPrototype::method_toString(builtin, that, argv, argc); +} + +ReturnedValue ArrayPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + ScopedObject instance(scope, thisObject); + if (!instance) + return scope.engine->throwTypeError(); + + uint len = instance->getLength(); + const QString separator = QStringLiteral(","); + + QString R; + + ScopedValue v(scope); + ScopedString s(scope); + + for (uint k = 0; k < len; ++k) { + if (k) + R += separator; + + v = instance->get(k); + if (v->isNullOrUndefined()) + continue; + v = Runtime::CallElement::call(scope.engine, v, *scope.engine->id_toLocaleString(), nullptr, 0); + s = v->toString(scope.engine); + if (scope.hasException()) + return Encode::undefined(); + + R += s->toQString(); + } + return scope.engine->newString(R)->asReturnedValue(); } -void ArrayPrototype::method_concat(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_concat(const FunctionObject *b, const Value *that, const Value *argv, int argc) { - ScopedObject thisObject(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject thisObject(scope, that->toObject(scope.engine)); if (!thisObject) RETURN_UNDEFINED(); ScopedArrayObject result(scope, scope.engine->newArrayObject()); - if (thisObject->isArrayObject()) { - result->copyArrayData(thisObject); - } else { - result->arraySet(0, thisObject); - } - ScopedArrayObject elt(scope); ScopedObject eltAsObj(scope); ScopedValue entry(scope); - for (int i = 0; i < callData->argc; ++i) { - eltAsObj = callData->args[i]; - elt = callData->args[i]; + for (int i = -1; i < argc; ++i) { + const Value *v = i == -1 ? thisObject.getPointer() : argv + i; + eltAsObj = *v; + elt = *v; if (elt) { uint n = elt->getLength(); uint newLen = ArrayData::append(result, elt, n); result->setArrayLengthUnchecked(newLen); + } else if (eltAsObj && eltAsObj->isConcatSpreadable()) { + const uint startIndex = result->getLength(); + const uint len = eltAsObj->getLength(); + if (scope.engine->hasException) + return Encode::undefined(); + + for (uint i = 0; i < len; ++i) { + bool hasProperty = false; + entry = eltAsObj->get(i, &hasProperty); + if (hasProperty) { + if (!result->put(startIndex + i, entry)) + return scope.engine->throwTypeError(); + } + } } else if (eltAsObj && eltAsObj->isListType()) { const uint startIndex = result->getLength(); for (int i = 0, len = eltAsObj->getLength(); i < len; ++i) { - entry = eltAsObj->getIndexed(i); - result->putIndexed(startIndex + i, entry); + entry = eltAsObj->get(i); + // spec says not to throw if this fails + result->put(startIndex + i, entry); } } else { - result->arraySet(result->getLength(), callData->args[i]); + result->arraySet(result->getLength(), *v); } } - scope.result = result; + return result.asReturnedValue(); +} + +ReturnedValue ArrayPrototype::method_copyWithin(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); + if (!instance) + RETURN_UNDEFINED(); + + double len = instance->getLength(); + double target = argv[0].toInteger(); + double start = argc > 1 ? argv[1].toInteger() : 0; + double end = len; + + if (argc > 2 && !argv[2].isUndefined()) { + end = argv[2].toInteger(); + } + + double relativeTarget = target; + double relativeStart = start; + double relativeEnd = end; + double from = 0; + double to = 0; + + if (relativeTarget < 0) { + to = std::max(len+relativeTarget, 0.0); + } else { + to = std::min(relativeTarget, len); + } + if (relativeStart < 0) { + from = std::max(len+relativeStart, 0.0); + } else { + from = std::min(relativeStart, len); + } + + double fin = 0; + if (relativeEnd < 0) { + fin = std::max(len+relativeEnd, 0.0); + } else { + fin = std::min(relativeEnd, len); + } + double count = std::min(fin-from, len-to); + double direction = 1; + if (from < to && to < from+count) { + direction = -1; + from = from + count - 1; + to = to + count - 1; + } + + while (count > 0) { + bool fromPresent = false; + ScopedValue fromVal(scope, instance->get(from, &fromPresent)); + + if (fromPresent) { + instance->setIndexed(to, fromVal, QV4::Object::DoThrowOnRejection); + CHECK_EXCEPTION(); + } else { + bool didDelete = instance->deleteProperty(PropertyKey::fromArrayIndex(to)); + CHECK_EXCEPTION(); + if (!didDelete) { + return scope.engine->throwTypeError(); + } + } + + from = from + direction; + to = to + direction; + count = count - 1; + } + + return instance.asReturnedValue(); +} + +ReturnedValue ArrayPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + ScopedObject O(scope, thisObject->toObject(scope.engine)); + if (!O) + RETURN_UNDEFINED(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(O)); + ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; + return ao->asReturnedValue(); } -void ArrayPrototype::method_find(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_find(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + if (!argc || !argv[0].isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); - ScopedCallData cData(scope, 3); - cData->thisObject = callData->argument(1); - cData->args[2] = instance; + ScopedValue result(scope); + Value *arguments = scope.alloc(3); - ScopedValue v(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); for (uint k = 0; k < len; ++k) { - v = instance->getIndexed(k); + arguments[0] = instance->get(k); CHECK_EXCEPTION(); - cData->args[0] = v; - cData->args[1] = Primitive::fromDouble(k); - callback->call(scope, cData); + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + result = callback->call(that, arguments, 3); CHECK_EXCEPTION(); - if (scope.result.toBoolean()) - RETURN_RESULT(v); + if (result->toBoolean()) + return arguments[0].asReturnedValue(); } RETURN_UNDEFINED(); } -void ArrayPrototype::method_findIndex(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_findIndex(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + if (!argc || !argv[0].isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); - ScopedCallData cData(scope, 3); - cData->thisObject = callData->argument(1); - cData->args[2] = instance; + ScopedValue result(scope); + Value *arguments = scope.alloc(3); - ScopedValue v(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); for (uint k = 0; k < len; ++k) { - v = instance->getIndexed(k); + arguments[0] = instance->get(k); CHECK_EXCEPTION(); - cData->args[0] = v; - cData->args[1] = Primitive::fromDouble(k); - callback->call(scope, cData); + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + result = callback->call(that, arguments, 3); CHECK_EXCEPTION(); - if (scope.result.toBoolean()) - RETURN_RESULT(Encode(k)); + if (result->toBoolean()) + return Encode(k); } - RETURN_RESULT(Encode(-1)); + return Encode(-1); } -void ArrayPrototype::method_join(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_join(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedValue arg(scope, callData->argument(0)); - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); - if (!instance) { - scope.result = scope.engine->newString(); - return; - } + if (!instance) + return Encode(scope.engine->newString()); + + ScopedValue arg(scope, argc ? argv[0] : Value::undefinedValue()); QString r4; if (arg->isUndefined()) @@ -270,10 +613,8 @@ void ArrayPrototype::method_join(const BuiltinFunction *, Scope &scope, CallData ScopedValue length(scope, instance->get(scope.engine->id_length())); const quint32 r2 = length->isUndefined() ? 0 : length->toUInt32(); - if (!r2) { - scope.result = scope.engine->newString(); - return; - } + if (!r2) + return Encode(scope.engine->newString()); QString R; @@ -284,7 +625,7 @@ void ArrayPrototype::method_join(const BuiltinFunction *, Scope &scope, CallData if (i) R += r4; - e = a->getIndexed(i); + e = a->get(i); CHECK_EXCEPTION(); if (!e->isNullOrUndefined()) R += e->toQString(); @@ -302,7 +643,7 @@ void ArrayPrototype::method_join(const BuiltinFunction *, Scope &scope, CallData for (quint32 k = 1; k < r2; ++k) { R += r4; - name = Primitive::fromDouble(k).toString(scope.engine); + name = Value::fromDouble(k).toString(scope.engine); r12 = instance->get(name); CHECK_EXCEPTION(); @@ -311,12 +652,13 @@ void ArrayPrototype::method_join(const BuiltinFunction *, Scope &scope, CallData } } - scope.result = scope.engine->newString(R); + return Encode(scope.engine->newString(R)); } -void ArrayPrototype::method_pop(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_pop(const FunctionObject *b, const Value *thisObject, const Value *, int) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); @@ -324,79 +666,90 @@ void ArrayPrototype::method_pop(const BuiltinFunction *, Scope &scope, CallData if (!len) { if (!instance->isArrayObject()) - instance->put(scope.engine->id_length(), ScopedValue(scope, Primitive::fromInt32(0))); + instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromInt32(0))); RETURN_UNDEFINED(); } - ScopedValue result(scope, instance->getIndexed(len - 1)); + ScopedValue result(scope, instance->get(len - 1)); CHECK_EXCEPTION(); - instance->deleteIndexedProperty(len - 1); - CHECK_EXCEPTION(); + if (!instance->deleteProperty(PropertyKey::fromArrayIndex(len - 1))) + return scope.engine->throwTypeError(); if (instance->isArrayObject()) instance->setArrayLength(len - 1); - else - instance->put(scope.engine->id_length(), ScopedValue(scope, Primitive::fromDouble(len - 1))); - scope.result = result; + else { + if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(len - 1)))) + return scope.engine->throwTypeError(); + } + return result->asReturnedValue(); } -void ArrayPrototype::method_push(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_push(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); instance->arrayCreate(); Q_ASSERT(instance->arrayData()); - uint len = instance->getLength(); + qint64 len = instance->getLength(); - if (len + callData->argc < len) { - // ughh... + if (len + quint64(argc) >= UINT_MAX) { + // ughh... this goes beyond UINT_MAX double l = len; ScopedString s(scope); - for (int i = 0; i < callData->argc; ++i) { - s = Primitive::fromDouble(l + i).toString(scope.engine); - instance->put(s, callData->args[i]); + for (int i = 0, ei = argc; i < ei; ++i) { + s = Value::fromDouble(l + i).toString(scope.engine); + if (!instance->put(s, argv[i])) + return scope.engine->throwTypeError(); } - double newLen = l + callData->argc; - if (!instance->isArrayObject()) - instance->put(scope.engine->id_length(), ScopedValue(scope, Primitive::fromDouble(newLen))); - else { + double newLen = l + argc; + if (!instance->isArrayObject()) { + if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(newLen)))) + return scope.engine->throwTypeError(); + } else { ScopedString str(scope, scope.engine->newString(QStringLiteral("Array.prototype.push: Overflow"))); - scope.result = scope.engine->throwRangeError(str); - return; + return scope.engine->throwRangeError(str); } - scope.result = Encode(newLen); - return; + return Encode(newLen); } - if (!callData->argc) + if (!argc) ; else if (!instance->protoHasArray() && instance->arrayData()->length() <= len && instance->arrayData()->type == Heap::ArrayData::Simple) { - instance->arrayData()->vtable()->putArray(instance, len, callData->args, callData->argc); + instance->arrayData()->vtable()->putArray(instance, len, argv, argc); len = instance->arrayData()->length(); } else { - for (int i = 0; i < callData->argc; ++i) - instance->putIndexed(len + i, callData->args[i]); - len += callData->argc; + for (int i = 0, ei = argc; i < ei; ++i) { + if (!instance->put(len + i, argv[i])) + return scope.engine->throwTypeError(); + } + len += argc; } if (instance->isArrayObject()) instance->setArrayLengthUnchecked(len); - else - instance->put(scope.engine->id_length(), ScopedValue(scope, Primitive::fromDouble(len))); + else { + if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(len)))) + return scope.engine->throwTypeError(); + } - scope.result = Encode(len); + return Encode(uint(len)); } -void ArrayPrototype::method_reverse(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_reverse(const FunctionObject *b, const Value *thisObject, const Value *, int) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); - uint length = instance->getLength(); + qint64 length = instance->getLength(); + // ### FIXME + if (length >= UINT_MAX) + return scope.engine->throwRangeError(QLatin1String("Array.prototype.reverse: Length out of range.")); int lo = 0, hi = length - 1; @@ -404,25 +757,30 @@ void ArrayPrototype::method_reverse(const BuiltinFunction *, Scope &scope, CallD ScopedValue hval(scope); for (; lo < hi; ++lo, --hi) { bool loExists, hiExists; - lval = instance->getIndexed(lo, &loExists); - hval = instance->getIndexed(hi, &hiExists); + lval = instance->get(lo, &loExists); + hval = instance->get(hi, &hiExists); CHECK_EXCEPTION(); + bool ok; if (hiExists) - instance->putIndexed(lo, hval); + ok = instance->put(lo, hval); else - instance->deleteIndexedProperty(lo); - CHECK_EXCEPTION(); - if (loExists) - instance->putIndexed(hi, lval); - else - instance->deleteIndexedProperty(hi); + ok = instance->deleteProperty(PropertyKey::fromArrayIndex(lo)); + if (ok) { + if (loExists) + ok = instance->put(hi, lval); + else + ok = instance->deleteProperty(PropertyKey::fromArrayIndex(hi)); + } + if (!ok) + return scope.engine->throwTypeError(); } - scope.result = instance; + return instance->asReturnedValue(); } -void ArrayPrototype::method_shift(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_shift(const FunctionObject *b, const Value *thisObject, const Value *, int) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); @@ -433,46 +791,57 @@ void ArrayPrototype::method_shift(const BuiltinFunction *, Scope &scope, CallDat if (!len) { if (!instance->isArrayObject()) - instance->put(scope.engine->id_length(), ScopedValue(scope, Primitive::fromInt32(0))); + if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromInt32(0)))) + return scope.engine->throwTypeError(); RETURN_UNDEFINED(); } + ScopedValue result(scope); if (!instance->protoHasArray() && !instance->arrayData()->attrs && instance->arrayData()->length() <= len && instance->arrayData()->type != Heap::ArrayData::Custom) { - scope.result = instance->arrayData()->vtable()->pop_front(instance); + result = instance->arrayData()->vtable()->pop_front(instance); } else { - scope.result = instance->getIndexed(0); + result = instance->get(uint(0)); CHECK_EXCEPTION(); ScopedValue v(scope); // do it the slow way for (uint k = 1; k < len; ++k) { bool exists; - v = instance->getIndexed(k, &exists); + v = instance->get(k, &exists); CHECK_EXCEPTION(); + bool ok; if (exists) - instance->putIndexed(k - 1, v); + ok = instance->put(k - 1, v); else - instance->deleteIndexedProperty(k - 1); - CHECK_EXCEPTION(); + ok = instance->deleteProperty(PropertyKey::fromArrayIndex(k - 1)); + if (!ok) + return scope.engine->throwTypeError(); } - instance->deleteIndexedProperty(len - 1); - CHECK_EXCEPTION(); + bool ok = instance->deleteProperty(PropertyKey::fromArrayIndex(len - 1)); + if (!ok) + return scope.engine->throwTypeError(); } if (instance->isArrayObject()) instance->setArrayLengthUnchecked(len - 1); - else - instance->put(scope.engine->id_length(), ScopedValue(scope, Primitive::fromDouble(len - 1))); + else { + bool ok = instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(len - 1))); + if (!ok) + return scope.engine->throwTypeError(); + } + + return result->asReturnedValue(); } -void ArrayPrototype::method_slice(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject o(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject o(scope, thisObject->toObject(scope.engine)); if (!o) RETURN_UNDEFINED(); ScopedArrayObject result(scope, scope.engine->newArrayObject()); uint len = o->getLength(); - double s = ScopedValue(scope, callData->argument(0))->toInteger(); + double s = (argc ? argv[0] : Value::undefinedValue()).toInteger(); uint start; if (s < 0) start = (uint)qMax(len + s, 0.); @@ -481,8 +850,8 @@ void ArrayPrototype::method_slice(const BuiltinFunction *, Scope &scope, CallDat else start = (uint) s; uint end = len; - if (callData->argc > 1 && !callData->args[1].isUndefined()) { - double e = callData->args[1].toInteger(); + if (argc > 1 && !argv[1].isUndefined()) { + double e = argv[1].toInteger(); if (e < 0) end = (uint)qMax(len + e, 0.); else if (e > len) @@ -495,106 +864,120 @@ void ArrayPrototype::method_slice(const BuiltinFunction *, Scope &scope, CallDat uint n = 0; for (uint i = start; i < end; ++i) { bool exists; - v = o->getIndexed(i, &exists); + v = o->get(i, &exists); CHECK_EXCEPTION(); if (exists) result->arraySet(n, v); ++n; } - scope.result = result; + return result->asReturnedValue(); } -void ArrayPrototype::method_sort(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_sort(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - ScopedValue comparefn(scope, callData->argument(0)); + ScopedValue comparefn(scope, argc ? argv[0] : Value::undefinedValue()); ArrayData::sort(scope.engine, instance, comparefn, len); - scope.result = callData->thisObject; + return thisObject->asReturnedValue(); } -void ArrayPrototype::method_splice(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_splice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); - uint len = instance->getLength(); - - ScopedArrayObject newArray(scope, scope.engine->newArrayObject()); + qint64 len = instance->getLength(); - double rs = ScopedValue(scope, callData->argument(0))->toInteger(); - uint start; + double rs = (argc ? argv[0] : Value::undefinedValue()).toInteger(); + qint64 start; if (rs < 0) - start = (uint) qMax(0., len + rs); + start = static_cast<qint64>(qMax(0., len + rs)); else - start = (uint) qMin(rs, (double)len); + start = static_cast<qint64>(qMin(rs, static_cast<double>(len))); + + qint64 deleteCount = 0; + qint64 itemCount = 0; + if (argc == 1) { + deleteCount = len - start; + } else if (argc > 1){ + itemCount = argc - 2; + double dc = argv[1].toInteger(); + deleteCount = static_cast<qint64>(qMin(qMax(dc, 0.), double(len - start))); + } - uint deleteCount = (uint)qMin(qMax(ScopedValue(scope, callData->argument(1))->toInteger(), 0.), (double)(len - start)); + if (len + itemCount - deleteCount > /*(static_cast<qint64>(1) << 53) - 1*/ UINT_MAX - 1) + return scope.engine->throwTypeError(); + if (deleteCount > /*(static_cast<qint64>(1) << 53) - 1*/ UINT_MAX - 1) + return scope.engine->throwRangeError(QString::fromLatin1("Array length out of range.")); + ScopedArrayObject newArray(scope, scope.engine->newArrayObject()); newArray->arrayReserve(deleteCount); ScopedValue v(scope); for (uint i = 0; i < deleteCount; ++i) { bool exists; - v = instance->getIndexed(start + i, &exists); + v = instance->get(start + i, &exists); CHECK_EXCEPTION(); if (exists) newArray->arrayPut(i, v); } newArray->setArrayLengthUnchecked(deleteCount); - uint itemCount = callData->argc < 2 ? 0 : callData->argc - 2; if (itemCount < deleteCount) { for (uint k = start; k < len - deleteCount; ++k) { bool exists; - v = instance->getIndexed(k + deleteCount, &exists); + v = instance->get(k + deleteCount, &exists); CHECK_EXCEPTION(); + bool ok; if (exists) - instance->putIndexed(k + itemCount, v); + ok = instance->put(k + itemCount, v); else - instance->deleteIndexedProperty(k + itemCount); - CHECK_EXCEPTION(); + ok = instance->deleteProperty(PropertyKey::fromArrayIndex(k + itemCount)); + if (!ok) + return scope.engine->throwTypeError(); } for (uint k = len; k > len - deleteCount + itemCount; --k) { - instance->deleteIndexedProperty(k - 1); - CHECK_EXCEPTION(); + if (!instance->deleteProperty(PropertyKey::fromArrayIndex(k - 1))) + return scope.engine->throwTypeError(); } } else if (itemCount > deleteCount) { uint k = len - deleteCount; while (k > start) { bool exists; - v = instance->getIndexed(k + deleteCount - 1, &exists); + v = instance->get(k + deleteCount - 1, &exists); CHECK_EXCEPTION(); + bool ok; if (exists) - instance->putIndexed(k + itemCount - 1, v); + ok = instance->put(k + itemCount - 1, v); else - instance->deleteIndexedProperty(k + itemCount - 1); - CHECK_EXCEPTION(); + ok = instance->deleteProperty(PropertyKey::fromArrayIndex(k + itemCount - 1)); + if (!ok) + return scope.engine->throwTypeError(); --k; } } - for (uint i = 0; i < itemCount; ++i) { - instance->putIndexed(start + i, callData->args[i + 2]); - CHECK_EXCEPTION(); - } + for (uint i = 0; i < itemCount; ++i) + instance->put(start + i, argv[i + 2]); - bool wasStrict = scope.engine->current->strictMode; - scope.engine->current->strictMode = true; - instance->put(scope.engine->id_length(), ScopedValue(scope, Primitive::fromDouble(len - deleteCount + itemCount))); + if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(len - deleteCount + itemCount)))) + return scope.engine->throwTypeError(); - scope.result = newArray; - scope.engine->current->strictMode = wasStrict; + return newArray->asReturnedValue(); } -void ArrayPrototype::method_unshift(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_unshift(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); @@ -605,52 +988,95 @@ void ArrayPrototype::method_unshift(const BuiltinFunction *, Scope &scope, CallD if (!instance->protoHasArray() && !instance->arrayData()->attrs && instance->arrayData()->length() <= len && instance->arrayData()->type != Heap::ArrayData::Custom) { - instance->arrayData()->vtable()->push_front(instance, callData->args, callData->argc); + instance->arrayData()->vtable()->push_front(instance, argv, argc); } else { ScopedValue v(scope); for (uint k = len; k > 0; --k) { bool exists; - v = instance->getIndexed(k - 1, &exists); + v = instance->get(k - 1, &exists); + bool ok; if (exists) - instance->putIndexed(k + callData->argc - 1, v); + ok = instance->put(k + argc - 1, v); else - instance->deleteIndexedProperty(k + callData->argc - 1); + ok = instance->deleteProperty(PropertyKey::fromArrayIndex(k + argc - 1)); + if (!ok) + return scope.engine->throwTypeError(); + } + for (int i = 0, ei = argc; i < ei; ++i) { + bool ok = instance->put(i, argv[i]); + if (!ok) + return scope.engine->throwTypeError(); } - for (int i = 0; i < callData->argc; ++i) - instance->putIndexed(i, callData->args[i]); } - uint newLen = len + callData->argc; + uint newLen = len + argc; if (instance->isArrayObject()) instance->setArrayLengthUnchecked(newLen); - else - instance->put(scope.engine->id_length(), ScopedValue(scope, Primitive::fromDouble(newLen))); + else { + if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(newLen)))) + return scope.engine->throwTypeError(); + } - scope.result = Encode(newLen); + return Encode(newLen); } -void ArrayPrototype::method_indexOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); - uint len = instance->getLength(); - if (!len) { - scope.result = Encode(-1); - return; + qint64 len = instance->getLength(); + if (len == 0) { + return Encode(false); + } + + double n = 0; + if (argc > 1 && !argv[1].isUndefined()) { + n = argv[1].toInteger(); + } + + double k = 0; + if (n >= 0) { + k = n; + } else { + k = len + n; + if (k < 0) { + k = 0; + } } - ScopedValue searchValue(scope, callData->argument(0)); + while (k < len) { + ScopedValue val(scope, instance->get(k)); + if (val->sameValueZero(argv[0])) { + return Encode(true); + } + k++; + } + + return Encode(false); +} + +ReturnedValue ArrayPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); + if (!instance) + RETURN_UNDEFINED(); + + uint len = instance->getLength(); + if (!len) + return Encode(-1); + + ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue()); uint fromIndex = 0; - if (callData->argc >= 2) { - double f = callData->args[1].toInteger(); + if (argc >= 2) { + double f = argv[1].toInteger(); CHECK_EXCEPTION(); - if (f >= len) { - scope.result = Encode(-1); - return; - } + if (f >= len) + return Encode(-1); if (f < 0) f = qMax(len + f, 0.); fromIndex = (uint) f; @@ -660,14 +1086,11 @@ void ArrayPrototype::method_indexOf(const BuiltinFunction *, Scope &scope, CallD ScopedValue v(scope); for (uint k = fromIndex; k < len; ++k) { bool exists; - v = instance->getIndexed(k, &exists); - if (exists && RuntimeHelpers::strictEqual(v, searchValue)) { - scope.result = Encode(k); - return; - } + v = instance->get(k, &exists); + if (exists && RuntimeHelpers::strictEqual(v, searchValue)) + return Encode(k); } - scope.result = Encode(-1); - return; + return Encode(-1); } ScopedValue value(scope); @@ -677,18 +1100,15 @@ void ArrayPrototype::method_indexOf(const BuiltinFunction *, Scope &scope, CallD // lets be safe and slow for (uint i = fromIndex; i < len; ++i) { bool exists; - value = instance->getIndexed(i, &exists); + value = instance->get(i, &exists); CHECK_EXCEPTION(); - if (exists && RuntimeHelpers::strictEqual(value, searchValue)) { - scope.result = Encode(i); - return; - } + if (exists && RuntimeHelpers::strictEqual(value, searchValue)) + return Encode(i); } } else if (!instance->arrayData()) { - scope.result = Encode(-1); - return; + return Encode(-1); } else { - Q_ASSERT(instance->arrayType() == Heap::ArrayData::Simple || instance->arrayType() == Heap::ArrayData::Complex); + Q_ASSERT(instance->arrayType() == Heap::ArrayData::Simple); Heap::SimpleArrayData *sa = instance->d()->arrayData.cast<Heap::SimpleArrayData>(); if (len > sa->values.size) len = sa->values.size; @@ -696,47 +1116,54 @@ void ArrayPrototype::method_indexOf(const BuiltinFunction *, Scope &scope, CallD while (idx < len) { value = sa->data(idx); CHECK_EXCEPTION(); - if (RuntimeHelpers::strictEqual(value, searchValue)) { - scope.result = Encode(idx); - return; - } + if (RuntimeHelpers::strictEqual(value, searchValue)) + return Encode(idx); ++idx; } } - scope.result = Encode(-1); + return Encode(-1); +} + +ReturnedValue ArrayPrototype::method_keys(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + ScopedObject O(scope, thisObject->toObject(scope.engine)); + if (!O) + RETURN_UNDEFINED(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(O)); + ao->d()->iterationKind = IteratorKind::KeyIteratorKind; + return ao->asReturnedValue(); } -void ArrayPrototype::method_lastIndexOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - if (!len) { - scope.result = Encode(-1); - return; - } + if (!len) + return Encode(-1); ScopedValue searchValue(scope); uint fromIndex = len; - if (callData->argc >= 1) - searchValue = callData->argument(0); + if (argc >= 1) + searchValue = argv[0]; else - searchValue = Primitive::undefinedValue(); + searchValue = Value::undefinedValue(); - if (callData->argc >= 2) { - double f = callData->args[1].toInteger(); + if (argc >= 2) { + double f = argv[1].toInteger(); CHECK_EXCEPTION(); if (f > 0) f = qMin(f, (double)(len - 1)); else if (f < 0) { f = len + f; - if (f < 0) { - scope.result = Encode(-1); - return; - } + if (f < 0) + return Encode(-1); } fromIndex = (uint) f + 1; } @@ -745,283 +1172,335 @@ void ArrayPrototype::method_lastIndexOf(const BuiltinFunction *, Scope &scope, C for (uint k = fromIndex; k > 0;) { --k; bool exists; - v = instance->getIndexed(k, &exists); + v = instance->get(k, &exists); CHECK_EXCEPTION(); - if (exists && RuntimeHelpers::strictEqual(v, searchValue)) { - scope.result = Encode(k); - return; - } + if (exists && RuntimeHelpers::strictEqual(v, searchValue)) + return Encode(k); } - scope.result = Encode(-1); + return Encode(-1); } -void ArrayPrototype::method_every(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_every(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); - ScopedCallData cData(scope, 3); - cData->args[2] = instance; - cData->thisObject = callData->argument(1); - ScopedValue v(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + ScopedValue r(scope); + Value *arguments = scope.alloc(3); bool ok = true; for (uint k = 0; ok && k < len; ++k) { bool exists; - v = instance->getIndexed(k, &exists); + arguments[0] = instance->get(k, &exists); if (!exists) continue; - cData->args[0] = v; - cData->args[1] = Primitive::fromDouble(k); - callback->call(scope, cData); - ok = scope.result.toBoolean(); + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + r = callback->call(that, arguments, 3); + ok = r->toBoolean(); } - scope.result = Encode(ok); + return Encode(ok); } -void ArrayPrototype::method_some(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_fill(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); + int relativeStart = argc > 1 ? argv[1].toInteger() : 0; + int relativeEnd = len; + if (argc > 2 && !argv[2].isUndefined()) { + relativeEnd = argv[2].toInteger(); + } + uint k = 0; + uint fin = 0; + + if (relativeStart < 0) { + k = std::max(len+relativeStart, uint(0)); + } else { + k = std::min(uint(relativeStart), len); + } + + if (relativeEnd < 0) { + fin = std::max(len + relativeEnd, uint(0)); + } else { + fin = std::min(uint(relativeEnd), len); + } - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + while (k < fin) { + instance->setIndexed(k, argv[0], QV4::Object::DoThrowOnRejection); + k++; + } + + return instance.asReturnedValue(); +} + +ReturnedValue ArrayPrototype::method_some(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); + if (!instance) + RETURN_UNDEFINED(); + + uint len = instance->getLength(); + + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); - ScopedCallData cData(scope, 3); - cData->thisObject = callData->argument(1); - cData->args[2] = instance; - ScopedValue v(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + ScopedValue result(scope); + Value *arguments = scope.alloc(3); for (uint k = 0; k < len; ++k) { bool exists; - v = instance->getIndexed(k, &exists); + arguments[0] = instance->get(k, &exists); if (!exists) continue; - cData->args[0] = v; - cData->args[1] = Primitive::fromDouble(k); - callback->call(scope, cData); - if (scope.result.toBoolean()) { - scope.result = Encode(true); - return; - } + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + result = callback->call(that, arguments, 3); + if (result->toBoolean()) + return Encode(true); } - scope.result = Encode(false); + return Encode(false); } -void ArrayPrototype::method_forEach(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); - ScopedCallData cData(scope, 3); - cData->thisObject = callData->argument(1); - cData->args[2] = instance; + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); - ScopedValue v(scope); for (uint k = 0; k < len; ++k) { bool exists; - v = instance->getIndexed(k, &exists); + arguments[0] = instance->get(k, &exists); if (!exists) continue; - cData->args[0] = v; - cData->args[1] = Primitive::fromDouble(k); - callback->call(scope, cData); + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + callback->call(that, arguments, 3); } RETURN_UNDEFINED(); } -void ArrayPrototype::method_map(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_map(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); - uint len = instance->getLength(); + qint64 len = instance->getLength(); - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + if (len > UINT_MAX - 1) + return scope.engine->throwRangeError(QString::fromLatin1("Array length out of range.")); ScopedArrayObject a(scope, scope.engine->newArrayObject()); a->arrayReserve(len); a->setArrayLengthUnchecked(len); - ScopedCallData cData(scope, 3); - cData->thisObject = callData->argument(1); - cData->args[2] = instance; - ScopedValue v(scope); + ScopedValue mapped(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); + for (uint k = 0; k < len; ++k) { bool exists; - v = instance->getIndexed(k, &exists); + arguments[0] = instance->get(k, &exists); if (!exists) continue; - cData->args[0] = v; - cData->args[1] = Primitive::fromDouble(k); - callback->call(scope, cData); - a->arraySet(k, scope.result); + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + mapped = callback->call(that, arguments, 3); + a->arraySet(k, mapped); } - scope.result = a.asReturnedValue(); + return a.asReturnedValue(); } -void ArrayPrototype::method_filter(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_filter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); ScopedArrayObject a(scope, scope.engine->newArrayObject()); a->arrayReserve(len); - ScopedCallData cData(scope, 3); - cData->thisObject = callData->argument(1); - cData->args[2] = instance; - - ScopedValue v(scope); + ScopedValue selected(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); uint to = 0; for (uint k = 0; k < len; ++k) { bool exists; - v = instance->getIndexed(k, &exists); + arguments[0] = instance->get(k, &exists); if (!exists) continue; - cData->args[0] = v; - cData->args[1] = Primitive::fromDouble(k); - callback->call(scope, cData); - if (scope.result.toBoolean()) { - a->arraySet(to, v); + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + selected = callback->call(that, arguments, 3); + if (selected->toBoolean()) { + a->arraySet(to, arguments[0]); ++to; } } - scope.result = a.asReturnedValue(); + return a.asReturnedValue(); } -void ArrayPrototype::method_reduce(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_reduce(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); uint k = 0; + ScopedValue acc(scope); ScopedValue v(scope); - if (callData->argc > 1) { - scope.result = callData->argument(1); + if (argc > 1) { + acc = argv[1]; } else { bool kPresent = false; while (k < len && !kPresent) { - v = instance->getIndexed(k, &kPresent); + v = instance->get(k, &kPresent); if (kPresent) - scope.result = v; + acc = v; ++k; } if (!kPresent) THROW_TYPE_ERROR(); } - ScopedCallData cData(scope, 4); - cData->thisObject = Primitive::undefinedValue(); - cData->args[0] = scope.result; - cData->args[3] = instance; + Value *arguments = scope.alloc(4); while (k < len) { bool kPresent; - v = instance->getIndexed(k, &kPresent); + v = instance->get(k, &kPresent); if (kPresent) { - cData->args[0] = scope.result; - cData->args[1] = v; - cData->args[2] = Primitive::fromDouble(k); - callback->call(scope, cData); + arguments[0] = acc; + arguments[1] = v; + arguments[2] = Value::fromDouble(k); + arguments[3] = instance; + acc = callback->call(nullptr, arguments, 4); } ++k; } + return acc->asReturnedValue(); } -void ArrayPrototype::method_reduceRight(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ArrayPrototype::method_reduceRight(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject instance(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject instance(scope, thisObject->toObject(scope.engine)); if (!instance) RETURN_UNDEFINED(); uint len = instance->getLength(); - ScopedFunctionObject callback(scope, callData->argument(0)); - if (!callback) + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); if (len == 0) { - if (callData->argc == 1) + if (argc == 1) THROW_TYPE_ERROR(); - scope.result = callData->argument(1); - return; + return argv[1].asReturnedValue(); } uint k = len; + ScopedValue acc(scope); ScopedValue v(scope); - if (callData->argc > 1) { - scope.result = callData->argument(1); + if (argc > 1) { + acc = argv[1]; } else { bool kPresent = false; while (k > 0 && !kPresent) { - v = instance->getIndexed(k - 1, &kPresent); + v = instance->get(k - 1, &kPresent); if (kPresent) - scope.result = v; + acc = v; --k; } if (!kPresent) THROW_TYPE_ERROR(); } - ScopedCallData cData(scope, 4); - cData->thisObject = Primitive::undefinedValue(); - cData->args[3] = instance; + Value *arguments = scope.alloc(4); while (k > 0) { bool kPresent; - v = instance->getIndexed(k - 1, &kPresent); + v = instance->get(k - 1, &kPresent); if (kPresent) { - cData->args[0] = scope.result; - cData->args[1] = v; - cData->args[2] = Primitive::fromDouble(k - 1); - callback->call(scope, cData); + arguments[0] = acc; + arguments[1] = v; + arguments[2] = Value::fromDouble(k - 1); + arguments[3] = instance; + acc = callback->call(nullptr, arguments, 4); } --k; } - scope.result = scope.result.asReturnedValue(); + return acc->asReturnedValue(); +} + +ReturnedValue ArrayPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + ScopedObject O(scope, thisObject->toObject(scope.engine)); + if (!O) + RETURN_UNDEFINED(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(O)); + ao->d()->iterationKind = IteratorKind::ValueIteratorKind; + return ao->asReturnedValue(); +} + +ReturnedValue ArrayPrototype::method_get_species(const FunctionObject *, const Value *thisObject, const Value *, int) +{ + return thisObject->asReturnedValue(); } diff --git a/src/qml/jsruntime/qv4arrayobject_p.h b/src/qml/jsruntime/qv4arrayobject_p.h index 689752433b..c959b71bc6 100644 --- a/src/qml/jsruntime/qv4arrayobject_p.h +++ b/src/qml/jsruntime/qv4arrayobject_p.h @@ -70,38 +70,50 @@ struct ArrayCtor: FunctionObject { V4_OBJECT2(ArrayCtor, FunctionObject) - static void construct(const Managed *m, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; struct ArrayPrototype: ArrayObject { void init(ExecutionEngine *engine, Object *ctor); - static void method_isArray(const BuiltinFunction *, Scope &, CallData *callData); - static void method_toString(const BuiltinFunction *, Scope &, CallData *callData); - static void method_toLocaleString(const BuiltinFunction *builtin, Scope &, CallData *callData); - static void method_concat(const BuiltinFunction *, Scope &, CallData *callData); - static void method_find(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_findIndex(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_join(const BuiltinFunction *, Scope &, CallData *callData); - static void method_pop(const BuiltinFunction *, Scope &, CallData *callData); - static void method_push(const BuiltinFunction *, Scope &, CallData *callData); - static void method_reverse(const BuiltinFunction *, Scope &, CallData *callData); - static void method_shift(const BuiltinFunction *, Scope &, CallData *callData); - static void method_slice(const BuiltinFunction *, Scope &, CallData *callData); - static void method_sort(const BuiltinFunction *, Scope &, CallData *callData); - static void method_splice(const BuiltinFunction *, Scope &, CallData *callData); - static void method_unshift(const BuiltinFunction *, Scope &, CallData *callData); - static void method_indexOf(const BuiltinFunction *, Scope &, CallData *callData); - static void method_lastIndexOf(const BuiltinFunction *, Scope &, CallData *callData); - static void method_every(const BuiltinFunction *, Scope &, CallData *callData); - static void method_some(const BuiltinFunction *, Scope &, CallData *callData); - static void method_forEach(const BuiltinFunction *, Scope &, CallData *callData); - static void method_map(const BuiltinFunction *, Scope &, CallData *callData); - static void method_filter(const BuiltinFunction *, Scope &, CallData *callData); - static void method_reduce(const BuiltinFunction *, Scope &, CallData *callData); - static void method_reduceRight(const BuiltinFunction *, Scope &, CallData *callData); + static ReturnedValue method_isArray(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_from(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_of(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_concat(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_copyWithin(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_entries(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_find(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_findIndex(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_join(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_pop(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_push(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_reverse(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_shift(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_slice(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_sort(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_splice(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_unshift(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_includes(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_indexOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_keys(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_lastIndexOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_every(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_fill(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_some(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_forEach(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_map(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_filter(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_reduce(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_reduceRight(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_values(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + // while this function is implemented here, it's the same for many other JS classes, so the corresponding JS function + // is instantiated in the engine, and it can be added to any JS object through Object::addSymbolSpecies() + static ReturnedValue method_get_species(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; diff --git a/src/qml/jsruntime/qv4atomics.cpp b/src/qml/jsruntime/qv4atomics.cpp new file mode 100644 index 0000000000..4299aef859 --- /dev/null +++ b/src/qml/jsruntime/qv4atomics.cpp @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qv4arraybuffer_p.h" +#include "qv4typedarray_p.h" +#include "qv4atomics_p.h" +#include "qv4symbol_p.h" + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(Atomics); + +void Heap::Atomics::init() +{ + Object::init(); + Scope scope(internalClass->engine); + ScopedObject m(scope, this); + + m->defineDefaultProperty(QStringLiteral("add"), QV4::Atomics::method_add, 3); + m->defineDefaultProperty(QStringLiteral("and"), QV4::Atomics::method_and, 3); + m->defineDefaultProperty(QStringLiteral("compareExchange"), QV4::Atomics::method_compareExchange, 4); + m->defineDefaultProperty(QStringLiteral("exchange"), QV4::Atomics::method_exchange, 3); + m->defineDefaultProperty(QStringLiteral("isLockFree"), QV4::Atomics::method_isLockFree, 1); + m->defineDefaultProperty(QStringLiteral("load"), QV4::Atomics::method_load, 2); + m->defineDefaultProperty(QStringLiteral("or"), QV4::Atomics::method_or, 3); + m->defineDefaultProperty(QStringLiteral("store"), QV4::Atomics::method_store, 3); + m->defineDefaultProperty(QStringLiteral("sub"), QV4::Atomics::method_sub, 3); + m->defineDefaultProperty(QStringLiteral("wait"), QV4::Atomics::method_wait, 4); + m->defineDefaultProperty(QStringLiteral("wake"), QV4::Atomics::method_wake, 3); + m->defineDefaultProperty(QStringLiteral("xor"), QV4::Atomics::method_xor, 3); + + ScopedString name(scope, scope.engine->newString(QStringLiteral("Atomics"))); + m->defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), name); +} + +static SharedArrayBuffer *validateSharedIntegerTypedArray(Scope &scope, const Value &typedArray, bool onlyInt32 = false) +{ + const TypedArray *a = typedArray.as<TypedArray>(); + if (!a) { + scope.engine->throwTypeError(); + return nullptr; + } + + TypedArrayType t(a->arrayType()); + if (!a->d()->type->atomicLoad || (onlyInt32 && t != TypedArrayType::Int32Array)) { + scope.engine->throwTypeError(); + return nullptr; + } + + Scoped<SharedArrayBuffer> buffer(scope, a->d()->buffer); + if (!buffer->isSharedArrayBuffer()) { + scope.engine->throwTypeError(); + return nullptr; + } + Q_ASSERT(!buffer->isDetachedBuffer()); + return buffer; +} + +static int validateAtomicAccess(Scope &scope, const TypedArray &typedArray, const Value &index) +{ + const TypedArray &a = static_cast<const TypedArray &>(typedArray); + qint64 idx = index.toIndex(); + if (scope.hasException()) + return -1; + if (idx < 0 || idx >= a.length()) { + scope.engine->throwRangeError(QStringLiteral("index out of range.")); + return -1; + } + return static_cast<int>(idx); +} + +ReturnedValue atomicReadModifyWrite(const FunctionObject *f, const Value *argv, int argc, AtomicModifyOps modify) +{ + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + SharedArrayBuffer *buffer = validateSharedIntegerTypedArray(scope, argv[0]); + if (!buffer) + return Encode::undefined(); + const TypedArray &a = static_cast<const TypedArray &>(argv[0]); + int index = validateAtomicAccess(scope, a, argc > 1 ? argv[1] : Value::undefinedValue()); + if (index < 0) + return Encode::undefined(); + + Value v = Value::fromReturnedValue((argc > 2 ? argv[2] : Value::undefinedValue()).convertedToNumber()); + if (scope.hasException()) + return Encode::undefined(); + + int bytesPerElement = a.d()->type->bytesPerElement; + int byteOffset = a.d()->byteOffset + index * bytesPerElement; + + return a.d()->type->atomicModifyOps[modify](buffer->data() + byteOffset, v); +} + +ReturnedValue Atomics::method_add(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return atomicReadModifyWrite(f, argv, argc, AtomicAdd); +} + +ReturnedValue Atomics::method_and(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return atomicReadModifyWrite(f, argv, argc, AtomicAnd); +} + +ReturnedValue Atomics::method_compareExchange(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + SharedArrayBuffer *buffer = validateSharedIntegerTypedArray(scope, argv[0]); + if (!buffer) + return Encode::undefined(); + const TypedArray &a = static_cast<const TypedArray &>(argv[0]); + int index = validateAtomicAccess(scope, a, argc > 1 ? argv[1] : Value::undefinedValue()); + if (index < 0) + return Encode::undefined(); + + Value expected = Value::fromReturnedValue((argc > 2 ? argv[2] : Value::undefinedValue()).convertedToNumber()); + if (scope.hasException()) + return Encode::undefined(); + Value v = Value::fromReturnedValue((argc > 3 ? argv[3] : Value::undefinedValue()).convertedToNumber()); + if (scope.hasException()) + return Encode::undefined(); + + int bytesPerElement = a.d()->type->bytesPerElement; + int byteOffset = a.d()->byteOffset + index * bytesPerElement; + + return a.d()->type->atomicCompareExchange(buffer->data() + byteOffset, expected, v); +} + +ReturnedValue Atomics::method_exchange(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return atomicReadModifyWrite(f, argv, argc, AtomicExchange); +} + +ReturnedValue Atomics::method_isLockFree(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + if (!argc) + return Encode(false); + double n = argv[0].toInteger(); + if (n == 4.) + return Encode(true); + if (n == 2.) + return Encode(QAtomicOps<unsigned short>::isTestAndSetNative()); +#ifdef Q_ATOMIC_INT8_IS_SUPPORTED + if (n == 1.) + return Encode(QAtomicOps<unsigned char>::isTestAndSetNative()); +#endif + return Encode(false); +} + +ReturnedValue Atomics::method_load(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + SharedArrayBuffer *buffer = validateSharedIntegerTypedArray(scope, argv[0]); + if (!buffer) + return Encode::undefined(); + const TypedArray &a = static_cast<const TypedArray &>(argv[0]); + int index = validateAtomicAccess(scope, a, argc > 1 ? argv[1] : Value::undefinedValue()); + if (index < 0) + return Encode::undefined(); + + int bytesPerElement = a.d()->type->bytesPerElement; + int byteOffset = a.d()->byteOffset + index * bytesPerElement; + + return a.d()->type->atomicLoad(buffer->data() + byteOffset); +} + +ReturnedValue Atomics::method_or(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return atomicReadModifyWrite(f, argv, argc, AtomicOr); +} + +ReturnedValue Atomics::method_store(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + SharedArrayBuffer *buffer = validateSharedIntegerTypedArray(scope, argv[0]); + if (!buffer) + return Encode::undefined(); + const TypedArray &a = static_cast<const TypedArray &>(argv[0]); + int index = validateAtomicAccess(scope, a, argc > 1 ? argv[1] : Value::undefinedValue()); + if (index < 0) + return Encode::undefined(); + + Value v = Value::fromReturnedValue((argc > 2 ? argv[2] : Value::undefinedValue()).convertedToNumber()); + if (scope.hasException()) + return Encode::undefined(); + + int bytesPerElement = a.d()->type->bytesPerElement; + int byteOffset = a.d()->byteOffset + index * bytesPerElement; + + return a.d()->type->atomicStore(buffer->data() + byteOffset, v); +} + +ReturnedValue Atomics::method_sub(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return atomicReadModifyWrite(f, argv, argc, AtomicSub); +} + +ReturnedValue Atomics::method_wait(const FunctionObject *f, const Value *, const Value *, int) +{ + return f->engine()->throwTypeError(); +} + +ReturnedValue Atomics::method_wake(const FunctionObject *f, const Value *, const Value *, int) +{ + return f->engine()->throwTypeError(); +} + +ReturnedValue Atomics::method_xor(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return atomicReadModifyWrite(f, argv, argc, AtomicXor); + +} diff --git a/src/qml/jsruntime/qv4atomics_p.h b/src/qml/jsruntime/qv4atomics_p.h new file mode 100644 index 0000000000..35b64bf4fe --- /dev/null +++ b/src/qml/jsruntime/qv4atomics_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4ATOMICS_H +#define QV4ATOMICS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4functionobject_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Heap { + +struct Atomics : Object { + void init(); +}; + +} + +struct Atomics : Object +{ + V4_OBJECT2(Atomics, Object) + + static ReturnedValue method_add(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_and(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_compareExchange(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_exchange(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isLockFree(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_load(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_or(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_store(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_sub(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_wait(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_wake(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_xor(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + + +} // namespace QV4 + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/jsruntime/qv4booleanobject.cpp b/src/qml/jsruntime/qv4booleanobject.cpp index c8e9ebb2dd..3e5f51c302 100644 --- a/src/qml/jsruntime/qv4booleanobject.cpp +++ b/src/qml/jsruntime/qv4booleanobject.cpp @@ -50,54 +50,70 @@ void Heap::BooleanCtor::init(QV4::ExecutionContext *scope) Heap::FunctionObject::init(scope, QStringLiteral("Boolean")); } -void BooleanCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue BooleanCtor::virtualCallAsConstructor(const FunctionObject *that, const Value *argv, int argc, const Value *newTarget) { - bool n = callData->argc ? callData->args[0].toBoolean() : false; - scope.result = Encode(scope.engine->newBooleanObject(n)); + auto v4 = that->engine(); + bool n = argc ? argv[0].toBoolean() : false; + + ReturnedValue o = Encode(v4->newBooleanObject(n)); + if (!newTarget) + return o; + Scope scope(v4); + ScopedObject obj(scope, o); + obj->setProtoFromNewTarget(newTarget); + return obj->asReturnedValue(); } -void BooleanCtor::call(const Managed *, Scope &scope, CallData *callData) +ReturnedValue BooleanCtor::virtualCall(const FunctionObject *, const Value *, const Value *argv, int argc) { - bool value = callData->argc ? callData->args[0].toBoolean() : 0; - scope.result = Encode(value); + bool value = argc ? argv[0].toBoolean() : 0; + return Encode(value); } void BooleanPrototype::init(ExecutionEngine *engine, Object *ctor) { Scope scope(engine); ScopedObject o(scope); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); defineDefaultProperty(engine->id_toString(), method_toString); defineDefaultProperty(engine->id_valueOf(), method_valueOf); } -void BooleanPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +static bool value(const Value *thisObject, bool *exception) { - bool result; - if (callData->thisObject.isBoolean()) { - result = callData->thisObject.booleanValue(); + *exception = false; + if (thisObject->isBoolean()) { + return thisObject->booleanValue(); } else { - const BooleanObject *thisObject = callData->thisObject.as<BooleanObject>(); - if (!thisObject) - THROW_TYPE_ERROR(); - result = thisObject->value(); + const BooleanObject *that = thisObject->as<BooleanObject>(); + if (that) + return that->value(); } + *exception = true; + return false; +} - scope.result = result ? scope.engine->id_true() : scope.engine->id_false(); +ReturnedValue BooleanPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + bool exception; + bool result = ::value(thisObject, &exception); + ExecutionEngine *v4 = b->engine(); + if (exception) + return v4->throwTypeError(); + + return Encode(result ? v4->id_true() : v4->id_false()); } -void BooleanPrototype::method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue BooleanPrototype::method_valueOf(const FunctionObject *b, const Value *thisObject, const Value *, int) { - if (callData->thisObject.isBoolean()) { - scope.result = callData->thisObject.asReturnedValue(); - return; + bool exception; + bool result = ::value(thisObject, &exception); + if (exception) { + ExecutionEngine *v4 = b->engine(); + return v4->throwTypeError(); } - const BooleanObject *thisObject = callData->thisObject.as<BooleanObject>(); - if (!thisObject) - THROW_TYPE_ERROR(); - - scope.result = Encode(thisObject->value()); + return Encode(result); } diff --git a/src/qml/jsruntime/qv4booleanobject_p.h b/src/qml/jsruntime/qv4booleanobject_p.h index ca2cf7d17a..276ec8393b 100644 --- a/src/qml/jsruntime/qv4booleanobject_p.h +++ b/src/qml/jsruntime/qv4booleanobject_p.h @@ -70,8 +70,8 @@ struct BooleanCtor: FunctionObject { V4_OBJECT2(BooleanCtor, FunctionObject) - static void construct(const Managed *, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; struct BooleanPrototype: BooleanObject @@ -79,8 +79,8 @@ struct BooleanPrototype: BooleanObject V4_PROTOTYPE(objectPrototype) void init(ExecutionEngine *engine, Object *ctor); - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_valueOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; diff --git a/src/qml/jsruntime/qv4context.cpp b/src/qml/jsruntime/qv4context.cpp index ba72a9e43b..b3bcfe21d5 100644 --- a/src/qml/jsruntime/qv4context.cpp +++ b/src/qml/jsruntime/qv4context.cpp @@ -48,66 +48,103 @@ #include "qv4errorobject_p.h" #include "qv4string_p.h" #include "qv4qmlcontext_p.h" -#include "qv4profiling_p.h" -#include <private/qqmljavascriptexpression_p.h> +#include "qv4stackframe_p.h" +#include "qv4symbol_p.h" using namespace QV4; DEFINE_MANAGED_VTABLE(ExecutionContext); -DEFINE_MANAGED_VTABLE(SimpleCallContext); DEFINE_MANAGED_VTABLE(CallContext); -DEFINE_MANAGED_VTABLE(WithContext); -DEFINE_MANAGED_VTABLE(CatchContext); -DEFINE_MANAGED_VTABLE(GlobalContext); -Heap::CallContext *ExecutionContext::newCallContext(Function *function, CallData *callData) +Heap::CallContext *ExecutionContext::newBlockContext(CppStackFrame *frame, int blockIndex) { - uint localsAndFormals = function->compiledFunction->nLocals + sizeof(CallData)/sizeof(Value) - 1 + qMax(static_cast<uint>(callData->argc), function->nFormals); - size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * (localsAndFormals); + Function *function = frame->v4Function; + + Heap::InternalClass *ic = function->compilationUnit->runtimeBlocks.at(blockIndex); + uint nLocals = ic->size; + size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * nLocals; + + ExecutionEngine *v4 = function->internalClass->engine; + Heap::CallContext *c = v4->memoryManager->allocManaged<CallContext>(requiredMemory, ic); + c->init(); + c->type = Heap::ExecutionContext::Type_BlockContext; + + Heap::ExecutionContext *outer = static_cast<Heap::ExecutionContext *>(frame->context()->m()); + c->outer.set(v4, outer); + c->function.set(v4, static_cast<Heap::FunctionObject *>(frame->jsFrame->function.m())); + + c->locals.size = nLocals; + c->locals.alloc = nLocals; - ExecutionEngine *v4 = engine(); - Heap::CallContext *c = v4->memoryManager->allocManaged<CallContext>(requiredMemory); - c->init(Heap::ExecutionContext::Type_CallContext); + c->setupLocalTemporalDeadZone(function->compilationUnit->unitData()->blockAt(blockIndex)); - c->v4Function = function; + return c; +} - c->strictMode = function->isStrict(); - c->outer.set(v4, this->d()); +Heap::CallContext *ExecutionContext::cloneBlockContext(ExecutionEngine *engine, + Heap::CallContext *callContext) +{ + uint nLocals = callContext->locals.alloc; + size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * nLocals; - c->compilationUnit = function->compilationUnit; - c->lookups = function->compilationUnit->runtimeLookups; - c->constantTable = function->compilationUnit->constants; + Heap::CallContext *c = engine->memoryManager->allocManaged<CallContext>( + requiredMemory, callContext->internalClass); + memcpy(c, callContext, requiredMemory); + + return c; +} + +Heap::CallContext *ExecutionContext::newCallContext(CppStackFrame *frame) +{ + Function *function = frame->v4Function; + Heap::ExecutionContext *outer = static_cast<Heap::ExecutionContext *>(frame->context()->m()); + + uint nFormals = qMax(static_cast<uint>(frame->originalArgumentsCount), function->nFormals); + uint localsAndFormals = function->compiledFunction->nLocals + nFormals; + size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * (localsAndFormals); + + ExecutionEngine *v4 = outer->internalClass->engine; + Heap::CallContext *c = v4->memoryManager->allocManaged<CallContext>(requiredMemory, function->internalClass); + c->init(); + + c->outer.set(v4, outer); + c->function.set(v4, static_cast<Heap::FunctionObject *>(frame->jsFrame->function.m())); const CompiledData::Function *compiledFunction = function->compiledFunction; uint nLocals = compiledFunction->nLocals; c->locals.size = nLocals; c->locals.alloc = localsAndFormals; -#if QT_POINTER_SIZE == 8 - // memory allocated from the JS heap is 0 initialized, so skip the std::fill() below - Q_ASSERT(Primitive::undefinedValue().asReturnedValue() == 0); -#else - if (nLocals) - std::fill(c->locals.values, c->locals.values + nLocals, Primitive::undefinedValue()); -#endif - - c->callData = reinterpret_cast<CallData *>(c->locals.values + nLocals); - ::memcpy(c->callData, callData, sizeof(CallData) - sizeof(Value) + static_cast<uint>(callData->argc) * sizeof(Value)); - if (callData->argc < static_cast<int>(compiledFunction->nFormals)) - std::fill(c->callData->args + c->callData->argc, c->callData->args + compiledFunction->nFormals, Primitive::undefinedValue()); + // memory allocated from the JS heap is 0 initialized, so check if empty is 0 + Q_ASSERT(Value::undefinedValue().asReturnedValue() == 0); + + c->setupLocalTemporalDeadZone(compiledFunction); + + Value *args = c->locals.values + nLocals; + ::memcpy(args, frame->originalArguments, frame->originalArgumentsCount * sizeof(Value)); + c->nArgs = frame->originalArgumentsCount; + for (uint i = frame->originalArgumentsCount; i < function->nFormals; ++i) + args[i] = Encode::undefined(); return c; } -Heap::WithContext *ExecutionContext::newWithContext(Heap::Object *with) +Heap::ExecutionContext *ExecutionContext::newWithContext(Heap::Object *with) const { - return engine()->memoryManager->alloc<WithContext>(d(), with); + Heap::ExecutionContext *c = engine()->memoryManager->alloc<ExecutionContext>(Heap::ExecutionContext::Type_WithContext); + c->outer.set(engine(), d()); + c->activation.set(engine(), with); + + return c; } -Heap::CatchContext *ExecutionContext::newCatchContext(Heap::String *exceptionVarName, ReturnedValue exceptionValue) +Heap::ExecutionContext *ExecutionContext::newCatchContext(CppStackFrame *frame, int blockIndex, Heap::String *exceptionVarName) { - Scope scope(this); - ScopedValue e(scope, exceptionValue); - return engine()->memoryManager->alloc<CatchContext>(d(), exceptionVarName, e); + Scope scope(frame->context()); + ScopedString name(scope, exceptionVarName); + ScopedValue val(scope, scope.engine->catchException(nullptr)); + ScopedContext ctx(scope, newBlockContext(frame, blockIndex)); + ctx->setProperty(name, val); + return ctx->d(); } void ExecutionContext::createMutableBinding(String *name, bool deletable) @@ -120,136 +157,97 @@ void ExecutionContext::createMutableBinding(String *name, bool deletable) while (ctx) { switch (ctx->d()->type) { case Heap::ExecutionContext::Type_CallContext: - case Heap::ExecutionContext::Type_SimpleCallContext: { - Heap::SimpleCallContext *c = static_cast<Heap::SimpleCallContext *>(ctx->d()); if (!activation) { + Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx->d()); if (!c->activation) c->activation.set(scope.engine, scope.engine->newObject()); activation = c->activation; } break; - } case Heap::ExecutionContext::Type_QmlContext: { // this is ugly, as it overrides the inner callcontext, but has to stay as long // as bindings still get their own callcontext - Heap::QmlContext *qml = static_cast<Heap::QmlContext *>(ctx->d()); - activation = qml->qml; + activation = ctx->d()->activation; break; } case Heap::ExecutionContext::Type_GlobalContext: { + Q_ASSERT(scope.engine->globalObject->d() == ctx->d()->activation); if (!activation) - activation = scope.engine->globalObject; + activation = ctx->d()->activation; break; } + case Heap::ExecutionContext::Type_BlockContext: + // never create activation records on block contexts default: break; } ctx = ctx->d()->outer; } - if (activation->hasOwnProperty(name)) + PropertyKey id = name->toPropertyKey(); + if (activation->getOwnProperty(id) != Attr_Invalid) return; ScopedProperty desc(scope); PropertyAttributes attrs(Attr_Data); attrs.setConfigurable(deletable); - activation->__defineOwnProperty__(scope.engine, name, desc, attrs); -} - -void Heap::GlobalContext::init(ExecutionEngine *eng) -{ - Heap::ExecutionContext::init(Heap::ExecutionContext::Type_GlobalContext); - global.set(eng, eng->globalObject->d()); -} - -void Heap::CatchContext::init(ExecutionContext *outerContext, String *exceptionVarName, - const Value &exceptionValue) -{ - Heap::ExecutionContext::init(Heap::ExecutionContext::Type_CatchContext); - outer.set(internalClass->engine, outerContext); - strictMode = outer->strictMode; - callData = outer->callData; - lookups = outer->lookups; - constantTable = outer->constantTable; - compilationUnit = outer->compilationUnit; - - this->exceptionVarName.set(internalClass->engine, exceptionVarName); - this->exceptionValue.set(internalClass->engine, exceptionValue); -} - -void Heap::WithContext::init(ExecutionContext *outerContext, Object *with) -{ - Heap::ExecutionContext::init(Heap::ExecutionContext::Type_WithContext); - outer.set(internalClass->engine, outerContext); - callData = outer->callData; - lookups = outer->lookups; - constantTable = outer->constantTable; - compilationUnit = outer->compilationUnit; - - withObject.set(internalClass->engine, with); -} - -Identifier * const *SimpleCallContext::formals() const -{ - return d()->v4Function ? d()->v4Function->internalClass->nameMap.constData() : 0; -} - -unsigned int SimpleCallContext::formalCount() const -{ - return d()->v4Function ? d()->v4Function->nFormals : 0; + if (!activation->defineOwnProperty(id, desc, attrs)) + scope.engine->throwTypeError(); } -Identifier * const *SimpleCallContext::variables() const +static bool unscopable(ExecutionEngine *engine, Heap::Object *withObject, PropertyKey id) { - return d()->v4Function ? d()->v4Function->internalClass->nameMap.constData() + d()->v4Function->nFormals : 0; -} - -unsigned int SimpleCallContext::variableCount() const -{ - return d()->v4Function ? d()->v4Function->compiledFunction->nLocals : 0; + if (!withObject) + return false; + Scope scope(engine); + ScopedObject w(scope, withObject); + ScopedObject o(scope, w->get(scope.engine->symbol_unscopables())); + if (o) { + ScopedValue blocked(scope, o->get(id)); + return blocked->toBoolean(); + } + return false; } - - bool ExecutionContext::deleteProperty(String *name) { - name->makeIdentifier(); - Identifier *id = name->identifier(); + PropertyKey id = name->toPropertyKey(); - Scope scope(this); - ScopedContext ctx(scope, this); - for (; ctx; ctx = ctx->d()->outer) { - switch (ctx->d()->type) { - case Heap::ExecutionContext::Type_CatchContext: { - Heap::CatchContext *c = static_cast<Heap::CatchContext *>(ctx->d()); - if (c->exceptionVarName->isEqualTo(name->d())) - return false; - break; - } - case Heap::ExecutionContext::Type_WithContext: { - ScopedObject withObject(scope, static_cast<Heap::WithContext *>(ctx->d())->withObject); - if (withObject->hasProperty(name)) - return withObject->deleteProperty(name); - break; - } - case Heap::ExecutionContext::Type_GlobalContext: { - ScopedObject global(scope, static_cast<Heap::GlobalContext *>(ctx->d())->global); - if (global->hasProperty(name)) - return global->deleteProperty(name); - break; - } + Heap::ExecutionContext *ctx = d(); + ExecutionEngine *engine = ctx->internalClass->engine; + + for (; ctx; ctx = ctx->outer) { + switch (ctx->type) { + case Heap::ExecutionContext::Type_BlockContext: case Heap::ExecutionContext::Type_CallContext: { - Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx->d()); - uint index = c->v4Function->internalClass->find(id); + Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); + uint index = c->internalClass->indexOfValueOrGetter(id); if (index < UINT_MAX) // ### throw in strict mode? return false; Q_FALLTHROUGH(); } - case Heap::ExecutionContext::Type_SimpleCallContext: { - Heap::SimpleCallContext *c = static_cast<Heap::SimpleCallContext *>(ctx->d()); - ScopedObject qml(scope, c->activation); - if (qml && qml->hasProperty(name)) - return qml->deleteProperty(name); + case Heap::ExecutionContext::Type_WithContext: { + if (ctx->activation) { + Scope scope(this); + ScopedObject object(scope, ctx->activation); + if (object && object->hasProperty(id)) { + bool u = ::unscopable(engine, ctx->activation, id); + if (engine->hasException) + return false; + if (u) + break; + return object->deleteProperty(id); + } + } + break; + } + case Heap::ExecutionContext::Type_GlobalContext: { + if (ctx->activation) { + Scope scope(this); + ScopedObject object(scope, ctx->activation); + if (object && object->hasProperty(id)) + return object->deleteProperty(id); + } break; } case Heap::ExecutionContext::Type_QmlContext: @@ -258,302 +256,180 @@ bool ExecutionContext::deleteProperty(String *name) } } - if (d()->strictMode) - engine()->throwSyntaxError(QStringLiteral("Can't delete property %1").arg(name->toQString())); - return true; -} - -// Do a standard call with this execution context as the outer scope -void ExecutionContext::call(Scope &scope, CallData *callData, Function *function, const FunctionObject *f) -{ - ExecutionContextSaver ctxSaver(scope); - - Scoped<CallContext> ctx(scope, newCallContext(function, callData)); - if (f) - ctx->d()->function.set(scope.engine, f->d()); - scope.engine->pushContext(ctx); - - scope.result = Q_V4_PROFILE(scope.engine, function); - - if (function->hasQmlDependencies) - QQmlPropertyCapture::registerQmlDependencies(function->compiledFunction, scope); -} - -// Do a simple, fast call with this execution context as the outer scope -void QV4::ExecutionContext::simpleCall(Scope &scope, CallData *callData, Function *function) -{ - Q_ASSERT(function->canUseSimpleFunction()); - - ExecutionContextSaver ctxSaver(scope); - - SimpleCallContext::Data *ctx = scope.engine->memoryManager->allocSimpleCallContext(); - - ctx->strictMode = function->isStrict(); - ctx->callData = callData; - ctx->v4Function = function; - ctx->compilationUnit = function->compilationUnit; - ctx->lookups = function->compilationUnit->runtimeLookups; - ctx->constantTable = function->compilationUnit->constants; - ctx->outer.set(scope.engine, this->d()); - for (int i = callData->argc; i < (int)function->nFormals; ++i) - callData->args[i] = Encode::undefined(); - - scope.engine->pushContext(ctx); - Q_ASSERT(scope.engine->current == ctx); - - scope.result = Q_V4_PROFILE(scope.engine, function); - - if (function->hasQmlDependencies) - QQmlPropertyCapture::registerQmlDependencies(function->compiledFunction, scope); - scope.engine->memoryManager->freeSimpleCallContext(); + return !engine->currentStackFrame->v4Function->isStrict(); } -void ExecutionContext::setProperty(String *name, const Value &value) +ExecutionContext::Error ExecutionContext::setProperty(String *name, const Value &value) { - name->makeIdentifier(); - Identifier *id = name->identifier(); + PropertyKey id = name->toPropertyKey(); - Scope scope(this); - ScopedContext ctx(scope, this); - ScopedObject activation(scope); + Heap::ExecutionContext *ctx = d(); + QV4::ExecutionEngine *engine = ctx->internalClass->engine; - for (; ctx; ctx = ctx->d()->outer) { - activation = (Object *)0; - switch (ctx->d()->type) { - case Heap::ExecutionContext::Type_CatchContext: { - Heap::CatchContext *c = static_cast<Heap::CatchContext *>(ctx->d()); - if (c->exceptionVarName->isEqualTo(name->d())) { - c->exceptionValue.set(scope.engine, value); - return; - } - break; - } + for (; ctx; ctx = ctx->outer) { + switch (ctx->type) { case Heap::ExecutionContext::Type_WithContext: { - ScopedObject w(scope, static_cast<Heap::WithContext *>(ctx->d())->withObject); - if (w->hasProperty(name)) { - w->put(name, value); - return; + Scope scope(engine); + ScopedObject w(scope, ctx->activation); + if (w->hasProperty(id)) { + bool u = ::unscopable(engine, ctx->activation, id); + if (engine->hasException) + return TypeError; + if (u) + break; + if (!w->put(name, value)) + return TypeError; + return NoError; } break; } - case Heap::ExecutionContext::Type_GlobalContext: { - activation = static_cast<Heap::GlobalContext *>(ctx->d())->global; - break; + case Heap::ExecutionContext::Type_BlockContext: + case Heap::ExecutionContext::Type_CallContext: { + Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); + uint index = c->internalClass->indexOfValueOrGetter(id); + if (index < UINT_MAX) { + static_cast<Heap::CallContext *>(c)->locals.set(engine, index, value); + return NoError; + } } - case Heap::ExecutionContext::Type_CallContext: - case Heap::ExecutionContext::Type_SimpleCallContext: { - Heap::SimpleCallContext *c = static_cast<Heap::SimpleCallContext *>(ctx->d()); - if (c->v4Function) { - uint index = c->v4Function->internalClass->find(id); - if (index < UINT_MAX) { - if (index < c->v4Function->nFormals) { - c->callData->args[c->v4Function->nFormals - index - 1] = value; - } else { - Q_ASSERT(c->type == Heap::ExecutionContext::Type_CallContext); - index -= c->v4Function->nFormals; - static_cast<Heap::CallContext *>(c)->locals.set(scope.engine, index, value); - } - return; + Q_FALLTHROUGH(); + case Heap::ExecutionContext::Type_GlobalContext: + if (ctx->activation) { + auto member = ctx->activation->internalClass->findValueOrSetter(id); + if (member.index < UINT_MAX) { + Scope scope(engine); + ScopedObject a(scope, ctx->activation); + if (!a->putValue(member.index, member.attrs, value)) + return TypeError; + return NoError; } } - activation = c->activation; break; - } case Heap::ExecutionContext::Type_QmlContext: { - activation = static_cast<Heap::QmlContext *>(ctx->d())->qml; - activation->put(name, value); - return; + Scope scope(engine); + ScopedObject activation(scope, ctx->activation); + if (!activation->put(name, value)) + return TypeError; + return NoError; } } - if (activation) { - uint member = activation->internalClass()->find(id); - if (member < UINT_MAX) { - activation->putValue(member, value); - return; - } - } } - if (d()->strictMode || name->equals(engine()->id_this())) { - ScopedValue n(scope, name->asReturnedValue()); - engine()->throwReferenceError(n); - return; - } - engine()->globalObject->put(name, value); + return RangeError; } ReturnedValue ExecutionContext::getProperty(String *name) { - Scope scope(this); - ScopedValue v(scope); - name->makeIdentifier(); + PropertyKey id = name->toPropertyKey(); - if (name->equals(engine()->id_this())) - return thisObject().asReturnedValue(); + Heap::ExecutionContext *ctx = d(); + QV4::ExecutionEngine *engine = ctx->internalClass->engine; - ScopedContext ctx(scope, this); - for (; ctx; ctx = ctx->d()->outer) { - switch (ctx->d()->type) { - case Heap::ExecutionContext::Type_CatchContext: { - Heap::CatchContext *c = static_cast<Heap::CatchContext *>(ctx->d()); - if (c->exceptionVarName->isEqualTo(name->d())) - return c->exceptionValue.asReturnedValue(); - break; - } - case Heap::ExecutionContext::Type_WithContext: { - ScopedObject w(scope, static_cast<Heap::WithContext *>(ctx->d())->withObject); - bool hasProperty = false; - v = w->get(name, &hasProperty); - if (hasProperty) { - return v->asReturnedValue(); - } - break; - } - case Heap::ExecutionContext::Type_GlobalContext: { - ScopedObject global(scope, static_cast<Heap::GlobalContext *>(ctx->d())->global); - bool hasProperty = false; - v = global->get(name, &hasProperty); - if (hasProperty) - return v->asReturnedValue(); - break; - } + for (; ctx; ctx = ctx->outer) { + switch (ctx->type) { + case Heap::ExecutionContext::Type_BlockContext: case Heap::ExecutionContext::Type_CallContext: { - Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx->d()); - name->makeIdentifier(); - Identifier *id = name->identifier(); + Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); - uint index = c->v4Function->internalClass->find(id); - if (index < UINT_MAX) { - if (index < c->v4Function->nFormals) - return c->callData->args[c->v4Function->nFormals - index - 1].asReturnedValue(); - Q_ASSERT(c->type == Heap::ExecutionContext::Type_CallContext); - return c->locals[index - c->v4Function->nFormals].asReturnedValue(); - } - if (c->v4Function->isNamedExpression()) { - if (c->function && name->equals(ScopedString(scope, c->v4Function->name()))) - return c->function->asReturnedValue(); - } + uint index = c->internalClass->indexOfValueOrGetter(id); + if (index < UINT_MAX) + return c->locals[index].asReturnedValue(); Q_FALLTHROUGH(); } - case Heap::ExecutionContext::Type_SimpleCallContext: { - Heap::SimpleCallContext *c = static_cast<Heap::SimpleCallContext *>(ctx->d()); - ScopedObject activation(scope, c->activation); - if (activation) { - bool hasProperty = false; - v = activation->get(name, &hasProperty); - if (hasProperty) - return v->asReturnedValue(); + case Heap::ExecutionContext::Type_WithContext: + if (ctx->activation) { + Scope scope(this); + ScopedObject activation(scope, ctx->activation); + if (activation->hasProperty(id)) { + bool u = ::unscopable(engine, ctx->activation, id); + if (engine->hasException) + return false; + if (u) + break; + return activation->get(id); + } } break; - } + case Heap::ExecutionContext::Type_GlobalContext: case Heap::ExecutionContext::Type_QmlContext: { - ScopedObject qml(scope, static_cast<Heap::QmlContext *>(ctx->d())->qml); - bool hasProperty = false; - v = qml->get(name, &hasProperty); - if (hasProperty) - return v->asReturnedValue(); + if (ctx->activation) { + Scope scope(this); + ScopedObject activation(scope, ctx->activation); + bool hasProperty = false; + ReturnedValue v = activation->get(id, nullptr, &hasProperty); + if (hasProperty) + return v; + } break; } } } - ScopedValue n(scope, name); - return engine()->throwReferenceError(n); + return engine->throwReferenceError(*name); } ReturnedValue ExecutionContext::getPropertyAndBase(String *name, Value *base) { - Scope scope(this); - ScopedValue v(scope); - base->setM(0); - name->makeIdentifier(); + base->setM(nullptr); + PropertyKey id = name->toPropertyKey(); - if (name->equals(engine()->id_this())) - return thisObject().asReturnedValue(); + Heap::ExecutionContext *ctx = d(); + QV4::ExecutionEngine *engine = ctx->internalClass->engine; - ScopedContext ctx(scope, this); - for (; ctx; ctx = ctx->d()->outer) { - switch (ctx->d()->type) { - case Heap::ExecutionContext::Type_CatchContext: { - Heap::CatchContext *c = static_cast<Heap::CatchContext *>(ctx->d()); - if (c->exceptionVarName->isEqualTo(name->d())) - return c->exceptionValue.asReturnedValue(); - break; - } - case Heap::ExecutionContext::Type_WithContext: { - ScopedObject w(scope, static_cast<Heap::WithContext *>(ctx->d())->withObject); - bool hasProperty = false; - v = w->get(name, &hasProperty); - if (hasProperty) { - base->setM(w->d()); - return v->asReturnedValue(); - } - break; - } - case Heap::ExecutionContext::Type_GlobalContext: { - ScopedObject global(scope, static_cast<Heap::GlobalContext *>(ctx->d())->global); - bool hasProperty = false; - v = global->get(name, &hasProperty); - if (hasProperty) - return v->asReturnedValue(); - break; - } + for (; ctx; ctx = ctx->outer) { + switch (ctx->type) { + case Heap::ExecutionContext::Type_BlockContext: case Heap::ExecutionContext::Type_CallContext: { - Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx->d()); - name->makeIdentifier(); - Identifier *id = name->identifier(); + Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); - uint index = c->v4Function->internalClass->find(id); - if (index < UINT_MAX) { - if (index < c->v4Function->nFormals) - return c->callData->args[c->v4Function->nFormals - index - 1].asReturnedValue(); - return c->locals[index - c->v4Function->nFormals].asReturnedValue(); - } - if (c->v4Function->isNamedExpression()) { - if (c->function && name->equals(ScopedString(scope, c->v4Function->name()))) - return c->function->asReturnedValue(); - } + uint index = c->internalClass->indexOfValueOrGetter(id); + if (index < UINT_MAX) + return c->locals[index].asReturnedValue(); Q_FALLTHROUGH(); } - case Heap::ExecutionContext::Type_SimpleCallContext: { - Heap::SimpleCallContext *c = static_cast<Heap::SimpleCallContext *>(ctx->d()); - ScopedObject activation(scope, c->activation); - if (activation) { + case Heap::ExecutionContext::Type_GlobalContext: { + if (ctx->activation) { + Scope scope(this); + ScopedObject activation(scope, ctx->activation); bool hasProperty = false; - v = activation->get(name, &hasProperty); + ReturnedValue v = activation->get(name, &hasProperty); if (hasProperty) - return v->asReturnedValue(); + return v; } break; } + case Heap::ExecutionContext::Type_WithContext: + if (ctx->activation) { + Scope scope(this); + ScopedObject activation(scope, ctx->activation); + if (activation->hasProperty(id)) { + bool u = ::unscopable(engine, ctx->activation, id); + if (engine->hasException) + return false; + if (u) + break; + base->setM(activation->d()); + return activation->get(id); + } + } + break; case Heap::ExecutionContext::Type_QmlContext: { - ScopedObject qml(scope, static_cast<Heap::QmlContext *>(ctx->d())->qml); + Scope scope(this); + ScopedObject o(scope, ctx->activation); bool hasProperty = false; - v = qml->get(name, &hasProperty); + ReturnedValue v = o->get(id, nullptr, &hasProperty); if (hasProperty) { - base->setM(qml->d()); - return v->asReturnedValue(); + base->setM(o->d()); + return v; } break; } } } - ScopedValue n(scope, name); - return engine()->throwReferenceError(n); + return engine->throwReferenceError(*name); } -Function *ExecutionContext::getFunction() const +void Heap::CallContext::setArg(uint index, Value v) { - Scope scope(engine()); - ScopedContext it(scope, this->d()); - for (; it; it = it->d()->outer) { - if (const SimpleCallContext *callCtx = it->asSimpleCallContext()) - return callCtx->d()->v4Function; - else if (it->asCatchContext() || it->asWithContext()) - continue; // look in the parent context for a FunctionObject - else - break; - } - - return 0; + locals.set(internalClass->engine, locals.size + index, v); } diff --git a/src/qml/jsruntime/qv4context_p.h b/src/qml/jsruntime/qv4context_p.h index a854c324d0..75fa2d08e6 100644 --- a/src/qml/jsruntime/qv4context_p.h +++ b/src/qml/jsruntime/qv4context_p.h @@ -55,72 +55,24 @@ QT_BEGIN_NAMESPACE -class QObject; -class QQmlContextData; - namespace QV4 { -namespace CompiledData { -struct CompilationUnitBase; -struct Function; -} - -struct Function; -struct Identifier; -struct CallContext; -struct SimpleCallContext; -struct CatchContext; -struct WithContext; -struct QmlContext; -struct QQmlContextWrapper; - -// Attention: Make sure that this structure is the same size on 32-bit and 64-bit -// architecture or you'll have to change the JIT code. -struct CallData -{ - // below is to be compatible with Value. Initialize tag to 0 -#if Q_BYTE_ORDER != Q_LITTLE_ENDIAN - uint tag; -#endif - int argc; -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - uint tag; -#endif - inline ReturnedValue argument(int i) const { - return i < argc ? args[i].asReturnedValue() : Primitive::undefinedValue().asReturnedValue(); - } - - Value thisObject; - Value args[1]; -}; - -Q_STATIC_ASSERT(std::is_standard_layout<CallData>::value); -Q_STATIC_ASSERT(offsetof(CallData, thisObject) == 8); -Q_STATIC_ASSERT(offsetof(CallData, args) == 16); namespace Heap { -struct QmlContext; - #define ExecutionContextMembers(class, Member) \ - Member(class, NoMark, CallData *, callData) \ Member(class, Pointer, ExecutionContext *, outer) \ - Member(class, NoMark, Lookup *, lookups) \ - Member(class, NoMark, const QV4::Value *, constantTable) \ - Member(class, NoMark, CompiledData::CompilationUnitBase *, compilationUnit) \ - Member(class, NoMark, int, lineNumber) // as member of non-pointer size this has to come last to preserve the ability to - // translate offsetof of it between 64-bit and 32-bit. + Member(class, Pointer, Object *, activation) DECLARE_HEAP_OBJECT(ExecutionContext, Base) { - DECLARE_MARK_TABLE(ExecutionContext); + DECLARE_MARKOBJECTS(ExecutionContext); enum ContextType { Type_GlobalContext = 0x1, - Type_CatchContext = 0x2, - Type_WithContext = 0x3, - Type_QmlContext = 0x4, - Type_SimpleCallContext = 0x5, - Type_CallContext = 0x6 + Type_WithContext = 0x2, + Type_QmlContext = 0x3, + Type_BlockContext = 0x4, + Type_CallContext = 0x5 }; void init(ContextType t) @@ -128,104 +80,62 @@ DECLARE_HEAP_OBJECT(ExecutionContext, Base) { Base::init(); type = t; - lineNumber = -1; } - quint8 type; - bool strictMode : 8; + const VTable *vtable() const { + return internalClass->vtable; + } + + quint32 type : 8; + quint32 nArgs : 24; #if QT_POINTER_SIZE == 8 - quint8 padding_[6]; -#else - quint8 padding_[2]; + quint8 padding_[4]; #endif }; -V4_ASSERT_IS_TRIVIAL(ExecutionContext) +Q_STATIC_ASSERT(std::is_trivial< ExecutionContext >::value); Q_STATIC_ASSERT(sizeof(ExecutionContext) == sizeof(Base) + sizeof(ExecutionContextData) + QT_POINTER_SIZE); Q_STATIC_ASSERT(std::is_standard_layout<ExecutionContextData>::value); -Q_STATIC_ASSERT(offsetof(ExecutionContextData, callData) == 0); -Q_STATIC_ASSERT(offsetof(ExecutionContextData, outer) == offsetof(ExecutionContextData, callData) + QT_POINTER_SIZE); -Q_STATIC_ASSERT(offsetof(ExecutionContextData, lookups) == offsetof(ExecutionContextData, outer) + QT_POINTER_SIZE); -Q_STATIC_ASSERT(offsetof(ExecutionContextData, constantTable) == offsetof(ExecutionContextData, lookups) + QT_POINTER_SIZE); -Q_STATIC_ASSERT(offsetof(ExecutionContextData, compilationUnit) == offsetof(ExecutionContextData, constantTable) + QT_POINTER_SIZE); -Q_STATIC_ASSERT(offsetof(ExecutionContextData, lineNumber) == offsetof(ExecutionContextData, compilationUnit) + QT_POINTER_SIZE); +Q_STATIC_ASSERT(offsetof(ExecutionContextData, outer) == 0); +Q_STATIC_ASSERT(offsetof(ExecutionContextData, activation) == offsetof(ExecutionContextData, outer) + QT_POINTER_SIZE); -#define SimpleCallContextMembers(class, Member) \ - Member(class, Pointer, Object *, activation) \ - Member(class, NoMark, QV4::Function *, v4Function) +#define CallContextMembers(class, Member) \ + Member(class, Pointer, FunctionObject *, function) \ + Member(class, ValueArray, ValueArray, locals) -DECLARE_HEAP_OBJECT(SimpleCallContext, ExecutionContext) { - DECLARE_MARK_TABLE(SimpleCallContext); +DECLARE_HEAP_OBJECT(CallContext, ExecutionContext) { + DECLARE_MARKOBJECTS(CallContext); - void init(ContextType t = Type_SimpleCallContext) + void init() { - ExecutionContext::init(t); + ExecutionContext::init(Type_CallContext); } - inline unsigned int formalParameterCount() const; - -}; -V4_ASSERT_IS_TRIVIAL(SimpleCallContext) -Q_STATIC_ASSERT(std::is_standard_layout<SimpleCallContextData>::value); -Q_STATIC_ASSERT(offsetof(SimpleCallContextData, activation) == 0); -Q_STATIC_ASSERT(offsetof(SimpleCallContextData, v4Function) == offsetof(SimpleCallContextData, activation) + QT_POINTER_SIZE); -Q_STATIC_ASSERT(sizeof(SimpleCallContextData) == 2 * QT_POINTER_SIZE); -Q_STATIC_ASSERT(sizeof(SimpleCallContext) == sizeof(ExecutionContext) + sizeof(SimpleCallContextData)); - -#if QT_POINTER_SIZE == 8 -#define CallContextMembers(class, Member) \ - Member(class, Pointer, FunctionObject *, function) \ - Member(class, ValueArray, ValueArray, locals) -#else -#define CallContextMembers(class, Member) \ - Member(class, Pointer, FunctionObject *, function) \ - Member(class, NoMark, void *, padding) \ - Member(class, ValueArray, ValueArray, locals) -#endif - -DECLARE_HEAP_OBJECT(CallContext, SimpleCallContext) { - DECLARE_MARK_TABLE(CallContext); + int argc() const { + return static_cast<int>(nArgs); + } + const Value *args() const { + return locals.data() + locals.size; + } + void setArg(uint index, Value v); - using SimpleCallContext::formalParameterCount; + template <typename BlockOrFunction> + void setupLocalTemporalDeadZone(BlockOrFunction *bof) { + for (uint i = bof->nLocals - bof->sizeOfLocalTemporalDeadZone; i < bof->nLocals; ++i) + locals.values[i] = Value::emptyValue(); + } }; - +Q_STATIC_ASSERT(std::is_trivial< CallContext >::value); Q_STATIC_ASSERT(std::is_standard_layout<CallContextData>::value); Q_STATIC_ASSERT(offsetof(CallContextData, function) == 0); -// IMPORTANT: we cannot do offsetof(CallContextData, locals) in the JIT as the offset does not scale with -// the pointer size. On 32-bit ARM the offset of the ValueArray is aligned to 8 bytes, on 32-bit x86 for -// example it is not. Therefore we have a padding in place and always have a distance of 8 bytes. -Q_STATIC_ASSERT(offsetof(CallContextData, locals) == offsetof(CallContextData, function) + 8); - -#define GlobalContextMembers(class, Member) \ - Member(class, Pointer, Object *, global) - -DECLARE_HEAP_OBJECT(GlobalContext, ExecutionContext) { - DECLARE_MARK_TABLE(GlobalContext); +//### The following size check fails on Win8. With the ValueArray at the end of the +// CallContextMembers, it doesn't look very useful. +//#if defined(Q_PROCESSOR_ARM_32) && !defined(Q_OS_IOS) +//Q_STATIC_ASSERT(sizeof(CallContext) == sizeof(ExecutionContext) + sizeof(CallContextData) + QT_POINTER_SIZE); +//#else +//Q_STATIC_ASSERT(sizeof(CallContext) == sizeof(ExecutionContext) + sizeof(CallContextData)); +//#endif - void init(ExecutionEngine *engine); -}; -V4_ASSERT_IS_TRIVIAL(GlobalContext) - -#define CatchContextMembers(class, Member) \ - Member(class, Pointer, String *, exceptionVarName) \ - Member(class, HeapValue, HeapValue, exceptionValue) - -DECLARE_HEAP_OBJECT(CatchContext, ExecutionContext) { - DECLARE_MARK_TABLE(CatchContext); - - void init(ExecutionContext *outerContext, String *exceptionVarName, const Value &exceptionValue); -}; -V4_ASSERT_IS_TRIVIAL(CatchContext) - -#define WithContextMembers(class, Member) \ - Member(class, Pointer, Object *, withObject) - -DECLARE_HEAP_OBJECT(WithContext, ExecutionContext) { - DECLARE_MARK_TABLE(WithContext); - - void init(ExecutionContext *outerContext, Object *with); -}; -V4_ASSERT_IS_TRIVIAL(WithContext) } @@ -239,98 +149,58 @@ struct Q_QML_EXPORT ExecutionContext : public Managed Q_MANAGED_TYPE(ExecutionContext) V4_INTERNALCLASS(ExecutionContext) - Heap::CallContext *newCallContext(Function *f, CallData *callData); - Heap::WithContext *newWithContext(Heap::Object *with); - Heap::CatchContext *newCatchContext(Heap::String *exceptionVarName, ReturnedValue exceptionValue); + static Heap::CallContext *newBlockContext(QV4::CppStackFrame *frame, int blockIndex); + static Heap::CallContext *cloneBlockContext(ExecutionEngine *engine, + Heap::CallContext *callContext); + static Heap::CallContext *newCallContext(QV4::CppStackFrame *frame); + Heap::ExecutionContext *newWithContext(Heap::Object *with) const; + static Heap::ExecutionContext *newCatchContext(CppStackFrame *frame, int blockIndex, Heap::String *exceptionVarName); void createMutableBinding(String *name, bool deletable); - void setProperty(String *name, const Value &value); + enum Error { + NoError, + TypeError, + RangeError + }; + + Error setProperty(String *name, const Value &value); + ReturnedValue getProperty(String *name); ReturnedValue getPropertyAndBase(String *name, Value *base); bool deleteProperty(String *name); - inline SimpleCallContext *asSimpleCallContext(); - inline const SimpleCallContext *asSimpleCallContext() const; - inline const CatchContext *asCatchContext() const; - inline const WithContext *asWithContext() const; - - Function *getFunction() const; + inline CallContext *asCallContext(); + inline const CallContext *asCallContext() const; - Value &thisObject() const { - return d()->callData->thisObject; - } - int argc() const { - return d()->callData->argc; +protected: + // vtable method required for compilation + static bool virtualDeleteProperty(Managed *, PropertyKey) { + Q_UNREACHABLE(); } - const Value *args() const { - return d()->callData->args; - } - ReturnedValue argument(int i) const { - return d()->callData->argument(i); - } - - void call(Scope &scope, CallData *callData, QV4::Function *function, const QV4::FunctionObject *f = 0); - void simpleCall(Scope &scope, CallData *callData, QV4::Function *function); -}; - -struct Q_QML_EXPORT SimpleCallContext : public ExecutionContext -{ - V4_MANAGED(SimpleCallContext, ExecutionContext) - V4_INTERNALCLASS(SimpleCallContext) - - // formals are in reverse order - Identifier * const *formals() const; - unsigned int formalCount() const; - Identifier * const *variables() const; - unsigned int variableCount() const; - - inline ReturnedValue argument(int i) const; -}; - -inline ReturnedValue SimpleCallContext::argument(int i) const { - return i < argc() ? args()[i].asReturnedValue() : Primitive::undefinedValue().asReturnedValue(); -} - -struct Q_QML_EXPORT CallContext : public SimpleCallContext -{ - V4_MANAGED(CallContext, SimpleCallContext) -}; - -struct GlobalContext : public ExecutionContext -{ - V4_MANAGED(GlobalContext, ExecutionContext) - }; -struct CatchContext : public ExecutionContext +struct Q_QML_EXPORT CallContext : public ExecutionContext { - V4_MANAGED(CatchContext, ExecutionContext) -}; + V4_MANAGED(CallContext, ExecutionContext) + V4_INTERNALCLASS(CallContext) -struct WithContext : public ExecutionContext -{ - V4_MANAGED(WithContext, ExecutionContext) + int argc() const { + return d()->argc(); + } + const Value *args() const { + return d()->args(); + } }; -inline SimpleCallContext *ExecutionContext::asSimpleCallContext() -{ - return d()->type >= Heap::ExecutionContext::Type_SimpleCallContext ? static_cast<SimpleCallContext *>(this) : 0; -} - -inline const SimpleCallContext *ExecutionContext::asSimpleCallContext() const -{ - return d()->type >= Heap::ExecutionContext::Type_SimpleCallContext ? static_cast<const SimpleCallContext *>(this) : 0; -} - -inline const CatchContext *ExecutionContext::asCatchContext() const +inline CallContext *ExecutionContext::asCallContext() { - return d()->type == Heap::ExecutionContext::Type_CatchContext ? static_cast<const CatchContext *>(this) : 0; + return d()->type == Heap::ExecutionContext::Type_CallContext ? static_cast<CallContext *>(this) : nullptr; } -inline const WithContext *ExecutionContext::asWithContext() const +inline const CallContext *ExecutionContext::asCallContext() const { - return d()->type == Heap::ExecutionContext::Type_WithContext ? static_cast<const WithContext *>(this) : 0; + return d()->type == Heap::ExecutionContext::Type_CallContext ? static_cast<const CallContext *>(this) : nullptr; } } // namespace QV4 diff --git a/src/qml/jsruntime/qv4dataview.cpp b/src/qml/jsruntime/qv4dataview.cpp index f1405e08ee..5ab8cf2dcb 100644 --- a/src/qml/jsruntime/qv4dataview.cpp +++ b/src/qml/jsruntime/qv4dataview.cpp @@ -40,6 +40,7 @@ #include "qv4dataview_p.h" #include "qv4arraybuffer_p.h" #include "qv4string_p.h" +#include "qv4symbol_p.h" #include <QtCore/private/qnumeric_p.h> #include "qendian.h" @@ -54,152 +55,195 @@ void Heap::DataViewCtor::init(QV4::ExecutionContext *scope) Heap::FunctionObject::init(scope, QStringLiteral("DataView")); } -void DataViewCtor::construct(const Managed *, Scope &scope, CallData *callData) +static uint toIndex(ExecutionEngine *e, const Value &v) { - Scoped<ArrayBuffer> buffer(scope, callData->argument(0)); - if (!buffer) { - scope.result = scope.engine->throwTypeError(); - return; + if (v.isUndefined()) + return 0; + double index = v.toInteger(); + if (index < 0) { + e->throwRangeError(QStringLiteral("index out of range")); + return 0; } + uint idx = static_cast<uint>(index); + if (idx != index) { + e->throwRangeError(QStringLiteral("index out of range")); + return 0; + } + return idx; +} + +ReturnedValue DataViewCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + Scope scope(f->engine()); + Scoped<SharedArrayBuffer> buffer(scope, argc ? argv[0] : Value::undefinedValue()); + if (!newTarget || !buffer) + return scope.engine->throwTypeError(); + + uint offset = ::toIndex(scope.engine, argc > 1 ? argv[1]: Value::undefinedValue()); + if (scope.hasException()) + return Encode::undefined(); + if (buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); - double bo = callData->argc > 1 ? callData->args[1].toNumber() : 0; - uint byteOffset = (uint)bo; uint bufferLength = buffer->d()->data->size; - double bl = callData->argc < 3 || callData->args[2].isUndefined() ? (bufferLength - bo) : callData->args[2].toNumber(); - uint byteLength = (uint)bl; - if (bo != byteOffset || bl != byteLength || byteOffset + byteLength > bufferLength) { - scope.result = scope.engine->throwRangeError(QStringLiteral("DataView: constructor arguments out of range")); - return; - } + if (offset > bufferLength) + return scope.engine->throwRangeError(QStringLiteral("DataView: constructor arguments out of range")); + + uint byteLength = (argc < 3 || argv[2].isUndefined()) ? (bufferLength - offset) : ::toIndex(scope.engine, argv[2]); + if (scope.hasException()) + return Encode::undefined(); + if (offset + byteLength > bufferLength) + return scope.engine->throwRangeError(QStringLiteral("DataView: constructor arguments out of range")); - Scoped<DataView> a(scope, scope.engine->memoryManager->allocObject<DataView>()); + Scoped<DataView> a(scope, scope.engine->memoryManager->allocate<DataView>()); a->d()->buffer.set(scope.engine, buffer->d()); a->d()->byteLength = byteLength; - a->d()->byteOffset = byteOffset; - scope.result = a.asReturnedValue(); + a->d()->byteOffset = offset; + return a.asReturnedValue(); } -void DataViewCtor::call(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue DataViewCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) { - construct(that, scope, callData); + return f->engine()->throwTypeError(); } void DataViewPrototype::init(ExecutionEngine *engine, Object *ctor) { Scope scope(engine); ScopedObject o(scope); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(3)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); defineDefaultProperty(engine->id_constructor(), (o = ctor)); - defineAccessorProperty(QStringLiteral("buffer"), method_get_buffer, 0); - defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, 0); - defineAccessorProperty(QStringLiteral("byteOffset"), method_get_byteOffset, 0); - - defineDefaultProperty(QStringLiteral("getInt8"), method_getChar<signed char>, 0); - defineDefaultProperty(QStringLiteral("getUint8"), method_getChar<unsigned char>, 0); - defineDefaultProperty(QStringLiteral("getInt16"), method_get<short>, 0); - defineDefaultProperty(QStringLiteral("getUint16"), method_get<unsigned short>, 0); - defineDefaultProperty(QStringLiteral("getInt32"), method_get<int>, 0); - defineDefaultProperty(QStringLiteral("getUint32"), method_get<unsigned int>, 0); - defineDefaultProperty(QStringLiteral("getFloat32"), method_getFloat<float>, 0); - defineDefaultProperty(QStringLiteral("getFloat64"), method_getFloat<double>, 0); - - defineDefaultProperty(QStringLiteral("setInt8"), method_setChar<signed char>, 0); - defineDefaultProperty(QStringLiteral("setUint8"), method_setChar<unsigned char>, 0); - defineDefaultProperty(QStringLiteral("setInt16"), method_set<short>, 0); - defineDefaultProperty(QStringLiteral("setUint16"), method_set<unsigned short>, 0); - defineDefaultProperty(QStringLiteral("setInt32"), method_set<int>, 0); - defineDefaultProperty(QStringLiteral("setUint32"), method_set<unsigned int>, 0); - defineDefaultProperty(QStringLiteral("setFloat32"), method_setFloat<float>, 0); - defineDefaultProperty(QStringLiteral("setFloat64"), method_setFloat<double>, 0); + defineAccessorProperty(QStringLiteral("buffer"), method_get_buffer, nullptr); + defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, nullptr); + defineAccessorProperty(QStringLiteral("byteOffset"), method_get_byteOffset, nullptr); + + defineDefaultProperty(QStringLiteral("getInt8"), method_getChar<signed char>, 1); + defineDefaultProperty(QStringLiteral("getUint8"), method_getChar<unsigned char>, 1); + defineDefaultProperty(QStringLiteral("getInt16"), method_get<short>, 1); + defineDefaultProperty(QStringLiteral("getUint16"), method_get<unsigned short>, 1); + defineDefaultProperty(QStringLiteral("getInt32"), method_get<int>, 1); + defineDefaultProperty(QStringLiteral("getUint32"), method_get<unsigned int>, 1); + defineDefaultProperty(QStringLiteral("getFloat32"), method_getFloat<float>, 1); + defineDefaultProperty(QStringLiteral("getFloat64"), method_getFloat<double>, 1); + + defineDefaultProperty(QStringLiteral("setInt8"), method_setChar<signed char>, 2); + defineDefaultProperty(QStringLiteral("setUint8"), method_setChar<unsigned char>, 2); + defineDefaultProperty(QStringLiteral("setInt16"), method_set<short>, 2); + defineDefaultProperty(QStringLiteral("setUint16"), method_set<unsigned short>, 2); + defineDefaultProperty(QStringLiteral("setInt32"), method_set<int>, 2); + defineDefaultProperty(QStringLiteral("setUint32"), method_set<unsigned int>, 2); + defineDefaultProperty(QStringLiteral("setFloat32"), method_setFloat<float>, 2); + defineDefaultProperty(QStringLiteral("setFloat64"), method_setFloat<double>, 2); + + ScopedString name(scope, engine->newString(QStringLiteral("DataView"))); + defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), name); // For backword compatibility - defineDefaultProperty(QStringLiteral("getUInt8"), method_getChar<unsigned char>, 0); - defineDefaultProperty(QStringLiteral("getUInt16"), method_get<unsigned short>, 0); - defineDefaultProperty(QStringLiteral("getUInt32"), method_get<unsigned int>, 0); - defineDefaultProperty(QStringLiteral("setUInt8"), method_setChar<unsigned char>, 0); - defineDefaultProperty(QStringLiteral("setUInt16"), method_set<unsigned short>, 0); - defineDefaultProperty(QStringLiteral("setUInt32"), method_set<unsigned int>, 0); + defineDefaultProperty(QStringLiteral("getUInt8"), method_getChar<unsigned char>, 1); + defineDefaultProperty(QStringLiteral("getUInt16"), method_get<unsigned short>, 1); + defineDefaultProperty(QStringLiteral("getUInt32"), method_get<unsigned int>, 1); + defineDefaultProperty(QStringLiteral("setUInt8"), method_setChar<unsigned char>, 1); + defineDefaultProperty(QStringLiteral("setUInt16"), method_set<unsigned short>, 1); + defineDefaultProperty(QStringLiteral("setUInt32"), method_set<unsigned int>, 1); } -void DataViewPrototype::method_get_buffer(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_get_buffer(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<DataView> v(scope, callData->thisObject); + const DataView *v = thisObject->as<DataView>(); if (!v) - THROW_TYPE_ERROR(); + return b->engine()->throwTypeError(); - scope.result = v->d()->buffer; + return v->d()->buffer->asReturnedValue(); } -void DataViewPrototype::method_get_byteLength(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_get_byteLength(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<DataView> v(scope, callData->thisObject); + const DataView *v = thisObject->as<DataView>(); if (!v) - THROW_TYPE_ERROR(); + return b->engine()->throwTypeError(); - scope.result = Encode(v->d()->byteLength); + if (v->d()->buffer->isDetachedBuffer()) + return b->engine()->throwTypeError(); + + return Encode(v->d()->byteLength); } -void DataViewPrototype::method_get_byteOffset(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_get_byteOffset(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<DataView> v(scope, callData->thisObject); + const DataView *v = thisObject->as<DataView>(); if (!v) - THROW_TYPE_ERROR(); + return b->engine()->throwTypeError(); + + if (v->d()->buffer->isDetachedBuffer()) + return b->engine()->throwTypeError(); - scope.result = Encode(v->d()->byteOffset); + return Encode(v->d()->byteOffset); } template <typename T> -void DataViewPrototype::method_getChar(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_getChar(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<DataView> v(scope, callData->thisObject); - if (!v || callData->argc < 1) - THROW_TYPE_ERROR(); - double l = callData->args[0].toNumber(); - uint idx = (uint)l; - if (l != idx || idx + sizeof(T) > v->d()->byteLength) - THROW_TYPE_ERROR(); + ExecutionEngine *e = b->engine(); + const DataView *v = thisObject->as<DataView>(); + if (!v) + return e->throwTypeError(); + uint idx = ::toIndex(e, argc ? argv[0] : Value::undefinedValue()); + if (e->hasException) + return Encode::undefined(); + if (v->d()->buffer->isDetachedBuffer()) + return e->throwTypeError(); + if (idx + sizeof(T) > v->d()->byteLength) + return e->throwRangeError(QStringLiteral("index out of range")); idx += v->d()->byteOffset; T t = T(v->d()->buffer->data->data()[idx]); - scope.result = Encode((int)t); + return Encode((int)t); } template <typename T> -void DataViewPrototype::method_get(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_get(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<DataView> v(scope, callData->thisObject); - if (!v || callData->argc < 1) - THROW_TYPE_ERROR(); - double l = callData->args[0].toNumber(); - uint idx = (uint)l; - if (l != idx || idx + sizeof(T) > v->d()->byteLength) - THROW_TYPE_ERROR(); + ExecutionEngine *e = b->engine(); + const DataView *v = thisObject->as<DataView>(); + if (!v) + return e->throwTypeError(); + uint idx = ::toIndex(e, argc ? argv[0] : Value::undefinedValue()); + if (e->hasException) + return Encode::undefined(); + if (v->d()->buffer->isDetachedBuffer()) + return e->throwTypeError(); + if (idx + sizeof(T) > v->d()->byteLength) + return e->throwRangeError(QStringLiteral("index out of range")); idx += v->d()->byteOffset; - bool littleEndian = callData->argc < 2 ? false : callData->args[1].toBoolean(); + bool littleEndian = argc < 2 ? false : argv[1].toBoolean(); T t = littleEndian ? qFromLittleEndian<T>((uchar *)v->d()->buffer->data->data() + idx) : qFromBigEndian<T>((uchar *)v->d()->buffer->data->data() + idx); - scope.result = Encode(t); + return Encode(t); } template <typename T> -void DataViewPrototype::method_getFloat(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_getFloat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<DataView> v(scope, callData->thisObject); - if (!v || callData->argc < 1) - THROW_TYPE_ERROR(); - double l = callData->args[0].toNumber(); - uint idx = (uint)l; - if (l != idx || idx + sizeof(T) > v->d()->byteLength) - THROW_TYPE_ERROR(); + ExecutionEngine *e = b->engine(); + const DataView *v = thisObject->as<DataView>(); + if (!v) + return e->throwTypeError(); + uint idx = ::toIndex(e, argc ? argv[0] : Value::undefinedValue()); + if (e->hasException) + return Encode::undefined(); + if (v->d()->buffer->isDetachedBuffer()) + return e->throwTypeError(); + if (idx + sizeof(T) > v->d()->byteLength) + return e->throwRangeError(QStringLiteral("index out of range")); idx += v->d()->byteOffset; - bool littleEndian = callData->argc < 2 ? false : callData->args[1].toBoolean(); + bool littleEndian = argc < 2 ? false : argv[1].toBoolean(); if (sizeof(T) == 4) { // float @@ -210,7 +254,7 @@ void DataViewPrototype::method_getFloat(const BuiltinFunction *, Scope &scope, C u.i = littleEndian ? qFromLittleEndian<uint>((uchar *)v->d()->buffer->data->data() + idx) : qFromBigEndian<uint>((uchar *)v->d()->buffer->data->data() + idx); - scope.result = Encode(u.f); + return Encode(u.f); } else { Q_ASSERT(sizeof(T) == 8); union { @@ -220,43 +264,56 @@ void DataViewPrototype::method_getFloat(const BuiltinFunction *, Scope &scope, C u.i = littleEndian ? qFromLittleEndian<quint64>((uchar *)v->d()->buffer->data->data() + idx) : qFromBigEndian<quint64>((uchar *)v->d()->buffer->data->data() + idx); - scope.result = Encode(u.d); + return Encode(u.d); } } template <typename T> -void DataViewPrototype::method_setChar(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_setChar(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<DataView> v(scope, callData->thisObject); - if (!v || callData->argc < 1) - THROW_TYPE_ERROR(); - double l = callData->args[0].toNumber(); - uint idx = (uint)l; - if (l != idx || idx + sizeof(T) > v->d()->byteLength) - THROW_TYPE_ERROR(); + ExecutionEngine *e = b->engine(); + const DataView *v = thisObject->as<DataView>(); + if (!v) + return e->throwTypeError(); + uint idx = ::toIndex(e, argc ? argv[0] : Value::undefinedValue()); + if (e->hasException) + return Encode::undefined(); + + int val = argc >= 2 ? argv[1].toInt32() : 0; + + if (v->d()->buffer->isDetachedBuffer()) + return e->throwTypeError(); + + if (idx + sizeof(T) > v->d()->byteLength) + return e->throwRangeError(QStringLiteral("index out of range")); idx += v->d()->byteOffset; - int val = callData->argc >= 2 ? callData->args[1].toInt32() : 0; v->d()->buffer->data->data()[idx] = (char)val; RETURN_UNDEFINED(); } template <typename T> -void DataViewPrototype::method_set(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<DataView> v(scope, callData->thisObject); - if (!v || callData->argc < 1) - THROW_TYPE_ERROR(); - double l = callData->args[0].toNumber(); - uint idx = (uint)l; - if (l != idx || idx + sizeof(T) > v->d()->byteLength) - THROW_TYPE_ERROR(); - idx += v->d()->byteOffset; + ExecutionEngine *e = b->engine(); + const DataView *v = thisObject->as<DataView>(); + if (!v) + return e->throwTypeError(); + uint idx = ::toIndex(e, argc ? argv[0] : Value::undefinedValue()); + if (e->hasException) + return Encode::undefined(); + + int val = argc >= 2 ? argv[1].toInt32() : 0; + bool littleEndian = argc < 3 ? false : argv[2].toBoolean(); - int val = callData->argc >= 2 ? callData->args[1].toInt32() : 0; + if (v->d()->buffer->isDetachedBuffer()) + return e->throwTypeError(); + + if (idx + sizeof(T) > v->d()->byteLength) + return e->throwRangeError(QStringLiteral("index out of range")); + idx += v->d()->byteOffset; - bool littleEndian = callData->argc < 3 ? false : callData->args[2].toBoolean(); if (littleEndian) qToLittleEndian<T>(val, (uchar *)v->d()->buffer->data->data() + idx); @@ -267,19 +324,25 @@ void DataViewPrototype::method_set(const BuiltinFunction *, Scope &scope, CallDa } template <typename T> -void DataViewPrototype::method_setFloat(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DataViewPrototype::method_setFloat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<DataView> v(scope, callData->thisObject); - if (!v || callData->argc < 1) - THROW_TYPE_ERROR(); - double l = callData->args[0].toNumber(); - uint idx = (uint)l; - if (l != idx || idx + sizeof(T) > v->d()->byteLength) - THROW_TYPE_ERROR(); - idx += v->d()->byteOffset; + ExecutionEngine *e = b->engine(); + const DataView *v = thisObject->as<DataView>(); + if (!v) + return e->throwTypeError(); + uint idx = ::toIndex(e, argc ? argv[0] : Value::undefinedValue()); + if (e->hasException) + return Encode::undefined(); - double val = callData->argc >= 2 ? callData->args[1].toNumber() : qt_qnan(); - bool littleEndian = callData->argc < 3 ? false : callData->args[2].toBoolean(); + double val = argc >= 2 ? argv[1].toNumber() : qt_qnan(); + bool littleEndian = argc < 3 ? false : argv[2].toBoolean(); + + if (v->d()->buffer->isDetachedBuffer()) + return e->throwTypeError(); + + if (idx + sizeof(T) > v->d()->byteLength) + return e->throwRangeError(QStringLiteral("index out of range")); + idx += v->d()->byteOffset; if (sizeof(T) == 4) { // float diff --git a/src/qml/jsruntime/qv4dataview_p.h b/src/qml/jsruntime/qv4dataview_p.h index 5c50df4655..199a6f9f80 100644 --- a/src/qml/jsruntime/qv4dataview_p.h +++ b/src/qml/jsruntime/qv4dataview_p.h @@ -64,12 +64,12 @@ struct DataViewCtor : FunctionObject { }; #define DataViewMembers(class, Member) \ - Member(class, Pointer, ArrayBuffer *, buffer) \ + Member(class, Pointer, SharedArrayBuffer *, buffer) \ Member(class, NoMark, uint, byteLength) \ Member(class, NoMark, uint, byteOffset) DECLARE_HEAP_OBJECT(DataView, Object) { - DECLARE_MARK_TABLE(DataView); + DECLARE_MARKOBJECTS(DataView); void init() { Object::init(); } }; @@ -79,8 +79,8 @@ struct DataViewCtor: FunctionObject { V4_OBJECT2(DataViewCtor, FunctionObject) - static void construct(const Managed *m, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; struct DataView : Object @@ -93,21 +93,21 @@ struct DataViewPrototype: Object { void init(ExecutionEngine *engine, Object *ctor); - static void method_get_buffer(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_byteLength(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_byteOffset(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_get_buffer(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_byteLength(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_byteOffset(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); template <typename T> - static void method_getChar(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_getChar(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); template <typename T> - static void method_get(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_get(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); template <typename T> - static void method_getFloat(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_getFloat(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); template <typename T> - static void method_setChar(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_setChar(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); template <typename T> - static void method_set(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_set(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); template <typename T> - static void method_setFloat(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_setFloat(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp index c56d007028..21c6a5d06b 100644 --- a/src/qml/jsruntime/qv4dateobject.cpp +++ b/src/qml/jsruntime/qv4dateobject.cpp @@ -43,6 +43,8 @@ #include "qv4scopedvalue_p.h" #include "qv4runtime_p.h" #include "qv4string_p.h" +#include "qv4jscall_p.h" +#include "qv4symbol_p.h" #include <QtCore/QDebug> #include <QtCore/QDateTime> @@ -54,16 +56,29 @@ #include <wtf/MathExtras.h> -#ifdef Q_OS_WIN -# include <windows.h> +#if defined(Q_OS_LINUX) && QT_CONFIG(timezone) +/* + See QTBUG-56899. Although we don't (yet) have a proper way to reset the + system zone, the code below, that uses QTimeZone::systemTimeZone(), works + adequately on Linux, when the TZ environment variable is changed. + */ +#define USE_QTZ_SYSTEM_ZONE +#endif + +#ifdef USE_QTZ_SYSTEM_ZONE +#include <QtCore/QTimeZone> #else -# ifndef Q_OS_VXWORKS -# include <sys/time.h> +# ifdef Q_OS_WIN +# include <windows.h> # else -# include "qplatformdefs.h" +# ifndef Q_OS_VXWORKS +# include <sys/time.h> +# else +# include "qplatformdefs.h" +# endif +# include <unistd.h> // for _POSIX_THREAD_SAFE_FUNCTIONS # endif -# include <unistd.h> // for _POSIX_THREAD_SAFE_FUNCTIONS -#endif +#endif // USE_QTZ_SYSTEM_ZONE using namespace QV4; @@ -75,8 +90,6 @@ static const double msPerMinute = 60000.0; static const double msPerHour = 3600000.0; static const double msPerDay = 86400000.0; -static double LocalTZA = 0.0; // initialized at startup - static inline double TimeWithinDay(double t) { double r = ::fmod(t, msPerDay); @@ -209,7 +222,7 @@ static inline double MonthFromTime(double t) static inline double DateFromTime(double t) { - int m = (int) Primitive::toInteger(MonthFromTime(t)); + int m = (int) QV4::Value::toInteger(MonthFromTime(t)); double d = DayWithinYear(t); double l = InLeapYear(t); @@ -240,6 +253,12 @@ static inline double WeekDay(double t) static inline double MakeTime(double hour, double min, double sec, double ms) { + if (!qIsFinite(hour) || !qIsFinite(min) || !qIsFinite(sec) || !qIsFinite(ms)) + return qQNaN(); + hour = QV4::Value::toInteger(hour); + min = QV4::Value::toInteger(min); + sec = QV4::Value::toInteger(sec); + ms = QV4::Value::toInteger(ms); return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec) * msPerSecond + ms; } @@ -265,6 +284,12 @@ static inline double DayFromMonth(double month, double leap) static double MakeDay(double year, double month, double day) { + if (!qIsFinite(year) || !qIsFinite(month) || !qIsFinite(day)) + return qQNaN(); + year = QV4::Value::toInteger(year); + month = QV4::Value::toInteger(month); + day = QV4::Value::toInteger(day); + year += ::floor(month / 12.0); month = ::fmod(month, 12.0); @@ -285,10 +310,35 @@ static inline double MakeDate(double day, double time) return day * msPerDay + time; } -static inline double DaylightSavingTA(double t) +#ifdef USE_QTZ_SYSTEM_ZONE +/* + ECMAScript specifies use of a fixed (current, standard) time-zone offset, + LocalTZA; and LocalTZA + DaylightSavingTA(t) is taken to be (see LocalTime and + UTC, following) local time's offset from UTC at time t. For simple zones, + DaylightSavingTA(t) is thus the DST offset applicable at date/time t; however, + if a zone has changed its standard offset, the only way to make LocalTime and + UTC (if implemented in accord with the spec) perform correct transformations + is to have DaylightSavingTA(t) correct for the zone's standard offset change + as well as its actual DST offset. + + This means we have to treat any historical changes in the zone's standard + offset as DST perturbations, regardless of historical reality. (This shall + mean a whole day of DST offset for some zones, that have crossed the + international date line. This shall confuse client code.) The bug report + against the ECMAScript spec is https://github.com/tc39/ecma262/issues/725 +*/ + +static inline double DaylightSavingTA(double t, double localTZA) // t is a UTC time +{ + return QTimeZone::systemTimeZone().offsetFromUtc( + QDateTime::fromMSecsSinceEpoch(qint64(t), Qt::UTC)) * 1e3 - localTZA; +} +#else +// This implementation fails to take account of past changes in standard offset. +static inline double DaylightSavingTA(double t, double /*localTZA*/) { struct tm tmtm; -#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if defined(Q_CC_MSVC) __time64_t tt = (__time64_t)(t / msPerSecond); // _localtime_64_s returns non-zero on failure if (_localtime64_s(&tmtm, &tt) != 0) @@ -306,34 +356,26 @@ static inline double DaylightSavingTA(double t) return 0; return (tmtm.tm_isdst > 0) ? msPerHour : 0; } +#endif // USE_QTZ_SYSTEM_ZONE -static inline double LocalTime(double t) +static inline double LocalTime(double t, double localTZA) { - return t + LocalTZA + DaylightSavingTA(t); + // Flawed, yet verbatim from the spec: + return t + localTZA + DaylightSavingTA(t, localTZA); } -static inline double UTC(double t) +// The spec does note [*] that UTC and LocalTime are not quite mutually inverse. +// [*] http://www.ecma-international.org/ecma-262/7.0/index.html#sec-utc-t + +static inline double UTC(double t, double localTZA) { - return t - LocalTZA - DaylightSavingTA(t - LocalTZA); + // Flawed, yet verbatim from the spec: + return t - localTZA - DaylightSavingTA(t - localTZA, localTZA); } static inline double currentTime() { -#ifndef Q_OS_WIN - struct timeval tv; - - gettimeofday(&tv, 0); - return ::floor(tv.tv_sec * msPerSecond + (tv.tv_usec / 1000.0)); -#else - SYSTEMTIME st; - GetSystemTime(&st); - FILETIME ft; - SystemTimeToFileTime(&st, &ft); - LARGE_INTEGER li; - li.LowPart = ft.dwLowDateTime; - li.HighPart = ft.dwHighDateTime; - return double(li.QuadPart - Q_INT64_C(116444736000000000)) / 10000.0; -#endif + return QDateTime::currentDateTimeUtc().toMSecsSinceEpoch(); } static inline double TimeClip(double t) @@ -342,18 +384,25 @@ static inline double TimeClip(double t) return qt_qnan(); // +0 looks weird, but is correct. See ES6 20.3.1.15. We must not return -0. - return Primitive::toInteger(t) + 0; + return QV4::Value::toInteger(t) + 0; } -static inline double ParseString(const QString &s) +static inline double ParseString(const QString &s, double localTZA) { - // first try the format defined in 15.9.1.15, only if that fails fall back to - // QDateTime for parsing + /* + First, try the format defined in ECMA 262's "Date Time String Format"; + only if that fails, fall back to QDateTime for parsing - // the define string format is YYYY-MM-DDTHH:mm:ss.sssZ - // It can be date or time only, and the second and later components - // of both fields are optional - // and extended syntax for negative and large positive years exists: +/-YYYYYY + The defined string format is YYYY-MM-DDTHH:mm:ss.sssZ; the time (T and all + after it) may be omitted; in each part, the second and later components + are optional; and there's an extended syntax for negative and large + positive years: +/-YYYYYY; the leading sign, even when +, isn't optional. + If month or day is omitted, it is 01; if minute or second is omitted, it's + 00; if milliseconds are omitted, they're 000. + + When the time zone offset is absent, date-only forms are interpreted as + indicating a UTC time and date-time forms are interpreted in local time. + */ enum Format { Year, @@ -386,6 +435,8 @@ static inline double ParseString(const QString &s) int msec = 0; int offsetSign = 1; int offset = 0; + bool seenT = false; + bool seenZ = false; // Have seen zone, i.e. +HH:mm or literal Z. bool error = false; if (*ch == '+' || *ch == '-') { @@ -394,7 +445,7 @@ static inline double ParseString(const QString &s) yearSign = -1; ++ch; } - while (ch <= end) { + for (; ch <= end && !error && format != Done; ++ch) { if (*ch >= '0' && *ch <= '9') { current *= 10; current += ch->unicode() - '0'; @@ -422,7 +473,7 @@ static inline double ParseString(const QString &s) break; case Minute: minute = current; - error = (currentSize != 2) || minute > 60; + error = (currentSize != 2) || minute >= 60; break; case Second: second = current; @@ -433,8 +484,10 @@ static inline double ParseString(const QString &s) error = (currentSize != 3); break; case TimezoneHour: - offset = current*60; - error = (currentSize != 2) || offset > 23*60; + Q_ASSERT(offset == 0 && !seenZ); + offset = current * 60; + error = (currentSize != 2) || current > 23; + seenZ = true; break; case TimezoneMinute: offset += current; @@ -445,6 +498,7 @@ static inline double ParseString(const QString &s) if (format >= Hour) error = true; format = Hour; + seenT = true; } else if (*ch == '-') { if (format < Day) ++format; @@ -453,6 +507,7 @@ static inline double ParseString(const QString &s) else if (format >= TimezoneHour) error = true; else { + Q_ASSERT(offset == 0 && !seenZ); offsetSign = -1; format = TimezoneHour; } @@ -465,24 +520,32 @@ static inline double ParseString(const QString &s) error = true; ++format; } else if (*ch == '+') { - if (format < Minute || format >= TimezoneHour) + if (seenZ || format < Minute || format >= TimezoneHour) error = true; format = TimezoneHour; - } else if (*ch == 'Z' || ch->unicode() == 0) { + } else if (*ch == 'Z') { + if (seenZ || format < Minute || format >= TimezoneHour) + error = true; + else + Q_ASSERT(offset == 0); + format = Done; + seenZ = true; + } else if (ch->unicode() == 0) { format = Done; } current = 0; currentSize = 0; } - if (error || format == Done) - break; - ++ch; } if (!error) { double t = MakeDate(MakeDay(year * yearSign, month, day), MakeTime(hour, minute, second, msec)); - t -= offset * offsetSign * 60 * 1000; - return t; + if (seenZ) + t -= offset * offsetSign * 60 * 1000; + else if (seenT) // No zone specified, treat date-time as local time + t = UTC(t, localTZA); + // else: treat plain date as already in UTC + return TimeClip(t); } QDateTime dt = QDateTime::fromString(s, Qt::TextDate); @@ -491,63 +554,68 @@ static inline double ParseString(const QString &s) if (!dt.isValid()) dt = QDateTime::fromString(s, Qt::RFC2822Date); if (!dt.isValid()) { - QStringList formats; - formats << QStringLiteral("M/d/yyyy") - << QStringLiteral("M/d/yyyy hh:mm") - << QStringLiteral("M/d/yyyy hh:mm A") - - << QStringLiteral("M/d/yyyy, hh:mm") - << QStringLiteral("M/d/yyyy, hh:mm A") - - << QStringLiteral("MMM d yyyy") - << QStringLiteral("MMM d yyyy hh:mm") - << QStringLiteral("MMM d yyyy hh:mm:ss") - << QStringLiteral("MMM d yyyy, hh:mm") - << QStringLiteral("MMM d yyyy, hh:mm:ss") - - << QStringLiteral("MMMM d yyyy") - << QStringLiteral("MMMM d yyyy hh:mm") - << QStringLiteral("MMMM d yyyy hh:mm:ss") - << QStringLiteral("MMMM d yyyy, hh:mm") - << QStringLiteral("MMMM d yyyy, hh:mm:ss") - - << QStringLiteral("MMM d, yyyy") - << QStringLiteral("MMM d, yyyy hh:mm") - << QStringLiteral("MMM d, yyyy hh:mm:ss") - - << QStringLiteral("MMMM d, yyyy") - << QStringLiteral("MMMM d, yyyy hh:mm") - << QStringLiteral("MMMM d, yyyy hh:mm:ss") - - << QStringLiteral("d MMM yyyy") - << QStringLiteral("d MMM yyyy hh:mm") - << QStringLiteral("d MMM yyyy hh:mm:ss") - << QStringLiteral("d MMM yyyy, hh:mm") - << QStringLiteral("d MMM yyyy, hh:mm:ss") - - << QStringLiteral("d MMMM yyyy") - << QStringLiteral("d MMMM yyyy hh:mm") - << QStringLiteral("d MMMM yyyy hh:mm:ss") - << QStringLiteral("d MMMM yyyy, hh:mm") - << QStringLiteral("d MMMM yyyy, hh:mm:ss") - - << QStringLiteral("d MMM, yyyy") - << QStringLiteral("d MMM, yyyy hh:mm") - << QStringLiteral("d MMM, yyyy hh:mm:ss") - - << QStringLiteral("d MMMM, yyyy") - << QStringLiteral("d MMMM, yyyy hh:mm") - << QStringLiteral("d MMMM, yyyy hh:mm:ss"); - - for (int i = 0; i < formats.size(); ++i) { - dt = QDateTime::fromString(s, formats.at(i)); + const QString formats[] = { + QStringLiteral("M/d/yyyy"), + QStringLiteral("M/d/yyyy hh:mm"), + QStringLiteral("M/d/yyyy hh:mm A"), + + QStringLiteral("M/d/yyyy, hh:mm"), + QStringLiteral("M/d/yyyy, hh:mm A"), + + QStringLiteral("MMM d yyyy"), + QStringLiteral("MMM d yyyy hh:mm"), + QStringLiteral("MMM d yyyy hh:mm:ss"), + QStringLiteral("MMM d yyyy, hh:mm"), + QStringLiteral("MMM d yyyy, hh:mm:ss"), + + QStringLiteral("MMMM d yyyy"), + QStringLiteral("MMMM d yyyy hh:mm"), + QStringLiteral("MMMM d yyyy hh:mm:ss"), + QStringLiteral("MMMM d yyyy, hh:mm"), + QStringLiteral("MMMM d yyyy, hh:mm:ss"), + + QStringLiteral("MMM d, yyyy"), + QStringLiteral("MMM d, yyyy hh:mm"), + QStringLiteral("MMM d, yyyy hh:mm:ss"), + + QStringLiteral("MMMM d, yyyy"), + QStringLiteral("MMMM d, yyyy hh:mm"), + QStringLiteral("MMMM d, yyyy hh:mm:ss"), + + QStringLiteral("d MMM yyyy"), + QStringLiteral("d MMM yyyy hh:mm"), + QStringLiteral("d MMM yyyy hh:mm:ss"), + QStringLiteral("d MMM yyyy, hh:mm"), + QStringLiteral("d MMM yyyy, hh:mm:ss"), + + QStringLiteral("d MMMM yyyy"), + QStringLiteral("d MMMM yyyy hh:mm"), + QStringLiteral("d MMMM yyyy hh:mm:ss"), + QStringLiteral("d MMMM yyyy, hh:mm"), + QStringLiteral("d MMMM yyyy, hh:mm:ss"), + + QStringLiteral("d MMM, yyyy"), + QStringLiteral("d MMM, yyyy hh:mm"), + QStringLiteral("d MMM, yyyy hh:mm:ss"), + + QStringLiteral("d MMMM, yyyy"), + QStringLiteral("d MMMM, yyyy hh:mm"), + QStringLiteral("d MMMM, yyyy hh:mm:ss"), + }; + + for (uint i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) { + const QString &format(formats[i]); + dt = format.indexOf(QLatin1String("hh:mm")) < 0 + ? QDateTime(QDate::fromString(s, format), + QTime(0, 0, 0), Qt::UTC) + : QDateTime::fromString(s, format); // as local time if (dt.isValid()) break; } } if (!dt.isValid()) return qt_qnan(); - return dt.toMSecsSinceEpoch(); + return TimeClip(dt.toMSecsSinceEpoch()); } /*! @@ -560,15 +628,15 @@ static inline QDateTime ToDateTime(double t, Qt::TimeSpec spec) { if (std::isnan(t)) return QDateTime(); - return QDateTime::fromMSecsSinceEpoch(t, spec); + return QDateTime::fromMSecsSinceEpoch(t, Qt::UTC).toTimeSpec(spec); } -static inline QString ToString(double t) +static inline QString ToString(double t, double localTZA) { if (std::isnan(t)) return QStringLiteral("Invalid Date"); QString str = ToDateTime(t, Qt::LocalTime).toString() + QLatin1String(" GMT"); - double tzoffset = LocalTZA + DaylightSavingTA(t); + double tzoffset = localTZA + DaylightSavingTA(t, localTZA); if (tzoffset) { int hours = static_cast<int>(::fabs(tzoffset) / 1000 / 60 / 60); int mins = int(::fabs(tzoffset) / 1000 / 60) % 60; @@ -602,36 +670,44 @@ static inline QString ToTimeString(double t) static inline QString ToLocaleString(double t) { - return ToDateTime(t, Qt::LocalTime).toString(Qt::LocaleDate); + return ToDateTime(t, Qt::LocalTime).toString(Qt::DefaultLocaleShortDate); } static inline QString ToLocaleDateString(double t) { - return ToDateTime(t, Qt::LocalTime).date().toString(Qt::LocaleDate); + return ToDateTime(t, Qt::LocalTime).date().toString(Qt::DefaultLocaleShortDate); } static inline QString ToLocaleTimeString(double t) { - return ToDateTime(t, Qt::LocalTime).time().toString(Qt::LocaleDate); + return ToDateTime(t, Qt::LocalTime).time().toString(Qt::DefaultLocaleShortDate); } static double getLocalTZA() { #ifndef Q_OS_WIN + tzset(); +#endif +#ifdef USE_QTZ_SYSTEM_ZONE + // TODO: QTimeZone::resetSystemTimeZone(), see QTBUG-56899 and comment above. + // Standard offset, with no daylight-savings adjustment, in ms: + return QTimeZone::systemTimeZone().standardTimeOffset(QDateTime::currentDateTime()) * 1e3; +#else +# ifdef Q_OS_WIN + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + return -tzInfo.Bias * 60.0 * 1000.0; +# else struct tm t; time_t curr; - tzset(); time(&curr); - localtime_r(&curr, &t); + localtime_r(&curr, &t); // Wrong: includes DST offset time_t locl = mktime(&t); gmtime_r(&curr, &t); time_t globl = mktime(&t); return (double(locl) - double(globl)) * 1000.0; -#else - TIME_ZONE_INFORMATION tzInfo; - GetTimeZoneInformation(&tzInfo); - return -tzInfo.Bias * 60.0 * 1000.0; -#endif +# endif +#endif // USE_QTZ_SYSTEM_ZONE } DEFINE_OBJECT_VTABLE(DateObject); @@ -639,7 +715,7 @@ DEFINE_OBJECT_VTABLE(DateObject); void Heap::DateObject::init(const QDateTime &date) { Object::init(); - this->date = date.isValid() ? date.toMSecsSinceEpoch() : qt_qnan(); + this->date = date.isValid() ? TimeClip(date.toMSecsSinceEpoch()) : qt_qnan(); } void Heap::DateObject::init(const QTime &time) @@ -650,19 +726,21 @@ void Heap::DateObject::init(const QTime &time) return; } - /* All programmers know that stuff starts at 0. Whatever that may mean in this context (and - * local timezone), it's before the epoch, so there is defenitely no DST problem. Specifically: - * you can't start with a date before the epoch, add some[*] hours, and end up with a date - * after. That's a problem for timezones where new year happens during DST, like - * Australia/Hobart, because we have to ignore DST before the epoch (but honor it after the - * epoch). - * - * [*] Well, when "some" is in the range 0-24. If you add something like 1M then this might - * still happen. + /* We have to chose a date on which to instantiate this time. All we really + * care about is that it round-trips back to the same time if we extract the + * time from it, which shall (via toQDateTime(), below) discard the date + * part. We need a date for which time-zone data is likely to be sane (so + * MakeDay(0, 0, 0) was a bad choice; 2 BC, December 31st is before + * time-zones were standardized), with no transition nearby in date. We + * ignore DST transitions before 1970, but even then zone transitions did + * happen. Some do happen at new year, others on DST transitions in spring + * and autumn; so pick the three hundredth anniversary of the birth of + * Giovanni Domenico Cassini (1625-06-08), whose work first let us + * synchronize clocks tolerably accurately at distant locations. */ - static const double d = MakeDay(0, 0, 0); + static const double d = MakeDay(1925, 5, 8); double t = MakeTime(time.hour(), time.minute(), time.second(), time.msec()); - date = TimeClip(UTC(MakeDate(d, t))); + date = TimeClip(UTC(MakeDate(d, t), internalClass->engine->localTZA)); } QDateTime DateObject::toQDateTime() const @@ -677,48 +755,57 @@ void Heap::DateCtor::init(QV4::ExecutionContext *scope) Heap::FunctionObject::init(scope, QStringLiteral("Date")); } -void DateCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue DateCtor::virtualCallAsConstructor(const FunctionObject *that, const Value *argv, int argc, const Value *newTarget) { + ExecutionEngine *v4 = that->engine(); double t = 0; - if (callData->argc == 0) + if (argc == 0) t = currentTime(); - else if (callData->argc == 1) { - ScopedValue arg(scope, callData->args[0]); + else if (argc == 1) { + Scope scope(v4); + ScopedValue arg(scope, argv[0]); if (DateObject *d = arg->as<DateObject>()) { t = d->date(); } else { arg = RuntimeHelpers::toPrimitive(arg, PREFERREDTYPE_HINT); if (String *s = arg->stringValue()) - t = ParseString(s->toQString()); + t = ParseString(s->toQString(), v4->localTZA); else t = TimeClip(arg->toNumber()); } } else { // d.argc > 1 - double year = callData->args[0].toNumber(); - double month = callData->args[1].toNumber(); - double day = callData->argc >= 3 ? callData->args[2].toNumber() : 1; - double hours = callData->argc >= 4 ? callData->args[3].toNumber() : 0; - double mins = callData->argc >= 5 ? callData->args[4].toNumber() : 0; - double secs = callData->argc >= 6 ? callData->args[5].toNumber() : 0; - double ms = callData->argc >= 7 ? callData->args[6].toNumber() : 0; + double year = argv[0].toNumber(); + double month = argv[1].toNumber(); + double day = argc >= 3 ? argv[2].toNumber() : 1; + double hours = argc >= 4 ? argv[3].toNumber() : 0; + double mins = argc >= 5 ? argv[4].toNumber() : 0; + double secs = argc >= 6 ? argv[5].toNumber() : 0; + double ms = argc >= 7 ? argv[6].toNumber() : 0; if (year >= 0 && year <= 99) year += 1900; t = MakeDate(MakeDay(year, month, day), MakeTime(hours, mins, secs, ms)); - t = TimeClip(UTC(t)); + t = TimeClip(UTC(t, v4->localTZA)); } - scope.result = Encode(scope.engine->newDateObject(Primitive::fromDouble(t))); + ReturnedValue o = Encode(v4->newDateObject(Value::fromDouble(t))); + if (!newTarget) + return o; + Scope scope(v4); + ScopedObject obj(scope, o); + obj->setProtoFromNewTarget(newTarget); + return obj->asReturnedValue(); } -void DateCtor::call(const Managed *m, Scope &scope, CallData *) +ReturnedValue DateCtor::virtualCall(const FunctionObject *m, const Value *, const Value *, int) { + ExecutionEngine *e = m->engine(); double t = currentTime(); - scope.result = static_cast<const DateCtor *>(m)->engine()->newString(ToString(t)); + return e->newString(ToString(t, e->localTZA))->asReturnedValue(); } void DatePrototype::init(ExecutionEngine *engine, Object *ctor) @@ -726,8 +813,8 @@ void DatePrototype::init(ExecutionEngine *engine, Object *ctor) Scope scope(engine); ScopedObject o(scope); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); - ctor->defineReadonlyConfigurableProperty(engine->id_length(), Primitive::fromInt32(7)); - LocalTZA = getLocalTZA(); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(7)); + engine->localTZA = getLocalTZA(); ctor->defineDefaultProperty(QStringLiteral("parse"), method_parse, 1); ctor->defineDefaultProperty(QStringLiteral("UTC"), method_UTC, 7); @@ -737,7 +824,7 @@ void DatePrototype::init(ExecutionEngine *engine, Object *ctor) defineDefaultProperty(engine->id_toString(), method_toString, 0); defineDefaultProperty(QStringLiteral("toDateString"), method_toDateString, 0); defineDefaultProperty(QStringLiteral("toTimeString"), method_toTimeString, 0); - defineDefaultProperty(QStringLiteral("toLocaleString"), method_toLocaleString, 0); + defineDefaultProperty(engine->id_toLocaleString(), method_toLocaleString, 0); defineDefaultProperty(QStringLiteral("toLocaleDateString"), method_toLocaleDateString, 0); defineDefaultProperty(QStringLiteral("toLocaleTimeString"), method_toLocaleTimeString, 0); defineDefaultProperty(engine->id_valueOf(), method_valueOf, 0); @@ -784,528 +871,608 @@ void DatePrototype::init(ExecutionEngine *engine, Object *ctor) QString toGmtString(QStringLiteral("toGMTString")); ScopedString us(scope, engine->newIdentifier(toUtcString)); ScopedString gs(scope, engine->newIdentifier(toGmtString)); - ExecutionContext *global = engine->rootContext(); - ScopedFunctionObject toUtcGmtStringFn(scope, BuiltinFunction::create(global, us, method_toUTCString)); - toUtcGmtStringFn->defineReadonlyConfigurableProperty(engine->id_length(), Primitive::fromInt32(0)); + ScopedFunctionObject toUtcGmtStringFn(scope, FunctionObject::createBuiltinFunction(engine, us, method_toUTCString, 0)); defineDefaultProperty(us, toUtcGmtStringFn); defineDefaultProperty(gs, toUtcGmtStringFn); } defineDefaultProperty(QStringLiteral("toISOString"), method_toISOString, 0); defineDefaultProperty(QStringLiteral("toJSON"), method_toJSON, 1); + defineDefaultProperty(engine->symbol_toPrimitive(), method_symbolToPrimitive, 1, Attr_ReadOnly_ButConfigurable); } -double DatePrototype::getThisDate(Scope &scope, CallData *callData) +double DatePrototype::getThisDate(ExecutionEngine *v4, const Value *thisObject) { - if (DateObject *thisObject = callData->thisObject.as<DateObject>()) - return thisObject->date(); - else { - scope.engine->throwTypeError(); - return 0; - } + if (const DateObject *that = thisObject->as<DateObject>()) + return that->date(); + v4->throwTypeError(); + return 0; } -void DatePrototype::method_parse(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_parse(const FunctionObject *f, const Value *, const Value *argv, int argc) { - if (!callData->argc) - scope.result = Encode(qt_qnan()); + if (!argc) + return Encode(qt_qnan()); else - scope.result = Encode(ParseString(callData->args[0].toQString())); + return Encode(ParseString(argv[0].toQString(), f->engine()->localTZA)); } -void DatePrototype::method_UTC(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_UTC(const FunctionObject *f, const Value *, const Value *argv, int argc) { - const int numArgs = callData->argc; - if (numArgs >= 2) { - double year = callData->args[0].toNumber(); - double month = callData->args[1].toNumber(); - double day = numArgs >= 3 ? callData->args[2].toNumber() : 1; - double hours = numArgs >= 4 ? callData->args[3].toNumber() : 0; - double mins = numArgs >= 5 ? callData->args[4].toNumber() : 0; - double secs = numArgs >= 6 ? callData->args[5].toNumber() : 0; - double ms = numArgs >= 7 ? callData->args[6].toNumber() : 0; - if (year >= 0 && year <= 99) - year += 1900; - double t = MakeDate(MakeDay(year, month, day), - MakeTime(hours, mins, secs, ms)); - scope.result = Encode(TimeClip(t)); - return; - } - RETURN_UNDEFINED(); + const int numArgs = argc; + if (numArgs < 1) + return Encode(qQNaN()); + ExecutionEngine *e = f->engine(); + double year = argv[0].toNumber(); + if (e->hasException) + return Encode::undefined(); + double month = numArgs >= 2 ? argv[1].toNumber() : 0; + if (e->hasException) + return Encode::undefined(); + double day = numArgs >= 3 ? argv[2].toNumber() : 1; + if (e->hasException) + return Encode::undefined(); + double hours = numArgs >= 4 ? argv[3].toNumber() : 0; + if (e->hasException) + return Encode::undefined(); + double mins = numArgs >= 5 ? argv[4].toNumber() : 0; + if (e->hasException) + return Encode::undefined(); + double secs = numArgs >= 6 ? argv[5].toNumber() : 0; + if (e->hasException) + return Encode::undefined(); + double ms = numArgs >= 7 ? argv[6].toNumber() : 0; + if (e->hasException) + return Encode::undefined(); + double iyear = QV4::Value::toInteger(year); + if (!qIsNaN(year) && iyear >= 0 && iyear <= 99) + year = 1900 + iyear; + double t = MakeDate(MakeDay(year, month, day), + MakeTime(hours, mins, secs, ms)); + return Encode(TimeClip(t)); } -void DatePrototype::method_now(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_now(const FunctionObject *, const Value *, const Value *, int) { - Q_UNUSED(callData); - double t = currentTime(); - scope.result = Encode(t); + return Encode(currentTime()); } -void DatePrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); - scope.result = scope.engine->newString(ToString(t)); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); + return Encode(v4->newString(ToString(t, v4->localTZA))); } -void DatePrototype::method_toDateString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toDateString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); - scope.result = scope.engine->newString(ToDateString(t)); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); + return Encode(v4->newString(ToDateString(t))); } -void DatePrototype::method_toTimeString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toTimeString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); - scope.result = scope.engine->newString(ToTimeString(t)); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); + return Encode(v4->newString(ToTimeString(t))); } -void DatePrototype::method_toLocaleString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); - scope.result = scope.engine->newString(ToLocaleString(t)); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); + return Encode(v4->newString(ToLocaleString(t))); } -void DatePrototype::method_toLocaleDateString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toLocaleDateString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); - scope.result = scope.engine->newString(ToLocaleDateString(t)); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); + return Encode(v4->newString(ToLocaleDateString(t))); } -void DatePrototype::method_toLocaleTimeString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toLocaleTimeString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); - scope.result = scope.engine->newString(ToLocaleTimeString(t)); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); + return Encode(v4->newString(ToLocaleTimeString(t))); } -void DatePrototype::method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_valueOf(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); - scope.result = Encode(t); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); + return Encode(t); } -void DatePrototype::method_getTime(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getTime(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); - scope.result = Encode(t); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); + return Encode(t); } -void DatePrototype::method_getYear(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getYear(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = YearFromTime(LocalTime(t)) - 1900; - scope.result = Encode(t); + t = YearFromTime(LocalTime(t, v4->localTZA)) - 1900; + return Encode(t); } -void DatePrototype::method_getFullYear(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getFullYear(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = YearFromTime(LocalTime(t)); - scope.result = Encode(t); + t = YearFromTime(LocalTime(t, v4->localTZA)); + return Encode(t); } -void DatePrototype::method_getUTCFullYear(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getUTCFullYear(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) t = YearFromTime(t); - scope.result = Encode(t); + return Encode(t); } -void DatePrototype::method_getMonth(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getMonth(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = MonthFromTime(LocalTime(t)); - scope.result = Encode(t); + t = MonthFromTime(LocalTime(t, v4->localTZA)); + return Encode(t); } -void DatePrototype::method_getUTCMonth(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getUTCMonth(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) t = MonthFromTime(t); - scope.result = Encode(t); + return Encode(t); } -void DatePrototype::method_getDate(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getDate(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = DateFromTime(LocalTime(t)); - scope.result = Encode(t); + t = DateFromTime(LocalTime(t, v4->localTZA)); + return Encode(t); } -void DatePrototype::method_getUTCDate(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getUTCDate(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) t = DateFromTime(t); - scope.result = Encode(t); + return Encode(t); } -void DatePrototype::method_getDay(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getDay(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = WeekDay(LocalTime(t)); - scope.result = Encode(t); + t = WeekDay(LocalTime(t, v4->localTZA)); + return Encode(t); } -void DatePrototype::method_getUTCDay(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getUTCDay(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) t = WeekDay(t); - scope.result = Encode(t); + return Encode(t); } -void DatePrototype::method_getHours(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getHours(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = HourFromTime(LocalTime(t)); - scope.result = Encode(t); + t = HourFromTime(LocalTime(t, v4->localTZA)); + return Encode(t); } -void DatePrototype::method_getUTCHours(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getUTCHours(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) t = HourFromTime(t); - scope.result = Encode(t); + return Encode(t); } -void DatePrototype::method_getMinutes(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getMinutes(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = MinFromTime(LocalTime(t)); - scope.result = Encode(t); + t = MinFromTime(LocalTime(t, v4->localTZA)); + return Encode(t); } -void DatePrototype::method_getUTCMinutes(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getUTCMinutes(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) t = MinFromTime(t); - scope.result = Encode(t); + return Encode(t); } -void DatePrototype::method_getSeconds(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getSeconds(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = SecFromTime(LocalTime(t)); - scope.result = Encode(t); + t = SecFromTime(LocalTime(t, v4->localTZA)); + return Encode(t); } -void DatePrototype::method_getUTCSeconds(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getUTCSeconds(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) t = SecFromTime(t); - scope.result = Encode(t); + return Encode(t); } -void DatePrototype::method_getMilliseconds(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getMilliseconds(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = msFromTime(LocalTime(t)); - scope.result = Encode(t); + t = msFromTime(LocalTime(t, v4->localTZA)); + return Encode(t); } -void DatePrototype::method_getUTCMilliseconds(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getUTCMilliseconds(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) t = msFromTime(t); - scope.result = Encode(t); + return Encode(t); } -void DatePrototype::method_getTimezoneOffset(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_getTimezoneOffset(const FunctionObject *b, const Value *thisObject, const Value *, int) { - double t = getThisDate(scope, callData); + ExecutionEngine *v4 = b->engine(); + double t = getThisDate(v4, thisObject); if (!std::isnan(t)) - t = (t - LocalTime(t)) / msPerMinute; - scope.result = Encode(t); + t = (t - LocalTime(t, v4->localTZA)) / msPerMinute; + return Encode(t); } -void DatePrototype::method_setTime(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setTime(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<DateObject> self(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); - double t = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); + double t = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); self->setDate(TimeClip(t)); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setMilliseconds(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setMilliseconds(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<DateObject> self(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); - double t = LocalTime(self->date()); - CHECK_EXCEPTION(); - double ms = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); - self->setDate(TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms))))); - scope.result = Encode(self->date()); + double t = LocalTime(self->date(), v4->localTZA); + if (v4->hasException) + return QV4::Encode::undefined(); + double ms = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); + self->setDate(TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms)), v4->localTZA))); + return Encode(self->date()); } -void DatePrototype::method_setUTCMilliseconds(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setUTCMilliseconds(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); - CHECK_EXCEPTION(); - double ms = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); + if (v4->hasException) + return QV4::Encode::undefined(); + double ms = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); self->setDate(TimeClip(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms)))); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setSeconds(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setSeconds(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); - - double t = LocalTime(self->date()); - CHECK_EXCEPTION(); - double sec = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); - double ms = (callData->argc < 2) ? msFromTime(t) : callData->args[1].toNumber(); - CHECK_EXCEPTION(); - t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), sec, ms)))); + return v4->throwTypeError(); + + double t = LocalTime(self->date(), v4->localTZA); + if (v4->hasException) + return QV4::Encode::undefined(); + double sec = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); + double ms = (argc < 2) ? msFromTime(t) : argv[1].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), sec, ms)), v4->localTZA)); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setUTCSeconds(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setUTCSeconds(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); - double sec = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - double ms = (callData->argc < 2) ? msFromTime(t) : callData->args[1].toNumber(); + double sec = argc ? argv[0].toNumber() : qt_qnan(); + double ms = (argc < 2) ? msFromTime(t) : argv[1].toNumber(); t = TimeClip(MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), sec, ms))); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setMinutes(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setMinutes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); - - double t = LocalTime(self->date()); - CHECK_EXCEPTION(); - double min = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); - double sec = (callData->argc < 2) ? SecFromTime(t) : callData->args[1].toNumber(); - CHECK_EXCEPTION(); - double ms = (callData->argc < 3) ? msFromTime(t) : callData->args[2].toNumber(); - CHECK_EXCEPTION(); - t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), min, sec, ms)))); + return v4->throwTypeError(); + + double t = LocalTime(self->date(), v4->localTZA); + if (v4->hasException) + return QV4::Encode::undefined(); + double min = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); + double sec = (argc < 2) ? SecFromTime(t) : argv[1].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + double ms = (argc < 3) ? msFromTime(t) : argv[2].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(HourFromTime(t), min, sec, ms)), v4->localTZA)); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setUTCMinutes(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setUTCMinutes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); - double min = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - double sec = (callData->argc < 2) ? SecFromTime(t) : callData->args[1].toNumber(); - double ms = (callData->argc < 3) ? msFromTime(t) : callData->args[2].toNumber(); + double min = argc ? argv[0].toNumber() : qt_qnan(); + double sec = (argc < 2) ? SecFromTime(t) : argv[1].toNumber(); + double ms = (argc < 3) ? msFromTime(t) : argv[2].toNumber(); t = TimeClip(MakeDate(Day(t), MakeTime(HourFromTime(t), min, sec, ms))); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setHours(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setHours(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); - - double t = LocalTime(self->date()); - CHECK_EXCEPTION(); - double hour = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); - double min = (callData->argc < 2) ? MinFromTime(t) : callData->args[1].toNumber(); - CHECK_EXCEPTION(); - double sec = (callData->argc < 3) ? SecFromTime(t) : callData->args[2].toNumber(); - CHECK_EXCEPTION(); - double ms = (callData->argc < 4) ? msFromTime(t) : callData->args[3].toNumber(); - CHECK_EXCEPTION(); - t = TimeClip(UTC(MakeDate(Day(t), MakeTime(hour, min, sec, ms)))); + return v4->throwTypeError(); + + double t = LocalTime(self->date(), v4->localTZA); + if (v4->hasException) + return QV4::Encode::undefined(); + double hour = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); + double min = (argc < 2) ? MinFromTime(t) : argv[1].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + double sec = (argc < 3) ? SecFromTime(t) : argv[2].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + double ms = (argc < 4) ? msFromTime(t) : argv[3].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + t = TimeClip(UTC(MakeDate(Day(t), MakeTime(hour, min, sec, ms)), v4->localTZA)); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setUTCHours(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setUTCHours(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); - double hour = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - double min = (callData->argc < 2) ? MinFromTime(t) : callData->args[1].toNumber(); - double sec = (callData->argc < 3) ? SecFromTime(t) : callData->args[2].toNumber(); - double ms = (callData->argc < 4) ? msFromTime(t) : callData->args[3].toNumber(); + double hour = argc ? argv[0].toNumber() : qt_qnan(); + double min = (argc < 2) ? MinFromTime(t) : argv[1].toNumber(); + double sec = (argc < 3) ? SecFromTime(t) : argv[2].toNumber(); + double ms = (argc < 4) ? msFromTime(t) : argv[3].toNumber(); t = TimeClip(MakeDate(Day(t), MakeTime(hour, min, sec, ms))); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setDate(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setDate(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); - - double t = LocalTime(self->date()); - CHECK_EXCEPTION(); - double date = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); - t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)))); + return v4->throwTypeError(); + + double t = LocalTime(self->date(), v4->localTZA); + if (v4->hasException) + return QV4::Encode::undefined(); + double date = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); + t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)), v4->localTZA)); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setUTCDate(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setUTCDate(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); - CHECK_EXCEPTION(); - double date = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); + if (v4->hasException) + return QV4::Encode::undefined(); + double date = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); t = TimeClip(MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t))); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setMonth(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setMonth(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); - - double t = LocalTime(self->date()); - CHECK_EXCEPTION(); - double month = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); - double date = (callData->argc < 2) ? DateFromTime(t) : callData->args[1].toNumber(); - CHECK_EXCEPTION(); - t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), month, date), TimeWithinDay(t)))); + return v4->throwTypeError(); + + double t = LocalTime(self->date(), v4->localTZA); + if (v4->hasException) + return QV4::Encode::undefined(); + double month = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); + double date = (argc < 2) ? DateFromTime(t) : argv[1].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + t = TimeClip(UTC(MakeDate(MakeDay(YearFromTime(t), month, date), TimeWithinDay(t)), v4->localTZA)); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setUTCMonth(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setUTCMonth(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); - double month = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - double date = (callData->argc < 2) ? DateFromTime(t) : callData->args[1].toNumber(); + double month = argc ? argv[0].toNumber() : qt_qnan(); + double date = (argc < 2) ? DateFromTime(t) : argv[1].toNumber(); t = TimeClip(MakeDate(MakeDay(YearFromTime(t), month, date), TimeWithinDay(t))); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setYear(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setYear(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); if (std::isnan(t)) t = 0; else - t = LocalTime(t); - double year = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + t = LocalTime(t, v4->localTZA); + double year = argc ? argv[0].toNumber() : qt_qnan(); double r; if (std::isnan(year)) { r = qt_qnan(); } else { - if ((Primitive::toInteger(year) >= 0) && (Primitive::toInteger(year) <= 99)) + if ((QV4::Value::toInteger(year) >= 0) && (QV4::Value::toInteger(year) <= 99)) year += 1900; r = MakeDay(year, MonthFromTime(t), DateFromTime(t)); - r = UTC(MakeDate(r, TimeWithinDay(t))); + r = UTC(MakeDate(r, TimeWithinDay(t)), v4->localTZA); r = TimeClip(r); } self->setDate(r); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setUTCFullYear(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setUTCFullYear(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); - double year = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - double month = (callData->argc < 2) ? MonthFromTime(t) : callData->args[1].toNumber(); - double date = (callData->argc < 3) ? DateFromTime(t) : callData->args[2].toNumber(); + double year = argc ? argv[0].toNumber() : qt_qnan(); + double month = (argc < 2) ? MonthFromTime(t) : argv[1].toNumber(); + double date = (argc < 3) ? DateFromTime(t) : argv[2].toNumber(); t = TimeClip(MakeDate(MakeDay(year, month, date), TimeWithinDay(t))); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_setFullYear(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_setFullYear(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); - double t = LocalTime(self->date()); - CHECK_EXCEPTION(); + double t = LocalTime(self->date(), v4->localTZA); + if (v4->hasException) + return QV4::Encode::undefined(); if (std::isnan(t)) t = 0; - double year = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - CHECK_EXCEPTION(); - double month = (callData->argc < 2) ? MonthFromTime(t) : callData->args[1].toNumber(); - CHECK_EXCEPTION(); - double date = (callData->argc < 3) ? DateFromTime(t) : callData->args[2].toNumber(); - CHECK_EXCEPTION(); - t = TimeClip(UTC(MakeDate(MakeDay(year, month, date), TimeWithinDay(t)))); + double year = argc ? argv[0].toNumber() : qt_qnan(); + if (v4->hasException) + return QV4::Encode::undefined(); + double month = (argc < 2) ? MonthFromTime(t) : argv[1].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + double date = (argc < 3) ? DateFromTime(t) : argv[2].toNumber(); + if (v4->hasException) + return QV4::Encode::undefined(); + t = TimeClip(UTC(MakeDate(MakeDay(year, month, date), TimeWithinDay(t)), v4->localTZA)); self->setDate(t); - scope.result = Encode(self->date()); + return Encode(self->date()); } -void DatePrototype::method_toUTCString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toUTCString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); - scope.result = scope.engine->newString(ToUTCString(t)); + return Encode(v4->newString(ToUTCString(t))); } static void addZeroPrefixedInt(QString &str, int num, int nDigits) @@ -1321,21 +1488,22 @@ static void addZeroPrefixedInt(QString &str, int num, int nDigits) } } -void DatePrototype::method_toISOString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toISOString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - DateObject *self = callData->thisObject.as<DateObject>(); + ExecutionEngine *v4 = b->engine(); + DateObject *self = const_cast<DateObject *>(thisObject->as<DateObject>()); if (!self) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); double t = self->date(); if (!std::isfinite(t)) - RETURN_RESULT(scope.engine->throwRangeError(callData->thisObject)); + RETURN_RESULT(v4->throwRangeError(*thisObject)); QString result; int year = (int)YearFromTime(t); if (year < 0 || year > 9999) { if (qAbs(year) >= 1000000) - RETURN_RESULT(scope.engine->newString(QStringLiteral("Invalid Date"))); + RETURN_RESULT(v4->throwRangeError(*thisObject)); result += year < 0 ? QLatin1Char('-') : QLatin1Char('+'); year = qAbs(year); addZeroPrefixedInt(result, year, 6); @@ -1356,32 +1524,49 @@ void DatePrototype::method_toISOString(const BuiltinFunction *, Scope &scope, Ca addZeroPrefixedInt(result, msFromTime(t), 3); result += QLatin1Char('Z'); - scope.result = scope.engine->newString(result); + return Encode(v4->newString(result)); } -void DatePrototype::method_toJSON(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue DatePrototype::method_toJSON(const FunctionObject *b, const Value *thisObject, const Value *, int) { - ScopedObject O(scope, callData->thisObject.toObject(scope.engine)); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + Scope scope(v4); + ScopedObject O(scope, thisObject->toObject(v4)); + if (v4->hasException) + return QV4::Encode::undefined(); ScopedValue tv(scope, RuntimeHelpers::toPrimitive(O, NUMBER_HINT)); if (tv->isNumber() && !std::isfinite(tv->toNumber())) - RETURN_RESULT(Encode::null()); + return Encode::null(); - ScopedString s(scope, scope.engine->newString(QStringLiteral("toISOString"))); + ScopedString s(scope, v4->newString(QStringLiteral("toISOString"))); ScopedValue v(scope, O->get(s)); FunctionObject *toIso = v->as<FunctionObject>(); if (!toIso) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); + + return toIso->call(O, nullptr, 0); +} + +ReturnedValue DatePrototype::method_symbolToPrimitive(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *e = f->engine(); + if (!thisObject->isObject() || !argc || !argv->isString()) + return e->throwTypeError(); + + String *hint = argv->stringValue(); + PropertyKey id = hint->toPropertyKey(); + if (id == e->id_default()->propertyKey()) + hint = e->id_string(); + else if (id != e->id_string()->propertyKey() && id != e->id_number()->propertyKey()) + return e->throwTypeError(); - ScopedCallData cData(scope); - cData->thisObject = callData->thisObject; - toIso->call(scope, cData); + return RuntimeHelpers::ordinaryToPrimitive(e, static_cast<const Object *>(thisObject), hint); } -void DatePrototype::timezoneUpdated() +void DatePrototype::timezoneUpdated(ExecutionEngine *e) { - LocalTZA = getLocalTZA(); + e->localTZA = getLocalTZA(); } diff --git a/src/qml/jsruntime/qv4dateobject_p.h b/src/qml/jsruntime/qv4dateobject_p.h index b0373884dd..5b9934282c 100644 --- a/src/qml/jsruntime/qv4dateobject_p.h +++ b/src/qml/jsruntime/qv4dateobject_p.h @@ -101,15 +101,15 @@ struct DateObject: Object { template<> inline const DateObject *Value::as() const { - return isManaged() && m()->vtable()->type == Managed::Type_DateObject ? static_cast<const DateObject *>(this) : 0; + return isManaged() && m()->internalClass->vtable->type == Managed::Type_DateObject ? static_cast<const DateObject *>(this) : nullptr; } struct DateCtor: FunctionObject { V4_OBJECT2(DateCtor, FunctionObject) - static void construct(const Managed *, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int); }; struct DatePrototype: Object @@ -118,59 +118,60 @@ struct DatePrototype: Object void init(ExecutionEngine *engine, Object *ctor); - static double getThisDate(Scope &scope, CallData *callData); - - static void method_parse(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_UTC(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_now(const BuiltinFunction *, Scope &scope, CallData *callData); - - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toDateString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toTimeString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toLocaleString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toLocaleDateString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toLocaleTimeString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getTime(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getYear(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getFullYear(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getUTCFullYear(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getMonth(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getUTCMonth(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getDate(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getUTCDate(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getDay(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getUTCDay(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getHours(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getUTCHours(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getMinutes(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getUTCMinutes(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getSeconds(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getUTCSeconds(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getMilliseconds(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getUTCMilliseconds(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getTimezoneOffset(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setTime(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setMilliseconds(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setUTCMilliseconds(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setSeconds(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setUTCSeconds(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setMinutes(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setUTCMinutes(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setHours(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setUTCHours(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setDate(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setUTCDate(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setMonth(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setUTCMonth(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setYear(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setFullYear(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_setUTCFullYear(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toUTCString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toISOString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toJSON(const BuiltinFunction *, Scope &scope, CallData *callData); - - static void timezoneUpdated(); + static double getThisDate(ExecutionEngine *v4, const Value *thisObject); + + static ReturnedValue method_parse(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_UTC(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_now(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toDateString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toTimeString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleDateString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleTimeString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_valueOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getTime(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getYear(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getFullYear(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getUTCFullYear(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getMonth(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getUTCMonth(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getDate(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getUTCDate(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getDay(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getUTCDay(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getHours(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getUTCHours(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getMinutes(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getUTCMinutes(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getSeconds(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getUTCSeconds(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getMilliseconds(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getUTCMilliseconds(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getTimezoneOffset(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setTime(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setMilliseconds(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setUTCMilliseconds(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setSeconds(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setUTCSeconds(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setMinutes(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setUTCMinutes(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setHours(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setUTCHours(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setDate(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setUTCDate(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setMonth(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setUTCMonth(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setYear(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setFullYear(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setUTCFullYear(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toUTCString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toISOString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toJSON(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_symbolToPrimitive(const FunctionObject *f, const Value *thisObject, const Value *, int); + + static void timezoneUpdated(ExecutionEngine *e); }; } diff --git a/src/qml/jsruntime/qv4debugging_p.h b/src/qml/jsruntime/qv4debugging_p.h index 8e2eec03d2..9b41bb6e7a 100644 --- a/src/qml/jsruntime/qv4debugging_p.h +++ b/src/qml/jsruntime/qv4debugging_p.h @@ -59,7 +59,7 @@ QT_BEGIN_NAMESPACE namespace QV4 { namespace Debugging { -#ifdef QT_NO_QML_DEBUGGER +#if !QT_CONFIG(qml_debug) class Debugger { @@ -78,7 +78,7 @@ class Q_QML_EXPORT Debugger : public QObject Q_OBJECT public: - virtual ~Debugger() {} + ~Debugger() override {} virtual bool pauseAtNextOpportunity() const = 0; virtual void maybeBreakAtInstruction() = 0; virtual void enteringFunction() = 0; diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 019936767a..c04617dac0 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -37,11 +37,34 @@ ** ****************************************************************************/ #include <qv4engine_p.h> + +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsast_p.h> +#include <private/qv4compileddata_p.h> +#include <private/qv4compiler_p.h> +#include <private/qv4compilercontext_p.h> +#include <private/qv4codegen_p.h> + +#include <QtCore/QTextStream> +#include <QDateTime> +#include <QDir> +#include <QFileInfo> +#include <QLoggingCategory> +#if QT_CONFIG(regularexpression) +#include <QRegularExpression> +#endif + +#ifndef V4_BOOTSTRAP + #include <qv4qmlcontext_p.h> #include <qv4value_p.h> #include <qv4object_p.h> #include <qv4objectproto_p.h> #include <qv4objectiterator_p.h> +#include <qv4setiterator_p.h> +#include <qv4mapiterator_p.h> +#include <qv4arrayiterator_p.h> #include <qv4arrayobject_p.h> #include <qv4booleanobject_p.h> #include <qv4globalobject_p.h> @@ -52,6 +75,9 @@ #include <qv4numberobject_p.h> #include <qv4regexpobject_p.h> #include <qv4regexp_p.h> +#include "qv4symbol_p.h" +#include "qv4setobject_p.h" +#include "qv4mapobject_p.h" #include <qv4variantobject_p.h> #include <qv4runtime_p.h> #include <private/qv4mm_p.h> @@ -63,11 +89,23 @@ #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 "qv4atomics_p.h" + +#if QT_CONFIG(qml_sequence_object) #include "qv4sequenceobject_p.h" +#endif + #include "qv4qobjectwrapper_p.h" #include "qv4memberdata_p.h" #include "qv4arraybuffer_p.h" #include "qv4dataview_p.h" +#include "qv4promiseobject_p.h" #include "qv4typedarray_p.h" #include <private/qv8engine_p.h> #include <private/qjsvalue_p.h> @@ -76,18 +114,11 @@ #include <private/qqmlvaluetype_p.h> #include <private/qqmllistwrapper_p.h> #include <private/qqmllist_p.h> +#include <private/qqmltypeloader_p.h> +#if QT_CONFIG(qml_locale) #include <private/qqmllocale_p.h> - -#include <QtCore/QTextStream> -#include <QDateTime> - -#ifdef V4_ENABLE_JIT -#include "qv4isel_masm_p.h" -#endif // V4_ENABLE_JIT - -#if QT_CONFIG(qml_interpreter) -#include "qv4isel_moth_p.h" #endif +#include <qqmlfile.h> #if USE(PTHREADS) # include <pthread.h> @@ -103,48 +134,39 @@ #include <valgrind/memcheck.h> #endif -QT_BEGIN_NAMESPACE +#endif // #ifndef V4_BOOTSTRAP -using namespace QV4; +QT_BEGIN_NAMESPACE -static QBasicAtomicInt engineSerial = Q_BASIC_ATOMIC_INITIALIZER(1); +Q_LOGGING_CATEGORY(lcTracingAll, "qt.v4.tracing.all") -void throwTypeError(const BuiltinFunction *, Scope &scope, CallData *) -{ - scope.result = scope.engine->throwTypeError(); -} +using namespace QV4; +#ifndef V4_BOOTSTRAP -#ifdef V4_BOOTSTRAP -QJSEngine *ExecutionEngine::jsEngine() const -{ - return v8Engine->publicEngine(); -} +static QBasicAtomicInt engineSerial = Q_BASIC_ATOMIC_INITIALIZER(1); -QQmlEngine *ExecutionEngine::qmlEngine() const +ReturnedValue throwTypeError(const FunctionObject *b, const QV4::Value *, const QV4::Value *, int) { - return v8Engine->engine(); + return b->engine()->throwTypeError(); } -#endif // V4_BOOTSTRAP qint32 ExecutionEngine::maxCallDepth = -1; -ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) +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(0) - , v8Engine(0) - , argumentsAccessors(0) - , nArgumentsAccessors(0) + , globalCode(nullptr) + , v8Engine(nullptr) + , publicEngine(jsEngine) , m_engineId(engineSerial.fetchAndAddOrdered(1)) - , regExpCache(0) - , m_multiplyWrappedQObjects(0) -#ifndef QT_NO_QML_DEBUGGER - , m_debugger(0) - , m_profiler(0) + , regExpCache(nullptr) + , m_multiplyWrappedQObjects(nullptr) +#if defined(V4_ENABLE_JIT) && !defined(V4_BOOTSTRAP) + , m_canAllocateExecutableMemory(OSAllocator::canAllocateExecutableMemory()) #endif { memoryManager = new QV4::MemoryManager(this); @@ -153,38 +175,15 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) bool ok = false; maxCallDepth = qEnvironmentVariableIntValue("QV4_MAX_CALL_DEPTH", &ok); if (!ok || maxCallDepth <= 0) { +#ifdef QT_NO_DEBUG maxCallDepth = 1234; - } - } - Q_ASSERT(maxCallDepth > 0); - - if (!factory) { -#if QT_CONFIG(qml_interpreter) - bool jitDisabled = true; - -#ifdef V4_ENABLE_JIT - static const bool forceMoth = !qEnvironmentVariableIsEmpty("QV4_FORCE_INTERPRETER") || - !OSAllocator::canAllocateExecutableMemory(); - if (forceMoth) { - factory = new Moth::ISelFactory; - } else { - factory = new JIT::ISelFactory<>; - jitDisabled = false; - } -#else // !V4_ENABLE_JIT - factory = new Moth::ISelFactory; -#endif // V4_ENABLE_JIT - - if (jitDisabled) { - qWarning("JIT is disabled for QML. Property bindings and animations will be " - "very slow. Visit https://wiki.qt.io/V4 to learn about possible " - "solutions for your platform."); - } #else - factory = new JIT::ISelFactory<>; + // no (tail call) optimization is done, so there'll be a lot mare stack frames active + maxCallDepth = 200; #endif + } } - iselFactory.reset(factory); + Q_ASSERT(maxCallDepth > 0); // reserve space for the JS stack // we allow it to grow to a bit more than JSStackLimit, as we can overshoot due to ScopedValues @@ -203,27 +202,54 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) /* writable */ true, /* executable */ false, /* includesGuardPages */ true); + { + bool ok = false; + jitCallCountThreshold = qEnvironmentVariableIntValue("QV4_JIT_CALL_THRESHOLD", &ok); + if (!ok) + jitCallCountThreshold = 3; + if (qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER")) + jitCallCountThreshold = std::numeric_limits<int>::max(); + } + exceptionValue = jsAlloca(1); + *exceptionValue = Encode::undefined(); globalObject = static_cast<Object *>(jsAlloca(1)); jsObjects = jsAlloca(NJSObjects); typedArrayPrototype = static_cast<Object *>(jsAlloca(NTypedArrayTypes)); typedArrayCtors = static_cast<FunctionObject *>(jsAlloca(NTypedArrayTypes)); jsStrings = jsAlloca(NJSStrings); + jsSymbols = jsAlloca(NJSSymbols); // set up stack limits jsStackLimit = jsStackBase + JSStackLimit/sizeof(Value); identifierTable = new IdentifierTable(this); - classPool = new InternalClassPool; + memset(classes, 0, sizeof(classes)); + classes[Class_Empty] = memoryManager->allocIC<InternalClass>(); + classes[Class_Empty]->init(this); - internalClasses[Class_Empty] = new (classPool) InternalClass(this); - internalClasses[Class_String] = internalClasses[EngineBase::Class_Empty]->changeVTable(QV4::String::staticVTable()); - internalClasses[Class_MemberData] = internalClasses[EngineBase::Class_Empty]->changeVTable(QV4::MemberData::staticVTable()); - internalClasses[Class_SimpleArrayData] = internalClasses[EngineBase::Class_Empty]->changeVTable(QV4::SimpleArrayData::staticVTable()); - internalClasses[Class_SparseArrayData] = internalClasses[EngineBase::Class_Empty]->changeVTable(QV4::SparseArrayData::staticVTable()); - internalClasses[Class_ExecutionContext] = internalClasses[EngineBase::Class_Empty]->changeVTable(QV4::ExecutionContext::staticVTable()); - internalClasses[Class_SimpleCallContext] = internalClasses[EngineBase::Class_Empty]->changeVTable(QV4::CallContext::staticVTable()); + 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<InternalClass> ic(scope); + ic = classes[Class_Empty]->changeVTable(QV4::Object::staticVTable()); + jsObjects[ObjectProto] = memoryManager->allocObject<ObjectPrototype>(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<StringPrototype>(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<SymbolPrototype>(); + 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")); @@ -233,6 +259,8 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) 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")); @@ -255,146 +283,212 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) 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")); - - InternalClass *ic = internalClasses[Class_Empty]->changeVTable(QV4::Object::staticVTable()); - jsObjects[ObjectProto] = memoryManager->allocObject<ObjectPrototype>(ic); - internalClasses[Class_Object] = ic->changePrototype(objectPrototype()->d()); + 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->prototype); - ic = ic->addMember(id_length(), Attr_NotConfigurable|Attr_NotEnumerable); - Q_ASSERT(ic->prototype); - jsObjects[ArrayProto] = memoryManager->allocObject<ArrayPrototype>(ic, objectPrototype()); - internalClasses[Class_ArrayObject] = ic->changePrototype(arrayPrototype()->d()); - jsObjects[PropertyListProto] = memoryManager->allocObject<PropertyListPrototype>(); - - InternalClass *argsClass = newInternalClass(ArgumentsObject::staticVTable(), objectPrototype()); - argsClass = argsClass->addMember(id_length(), Attr_NotEnumerable); - internalClasses[EngineBase::Class_ArgumentsObject] = argsClass->addMember(id_callee(), Attr_Data|Attr_NotEnumerable); - argsClass = argsClass->addMember(id_callee(), Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable); - internalClasses[EngineBase::Class_StrictArgumentsObject] = argsClass->addMember(id_caller(), Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable); + Q_ASSERT(ic->d()->prototype); + ic = ic->addMember(id_length()->propertyKey(), Attr_NotConfigurable|Attr_NotEnumerable); + Q_ASSERT(ic->d()->prototype); + jsObjects[ArrayProto] = memoryManager->allocObject<ArrayPrototype>(ic->d()); + classes[Class_ArrayObject] = ic->changePrototype(arrayPrototype()->d()); + jsObjects[PropertyListProto] = memoryManager->allocate<PropertyListPrototype>(); + + Scoped<InternalClass> 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<Value *>(globalObject) = newObject(); Q_ASSERT(globalObject->d()->vtable()); initRootContext(); ic = newInternalClass(QV4::StringObject::staticVTable(), objectPrototype()); - ic = ic->addMember(id_length(), Attr_ReadOnly); - jsObjects[StringProto] = memoryManager->allocObject<StringPrototype>(ic); - internalClasses[Class_StringObject] = ic->changePrototype(stringPrototype()->d()); - Q_ASSERT(internalClasses[EngineBase::Class_StringObject]->find(id_length()) == Heap::StringObject::LengthPropertyIndex); + 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)); - jsObjects[NumberProto] = memoryManager->allocObject<NumberPrototype>(); - jsObjects[BooleanProto] = memoryManager->allocObject<BooleanPrototype>(); - jsObjects[DateProto] = memoryManager->allocObject<DatePrototype>(); + classes[Class_SymbolObject] = newInternalClass(QV4::SymbolObject::staticVTable(), symbolPrototype()); - uint index; + jsObjects[NumberProto] = memoryManager->allocate<NumberPrototype>(); + jsObjects[BooleanProto] = memoryManager->allocate<BooleanPrototype>(); + jsObjects[DateProto] = memoryManager->allocate<DatePrototype>(); + +#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()); - ic = ic->addMember(id_prototype(), Attr_NotEnumerable, &index); - Q_ASSERT(index == Heap::FunctionObject::Index_Prototype); - jsObjects[FunctionProto] = memoryManager->allocObject<FunctionPrototype>(ic, 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<FunctionPrototype>(ic->d()); ic = newInternalClass(FunctionObject::staticVTable(), functionPrototype()); - ic = ic->addMember(id_prototype(), Attr_NotEnumerable|Attr_NotConfigurable, &index); - Q_ASSERT(index == Heap::FunctionObject::Index_Prototype); - internalClasses[EngineBase::Class_FunctionObject] = ic; - ic = ic->addMember(id_name(), Attr_ReadOnly, &index); - Q_ASSERT(index == Heap::ScriptFunction::Index_Name); + 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()); - internalClasses[EngineBase::Class_ScriptFunction] = ic->addMember(id_length(), Attr_ReadOnly, &index); - Q_ASSERT(index == Heap::ScriptFunction::Index_Length); - internalClasses[EngineBase::Class_BuiltinFunction] = ic->changeVTable(BuiltinFunction::staticVTable()); - Q_ASSERT(index == Heap::ScriptFunction::Index_Length); - internalClasses[EngineBase::Class_ObjectProto] = internalClasses[Class_Object]->addMember(id_constructor(), Attr_NotEnumerable, &index); - Q_ASSERT(index == Heap::FunctionObject::Index_ProtoConstructor); + 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<GeneratorPrototype>(classes[Class_Object]); + classes[Class_GeneratorObject] = newInternalClass(QV4::GeneratorObject::staticVTable(), generatorPrototype()); - Scope scope(this); ScopedString str(scope); - internalClasses[Class_RegExp] = internalClasses[EngineBase::Class_Empty]->changeVTable(QV4::RegExp::staticVTable()); + classes[Class_RegExp] = classes[Class_Empty]->changeVTable(QV4::RegExp::staticVTable()); ic = newInternalClass(QV4::RegExpObject::staticVTable(), objectPrototype()); - ic = ic->addMember(id_lastIndex(), Attr_NotEnumerable|Attr_NotConfigurable, &index); - Q_ASSERT(index == RegExpObject::Index_LastIndex); - ic = ic->addMember((str = newIdentifier(QStringLiteral("source"))), Attr_ReadOnly, &index); - Q_ASSERT(index == RegExpObject::Index_Source); - ic = ic->addMember((str = newIdentifier(QStringLiteral("global"))), Attr_ReadOnly, &index); - Q_ASSERT(index == RegExpObject::Index_Global); - ic = ic->addMember((str = newIdentifier(QStringLiteral("ignoreCase"))), Attr_ReadOnly, &index); - Q_ASSERT(index == RegExpObject::Index_IgnoreCase); - ic = ic->addMember((str = newIdentifier(QStringLiteral("multiline"))), Attr_ReadOnly, &index); - Q_ASSERT(index == RegExpObject::Index_Multiline); - jsObjects[RegExpProto] = memoryManager->allocObject<RegExpPrototype>(ic, objectPrototype()); - internalClasses[Class_RegExpObject] = ic->changePrototype(regExpPrototype()->d()); - - ic = internalClasses[Class_ArrayObject]->addMember(id_index(), Attr_Data, &index); - Q_ASSERT(index == RegExpObject::Index_ArrayIndex); - internalClasses[EngineBase::Class_RegExpExecArray] = ic->addMember(id_input(), Attr_Data, &index); - Q_ASSERT(index == RegExpObject::Index_ArrayInput); - - ic = newInternalClass(ErrorObject::staticVTable(), 0); - ic = ic->addMember((str = newIdentifier(QStringLiteral("stack"))), Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable, &index); - Q_ASSERT(index == ErrorObject::Index_Stack); - ic = ic->addMember((str = newIdentifier(QStringLiteral("fileName"))), Attr_Data|Attr_NotEnumerable, &index); - Q_ASSERT(index == ErrorObject::Index_FileName); - ic = ic->addMember((str = newIdentifier(QStringLiteral("lineNumber"))), Attr_Data|Attr_NotEnumerable, &index); - internalClasses[EngineBase::Class_ErrorObject] = ic; - Q_ASSERT(index == ErrorObject::Index_LineNumber); - internalClasses[EngineBase::Class_ErrorObjectWithMessage] = ic->addMember((str = newIdentifier(QStringLiteral("message"))), Attr_Data|Attr_NotEnumerable, &index); - Q_ASSERT(index == ErrorObject::Index_Message); - ic = newInternalClass(ErrorObject::staticVTable(), objectPrototype()); - ic = ic->addMember(id_constructor(), Attr_Data|Attr_NotEnumerable, &index); - Q_ASSERT(index == ErrorPrototype::Index_Constructor); - ic = ic->addMember((str = newIdentifier(QStringLiteral("message"))), Attr_Data|Attr_NotEnumerable, &index); - Q_ASSERT(index == ErrorPrototype::Index_Message); - internalClasses[EngineBase::Class_ErrorProto] = ic->addMember(id_name(), Attr_Data|Attr_NotEnumerable, &index); - Q_ASSERT(index == ErrorPrototype::Index_Name); - - jsObjects[GetStack_Function] = BuiltinFunction::create(rootContext(), str = newIdentifier(QStringLiteral("stack")), ErrorObject::method_get_stack); - getStackFunction()->defineReadonlyProperty(id_length(), Primitive::fromInt32(0)); - - jsObjects[ErrorProto] = memoryManager->allocObject<ErrorPrototype>(internalClasses[EngineBase::Class_ErrorProto], objectPrototype()); - jsObjects[EvalErrorProto] = memoryManager->allocObject<EvalErrorPrototype>(internalClasses[EngineBase::Class_ErrorProto]->changePrototype(errorPrototype()->d()), errorPrototype()); - jsObjects[RangeErrorProto] = memoryManager->allocObject<RangeErrorPrototype>(internalClasses[EngineBase::Class_ErrorProto]->changePrototype(errorPrototype()->d()), errorPrototype()); - jsObjects[ReferenceErrorProto] = memoryManager->allocObject<ReferenceErrorPrototype>(internalClasses[EngineBase::Class_ErrorProto]->changePrototype(errorPrototype()->d()), errorPrototype()); - jsObjects[SyntaxErrorProto] = memoryManager->allocObject<SyntaxErrorPrototype>(internalClasses[EngineBase::Class_ErrorProto]->changePrototype(errorPrototype()->d()), errorPrototype()); - jsObjects[TypeErrorProto] = memoryManager->allocObject<TypeErrorPrototype>(internalClasses[EngineBase::Class_ErrorProto]->changePrototype(errorPrototype()->d()), errorPrototype()); - jsObjects[URIErrorProto] = memoryManager->allocObject<URIErrorPrototype>(internalClasses[EngineBase::Class_ErrorProto]->changePrototype(errorPrototype()->d()), errorPrototype()); - - jsObjects[VariantProto] = memoryManager->allocObject<VariantPrototype>(); - Q_ASSERT(variantPrototype()->prototype() == objectPrototype()->d()); - + ic = ic->addMember(id_lastIndex()->propertyKey(), Attr_NotEnumerable|Attr_NotConfigurable, index); + Q_ASSERT(index->index == RegExpObject::Index_LastIndex); + jsObjects[RegExpProto] = memoryManager->allocObject<RegExpPrototype>(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<ErrorPrototype>(classes[Class_ErrorProto]); + ic = classes[Class_ErrorProto]->changePrototype(errorPrototype()->d()); + jsObjects[EvalErrorProto] = memoryManager->allocObject<EvalErrorPrototype>(ic->d()); + jsObjects[RangeErrorProto] = memoryManager->allocObject<RangeErrorPrototype>(ic->d()); + jsObjects[ReferenceErrorProto] = memoryManager->allocObject<ReferenceErrorPrototype>(ic->d()); + jsObjects[SyntaxErrorProto] = memoryManager->allocObject<SyntaxErrorPrototype>(ic->d()); + jsObjects[TypeErrorProto] = memoryManager->allocObject<TypeErrorPrototype>(ic->d()); + jsObjects[URIErrorProto] = memoryManager->allocObject<URIErrorPrototype>(ic->d()); + + jsObjects[VariantProto] = memoryManager->allocate<VariantPrototype>(); + Q_ASSERT(variantPrototype()->getPrototypeOf() == objectPrototype()->d()); + +#if QT_CONFIG(qml_sequence_object) ic = newInternalClass(SequencePrototype::staticVTable(), SequencePrototype::defaultPrototype(this)); - jsObjects[SequenceProto] = ScopedValue(scope, memoryManager->allocObject<SequencePrototype>(ic, SequencePrototype::defaultPrototype(this))); + jsObjects[SequenceProto] = ScopedValue(scope, memoryManager->allocObject<SequencePrototype>(ic->d())); +#endif ExecutionContext *global = rootContext(); - jsObjects[Object_Ctor] = memoryManager->allocObject<ObjectCtor>(global); - jsObjects[String_Ctor] = memoryManager->allocObject<StringCtor>(global); - jsObjects[Number_Ctor] = memoryManager->allocObject<NumberCtor>(global); - jsObjects[Boolean_Ctor] = memoryManager->allocObject<BooleanCtor>(global); - jsObjects[Array_Ctor] = memoryManager->allocObject<ArrayCtor>(global); - jsObjects[Function_Ctor] = memoryManager->allocObject<FunctionCtor>(global); - jsObjects[Date_Ctor] = memoryManager->allocObject<DateCtor>(global); - jsObjects[RegExp_Ctor] = memoryManager->allocObject<RegExpCtor>(global); - jsObjects[Error_Ctor] = memoryManager->allocObject<ErrorCtor>(global); - jsObjects[EvalError_Ctor] = memoryManager->allocObject<EvalErrorCtor>(global); - jsObjects[RangeError_Ctor] = memoryManager->allocObject<RangeErrorCtor>(global); - jsObjects[ReferenceError_Ctor] = memoryManager->allocObject<ReferenceErrorCtor>(global); - jsObjects[SyntaxError_Ctor] = memoryManager->allocObject<SyntaxErrorCtor>(global); - jsObjects[TypeError_Ctor] = memoryManager->allocObject<TypeErrorCtor>(global); - jsObjects[URIError_Ctor] = memoryManager->allocObject<URIErrorCtor>(global); + + jsObjects[Object_Ctor] = memoryManager->allocate<ObjectCtor>(global); + jsObjects[String_Ctor] = memoryManager->allocate<StringCtor>(global); + jsObjects[Symbol_Ctor] = memoryManager->allocate<SymbolCtor>(global); + jsObjects[Number_Ctor] = memoryManager->allocate<NumberCtor>(global); + jsObjects[Boolean_Ctor] = memoryManager->allocate<BooleanCtor>(global); + jsObjects[Array_Ctor] = memoryManager->allocate<ArrayCtor>(global); + jsObjects[Function_Ctor] = memoryManager->allocate<FunctionCtor>(global); + jsObjects[GeneratorFunction_Ctor] = memoryManager->allocate<GeneratorFunctionCtor>(global); + jsObjects[Date_Ctor] = memoryManager->allocate<DateCtor>(global); + jsObjects[RegExp_Ctor] = memoryManager->allocate<RegExpCtor>(global); + jsObjects[Error_Ctor] = memoryManager->allocate<ErrorCtor>(global); + jsObjects[EvalError_Ctor] = memoryManager->allocate<EvalErrorCtor>(global); + jsObjects[RangeError_Ctor] = memoryManager->allocate<RangeErrorCtor>(global); + jsObjects[ReferenceError_Ctor] = memoryManager->allocate<ReferenceErrorCtor>(global); + jsObjects[SyntaxError_Ctor] = memoryManager->allocate<SyntaxErrorCtor>(global); + jsObjects[TypeError_Ctor] = memoryManager->allocate<TypeErrorCtor>(global); + jsObjects[URIError_Ctor] = memoryManager->allocate<URIErrorCtor>(global); + jsObjects[IteratorProto] = memoryManager->allocate<IteratorPrototype>(); + jsObjects[ForInIteratorProto] = memoryManager->allocObject<ForInIteratorPrototype>(newInternalClass(ForInIteratorPrototype::staticVTable(), iteratorPrototype())); + jsObjects[MapIteratorProto] = memoryManager->allocObject<MapIteratorPrototype>(newInternalClass(SetIteratorPrototype::staticVTable(), iteratorPrototype())); + jsObjects[SetIteratorProto] = memoryManager->allocObject<SetIteratorPrototype>(newInternalClass(SetIteratorPrototype::staticVTable(), iteratorPrototype())); + jsObjects[ArrayIteratorProto] = memoryManager->allocObject<ArrayIteratorPrototype>(newInternalClass(ArrayIteratorPrototype::staticVTable(), iteratorPrototype())); + jsObjects[StringIteratorProto] = memoryManager->allocObject<StringIteratorPrototype>(newInternalClass(StringIteratorPrototype::staticVTable(), iteratorPrototype())); + + str = newString(QStringLiteral("get [Symbol.species]")); + jsObjects[GetSymbolSpecies] = FunctionObject::createBuiltinFunction(this, str, ArrayPrototype::method_get_species, 0); static_cast<ObjectPrototype *>(objectPrototype())->init(this, objectCtor()); static_cast<StringPrototype *>(stringPrototype())->init(this, stringCtor()); + static_cast<SymbolPrototype *>(symbolPrototype())->init(this, symbolCtor()); static_cast<NumberPrototype *>(numberPrototype())->init(this, numberCtor()); static_cast<BooleanPrototype *>(booleanPrototype())->init(this, booleanCtor()); static_cast<ArrayPrototype *>(arrayPrototype())->init(this, arrayCtor()); static_cast<PropertyListPrototype *>(propertyListPrototype())->init(this); static_cast<DatePrototype *>(datePrototype())->init(this, dateCtor()); static_cast<FunctionPrototype *>(functionPrototype())->init(this, functionCtor()); + static_cast<GeneratorPrototype *>(generatorPrototype())->init(this, generatorFunctionCtor()); static_cast<RegExpPrototype *>(regExpPrototype())->init(this, regExpCtor()); static_cast<ErrorPrototype *>(errorPrototype())->init(this, errorCtor()); static_cast<EvalErrorPrototype *>(evalErrorPrototype())->init(this, evalErrorCtor()); @@ -404,37 +498,79 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) static_cast<TypeErrorPrototype *>(typeErrorPrototype())->init(this, typeErrorCtor()); static_cast<URIErrorPrototype *>(uRIErrorPrototype())->init(this, uRIErrorCtor()); + static_cast<IteratorPrototype *>(iteratorPrototype())->init(this); + static_cast<ForInIteratorPrototype *>(forInIteratorPrototype())->init(this); + static_cast<MapIteratorPrototype *>(mapIteratorPrototype())->init(this); + static_cast<SetIteratorPrototype *>(setIteratorPrototype())->init(this); + static_cast<ArrayIteratorPrototype *>(arrayIteratorPrototype())->init(this); + static_cast<StringIteratorPrototype *>(stringIteratorPrototype())->init(this); + static_cast<VariantPrototype *>(variantPrototype())->init(); + +#if QT_CONFIG(qml_sequence_object) sequencePrototype()->cast<SequencePrototype>()->init(); +#endif + + jsObjects[WeakMap_Ctor] = memoryManager->allocate<WeakMapCtor>(global); + jsObjects[WeakMapProto] = memoryManager->allocate<WeakMapPrototype>(); + static_cast<WeakMapPrototype *>(weakMapPrototype())->init(this, weakMapCtor()); + jsObjects[Map_Ctor] = memoryManager->allocate<MapCtor>(global); + jsObjects[MapProto] = memoryManager->allocate<MapPrototype>(); + static_cast<MapPrototype *>(mapPrototype())->init(this, mapCtor()); + + jsObjects[WeakSet_Ctor] = memoryManager->allocate<WeakSetCtor>(global); + jsObjects[WeakSetProto] = memoryManager->allocate<WeakSetPrototype>(); + static_cast<WeakSetPrototype *>(weakSetPrototype())->init(this, weakSetCtor()); + + jsObjects[Set_Ctor] = memoryManager->allocate<SetCtor>(global); + jsObjects[SetProto] = memoryManager->allocate<SetPrototype>(); + static_cast<SetPrototype *>(setPrototype())->init(this, setCtor()); + + // + // promises + // + + jsObjects[Promise_Ctor] = memoryManager->allocate<PromiseCtor>(global); + jsObjects[PromiseProto] = memoryManager->allocate<PromisePrototype>(); + static_cast<PromisePrototype *>(promisePrototype())->init(this, promiseCtor()); // typed arrays - jsObjects[ArrayBuffer_Ctor] = memoryManager->allocObject<ArrayBufferCtor>(global); - jsObjects[ArrayBufferProto] = memoryManager->allocObject<ArrayBufferPrototype>(); + jsObjects[SharedArrayBuffer_Ctor] = memoryManager->allocate<SharedArrayBufferCtor>(global); + jsObjects[SharedArrayBufferProto] = memoryManager->allocate<SharedArrayBufferPrototype>(); + static_cast<SharedArrayBufferPrototype *>(sharedArrayBufferPrototype())->init(this, sharedArrayBufferCtor()); + + jsObjects[ArrayBuffer_Ctor] = memoryManager->allocate<ArrayBufferCtor>(global); + jsObjects[ArrayBufferProto] = memoryManager->allocate<ArrayBufferPrototype>(); static_cast<ArrayBufferPrototype *>(arrayBufferPrototype())->init(this, arrayBufferCtor()); - jsObjects[DataView_Ctor] = memoryManager->allocObject<DataViewCtor>(global); - jsObjects[DataViewProto] = memoryManager->allocObject<DataViewPrototype>(); + jsObjects[DataView_Ctor] = memoryManager->allocate<DataViewCtor>(global); + jsObjects[DataViewProto] = memoryManager->allocate<DataViewPrototype>(); static_cast<DataViewPrototype *>(dataViewPrototype())->init(this, dataViewCtor()); - jsObjects[ValueTypeProto] = (Heap::Base *) 0; - jsObjects[SignalHandlerProto] = (Heap::Base *) 0; + jsObjects[ValueTypeProto] = (Heap::Base *) nullptr; + jsObjects[SignalHandlerProto] = (Heap::Base *) nullptr; + + jsObjects[IntrinsicTypedArray_Ctor] = memoryManager->allocate<IntrinsicTypedArrayCtor>(global); + jsObjects[IntrinsicTypedArrayProto] = memoryManager->allocate<IntrinsicTypedArrayPrototype>(); + static_cast<IntrinsicTypedArrayPrototype *>(intrinsicTypedArrayPrototype()) + ->init(this, static_cast<IntrinsicTypedArrayCtor *>(intrinsicTypedArrayCtor())); - for (int i = 0; i < Heap::TypedArray::NTypes; ++i) { - static_cast<Value &>(typedArrayCtors[i]) = memoryManager->allocObject<TypedArrayCtor>(global, Heap::TypedArray::Type(i)); - static_cast<Value &>(typedArrayPrototype[i]) = memoryManager->allocObject<TypedArrayPrototype>(Heap::TypedArray::Type(i)); + for (int i = 0; i < NTypedArrayTypes; ++i) { + static_cast<Value &>(typedArrayCtors[i]) = memoryManager->allocate<TypedArrayCtor>(global, Heap::TypedArray::Type(i)); + static_cast<Value &>(typedArrayPrototype[i]) = memoryManager->allocate<TypedArrayPrototype>(Heap::TypedArray::Type(i)); typedArrayPrototype[i].as<TypedArrayPrototype>()->init(this, static_cast<TypedArrayCtor *>(typedArrayCtors[i].as<Object>())); } // // set up the global object // - rootContext()->d()->global.set(scope.engine, globalObject->d()); - rootContext()->d()->callData->thisObject = globalObject; + 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()); @@ -449,21 +585,31 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) globalObject->defineDefaultProperty(QStringLiteral("SyntaxError"), *syntaxErrorCtor()); globalObject->defineDefaultProperty(QStringLiteral("TypeError"), *typeErrorCtor()); globalObject->defineDefaultProperty(QStringLiteral("URIError"), *uRIErrorCtor()); + globalObject->defineDefaultProperty(QStringLiteral("Promise"), *promiseCtor()); + globalObject->defineDefaultProperty(QStringLiteral("SharedArrayBuffer"), *sharedArrayBufferCtor()); globalObject->defineDefaultProperty(QStringLiteral("ArrayBuffer"), *arrayBufferCtor()); globalObject->defineDefaultProperty(QStringLiteral("DataView"), *dataViewCtor()); - for (int i = 0; i < Heap::TypedArray::NTypes; ++i) - globalObject->defineDefaultProperty((str = typedArrayCtors[i].as<FunctionObject>()->name())->toQString(), typedArrayCtors[i]); + 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<FunctionObject>()->name()), typedArrayCtors[i]); ScopedObject o(scope); - globalObject->defineDefaultProperty(QStringLiteral("Math"), (o = memoryManager->allocObject<MathObject>())); - globalObject->defineDefaultProperty(QStringLiteral("JSON"), (o = memoryManager->allocObject<JsonObject>())); + globalObject->defineDefaultProperty(QStringLiteral("Atomics"), (o = memoryManager->allocate<Atomics>())); + globalObject->defineDefaultProperty(QStringLiteral("Math"), (o = memoryManager->allocate<MathObject>())); + globalObject->defineDefaultProperty(QStringLiteral("JSON"), (o = memoryManager->allocate<JsonObject>())); + globalObject->defineDefaultProperty(QStringLiteral("Reflect"), (o = memoryManager->allocate<Reflect>())); + globalObject->defineDefaultProperty(QStringLiteral("Proxy"), (o = memoryManager->allocate<Proxy>(rootContext()))); - globalObject->defineReadonlyProperty(QStringLiteral("undefined"), Primitive::undefinedValue()); - globalObject->defineReadonlyProperty(QStringLiteral("NaN"), Primitive::fromDouble(std::numeric_limits<double>::quiet_NaN())); - globalObject->defineReadonlyProperty(QStringLiteral("Infinity"), Primitive::fromDouble(Q_INFINITY)); + globalObject->defineReadonlyProperty(QStringLiteral("undefined"), Value::undefinedValue()); + globalObject->defineReadonlyProperty(QStringLiteral("NaN"), Value::fromDouble(std::numeric_limits<double>::quiet_NaN())); + globalObject->defineReadonlyProperty(QStringLiteral("Infinity"), Value::fromDouble(Q_INFINITY)); - jsObjects[Eval_Function] = memoryManager->allocObject<EvalFunction>(global); + jsObjects[Eval_Function] = memoryManager->allocate<EvalFunction>(global); globalObject->defineDefaultProperty(QStringLiteral("eval"), *evalFunction()); // ES6: 20.1.2.12 & 20.1.2.13: @@ -475,11 +621,8 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) Scope scope(this); ScopedString pi(scope, newIdentifier(piString)); ScopedString pf(scope, newIdentifier(pfString)); - ExecutionContext *global = rootContext(); - ScopedFunctionObject parseIntFn(scope, BuiltinFunction::create(global, pi, GlobalFunctions::method_parseInt)); - ScopedFunctionObject parseFloatFn(scope, BuiltinFunction::create(global, pf, GlobalFunctions::method_parseFloat)); - parseIntFn->defineReadonlyConfigurableProperty(id_length(), Primitive::fromInt32(2)); - parseFloatFn->defineReadonlyConfigurableProperty(id_length(), Primitive::fromInt32(1)); + 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); @@ -495,30 +638,36 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory) 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); + ScopedFunctionObject t(scope, memoryManager->allocate<FunctionObject>(rootContext(), nullptr, ::throwTypeError)); + t->defineReadonlyProperty(id_length(), Value::fromInt32(0)); + t->setInternalClass(t->internalClass()->frozen()); + 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); } ExecutionEngine::~ExecutionEngine() { -#ifndef QT_NO_QML_DEBUGGER - delete m_debugger; - m_debugger = 0; - delete m_profiler; - m_profiler = 0; -#endif + if (Q_UNLIKELY(lcTracingAll().isDebugEnabled())) { + for (auto cu : compilationUnits) { + for (auto f : qAsConst(cu->runtimeFunctions)) + qCDebug(lcTracingAll).noquote().nospace() << f->traceInfoToString(); + } + } + + modules.clear(); delete m_multiplyWrappedQObjects; - m_multiplyWrappedQObjects = 0; + m_multiplyWrappedQObjects = nullptr; delete identifierTable; delete memoryManager; - QSet<QV4::CompiledData::CompilationUnit*> remainingUnits; - qSwap(compilationUnits, remainingUnits); - for (QV4::CompiledData::CompilationUnit *unit : qAsConst(remainingUnits)) - unit->unlink(); + while (!compilationUnits.isEmpty()) + (*compilationUnits.begin())->unlink(); - internalClasses[Class_Empty]->destroy(); - delete classPool; delete bumperPointerAllocator; delete regExpCache; delete regExpAllocator; @@ -527,99 +676,99 @@ ExecutionEngine::~ExecutionEngine() delete jsStack; gcStack->deallocate(); delete gcStack; - delete [] argumentsAccessors; } -#ifndef QT_NO_QML_DEBUGGER +ExecutionContext *ExecutionEngine::currentContext() const +{ + return static_cast<ExecutionContext *>(¤tStackFrame->jsFrame->context); +} + +#if QT_CONFIG(qml_debug) void ExecutionEngine::setDebugger(Debugging::Debugger *debugger) { Q_ASSERT(!m_debugger); - m_debugger = debugger; + m_debugger.reset(debugger); } void ExecutionEngine::setProfiler(Profiling::Profiler *profiler) { Q_ASSERT(!m_profiler); - m_profiler = profiler; + m_profiler.reset(profiler); } -#endif // QT_NO_QML_DEBUGGER +#endif // QT_CONFIG(qml_debug) void ExecutionEngine::initRootContext() { Scope scope(this); - Scoped<GlobalContext> r(scope, memoryManager->allocManaged<GlobalContext>( - sizeof(GlobalContext::Data) + sizeof(CallData))); - r->d_unchecked()->init(this); - r->d()->callData = reinterpret_cast<CallData *>(r->d() + 1); - r->d()->callData->tag = quint32(Value::ValueTypeInternal::Integer); - r->d()->callData->argc = 0; - r->d()->callData->thisObject = globalObject; - r->d()->callData->args[0] = Encode::undefined(); + Scoped<ExecutionContext> r(scope, memoryManager->allocManaged<ExecutionContext>(sizeof(ExecutionContext::Data))); + 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); - - currentContext = static_cast<ExecutionContext *>(jsObjects + RootContext); - current = currentContext->d(); -} - -InternalClass *ExecutionEngine::newClass(const InternalClass &other) -{ - return new (classPool) InternalClass(other); } -ExecutionContext *ExecutionEngine::pushGlobalContext() +Heap::InternalClass *ExecutionEngine::newClass(Heap::InternalClass *other) { - pushContext(rootContext()->d()); - - Q_ASSERT(current == rootContext()->d()); - return currentContext; + Heap::InternalClass *ic = memoryManager->allocIC<InternalClass>(); + ic->init(other); + return ic; } -InternalClass *ExecutionEngine::newInternalClass(const VTable *vtable, Object *prototype) +Heap::InternalClass *ExecutionEngine::newInternalClass(const VTable *vtable, Object *prototype) { - return internalClasses[EngineBase::Class_Empty]->changeVTable(vtable)->changePrototype(prototype ? prototype->d() : 0); + Scope scope(this); + Scoped<InternalClass> ic(scope, internalClasses(Class_Empty)->changeVTable(vtable)); + return ic->changePrototype(prototype ? prototype->d() : nullptr); } Heap::Object *ExecutionEngine::newObject() { - return memoryManager->allocObject<Object>(); + return memoryManager->allocate<Object>(); } -Heap::Object *ExecutionEngine::newObject(InternalClass *internalClass, QV4::Object *prototype) +Heap::Object *ExecutionEngine::newObject(Heap::InternalClass *internalClass) { - return memoryManager->allocObject<Object>(internalClass, prototype); + return memoryManager->allocObject<Object>(internalClass); } Heap::String *ExecutionEngine::newString(const QString &s) { - Scope scope(this); - return ScopedString(scope, memoryManager->allocWithStringData<String>(s.length() * sizeof(QChar), s))->d(); + return memoryManager->allocWithStringData<String>(s.length() * sizeof(QChar), s); } Heap::String *ExecutionEngine::newIdentifier(const QString &text) { - return identifierTable->insertString(text); + Scope scope(this); + ScopedString s(scope, memoryManager->allocWithStringData<String>(text.length() * sizeof(QChar), text)); + s->toPropertyKey(); + return s->d(); } Heap::Object *ExecutionEngine::newStringObject(const String *string) { - return memoryManager->allocObject<StringObject>(string); + return memoryManager->allocate<StringObject>(string); +} + +Heap::Object *ExecutionEngine::newSymbolObject(const Symbol *symbol) +{ + return memoryManager->allocObject<SymbolObject>(classes[Class_SymbolObject], symbol); } Heap::Object *ExecutionEngine::newNumberObject(double value) { - return memoryManager->allocObject<NumberObject>(value); + return memoryManager->allocate<NumberObject>(value); } Heap::Object *ExecutionEngine::newBooleanObject(bool b) { - return memoryManager->allocObject<BooleanObject>(b); + return memoryManager->allocate<BooleanObject>(b); } Heap::ArrayObject *ExecutionEngine::newArrayObject(int count) { Scope scope(this); - ScopedArrayObject object(scope, memoryManager->allocObject<ArrayObject>()); + ScopedArrayObject object(scope, memoryManager->allocate<ArrayObject>()); if (count) { if (count < 0x1000) @@ -632,7 +781,7 @@ Heap::ArrayObject *ExecutionEngine::newArrayObject(int count) Heap::ArrayObject *ExecutionEngine::newArrayObject(const Value *values, int length) { Scope scope(this); - ScopedArrayObject a(scope, memoryManager->allocObject<ArrayObject>()); + ScopedArrayObject a(scope, memoryManager->allocate<ArrayObject>()); if (length) { size_t size = sizeof(Heap::ArrayData) + (length-1)*sizeof(Value); @@ -653,76 +802,76 @@ Heap::ArrayObject *ExecutionEngine::newArrayObject(const Value *values, int leng Heap::ArrayObject *ExecutionEngine::newArrayObject(const QStringList &list) { - Scope scope(this); - ScopedArrayObject object(scope, memoryManager->allocObject<ArrayObject>(list)); - return object->d(); + return memoryManager->allocate<ArrayObject>(list); } -Heap::ArrayObject *ExecutionEngine::newArrayObject(InternalClass *internalClass, Object *prototype) +Heap::ArrayObject *ExecutionEngine::newArrayObject(Heap::InternalClass *internalClass) { - Scope scope(this); - ScopedArrayObject object(scope, memoryManager->allocObject<ArrayObject>(internalClass, prototype)); - return object->d(); + return memoryManager->allocObject<ArrayObject>(internalClass); } Heap::ArrayBuffer *ExecutionEngine::newArrayBuffer(const QByteArray &array) { - return memoryManager->allocObject<ArrayBuffer>(array); + return memoryManager->allocate<ArrayBuffer>(array); } Heap::ArrayBuffer *ExecutionEngine::newArrayBuffer(size_t length) { - return memoryManager->allocObject<ArrayBuffer>(length); + return memoryManager->allocate<ArrayBuffer>(length); } Heap::DateObject *ExecutionEngine::newDateObject(const Value &value) { - return memoryManager->allocObject<DateObject>(value); + return memoryManager->allocate<DateObject>(value); } Heap::DateObject *ExecutionEngine::newDateObject(const QDateTime &dt) { Scope scope(this); - Scoped<DateObject> object(scope, memoryManager->allocObject<DateObject>(dt)); + Scoped<DateObject> object(scope, memoryManager->allocate<DateObject>(dt)); return object->d(); } Heap::DateObject *ExecutionEngine::newDateObjectFromTime(const QTime &t) { Scope scope(this); - Scoped<DateObject> object(scope, memoryManager->allocObject<DateObject>(t)); + Scoped<DateObject> object(scope, memoryManager->allocate<DateObject>(t)); 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<RegExp> re(scope, RegExp::create(this, pattern, ignoreCase, multiline)); - return newRegExpObject(re, global); + Scoped<RegExp> re(scope, RegExp::create(this, pattern, static_cast<CompiledData::RegExp::Flags>(flags))); + return newRegExpObject(re); } -Heap::RegExpObject *ExecutionEngine::newRegExpObject(RegExp *re, bool global) +Heap::RegExpObject *ExecutionEngine::newRegExpObject(RegExp *re) { - return memoryManager->allocObject<RegExpObject>(re, global); + return memoryManager->allocate<RegExpObject>(re); } Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegExp &re) { - return memoryManager->allocObject<RegExpObject>(re); + return memoryManager->allocate<RegExpObject>(re); } +#if QT_CONFIG(regularexpression) +Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegularExpression &re) +{ + return memoryManager->allocate<RegExpObject>(re); +} +#endif + Heap::Object *ExecutionEngine::newErrorObject(const Value &value) { - return ErrorObject::create<ErrorObject>(this, value); + return ErrorObject::create<ErrorObject>(this, value, errorCtor()); +} + +Heap::Object *ExecutionEngine::newErrorObject(const QString &message) +{ + return ErrorObject::create<ErrorObject>(this, message); } Heap::Object *ExecutionEngine::newSyntaxErrorObject(const QString &message) @@ -759,38 +908,93 @@ Heap::Object *ExecutionEngine::newRangeErrorObject(const QString &message) Heap::Object *ExecutionEngine::newURIErrorObject(const Value &message) { + return ErrorObject::create<URIErrorObject>(this, message, uRIErrorCtor()); +} + +Heap::PromiseObject *ExecutionEngine::newPromiseObject() +{ + if (!m_reactionHandler) { + m_reactionHandler.reset(new Promise::ReactionHandler); + } + + Scope scope(this); + Scoped<PromiseObject> object(scope, memoryManager->allocate<PromiseObject>(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<CapabilitiesExecutorWrapper> executor(scope, memoryManager->allocate<CapabilitiesExecutorWrapper>()); + 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<URIErrorObject>(this, message); } +Heap::Object *ExecutionEngine::newEvalErrorObject(const QString &message) +{ + return ErrorObject::create<EvalErrorObject>(this, message); +} + Heap::Object *ExecutionEngine::newVariantObject(const QVariant &v) { - return memoryManager->allocObject<VariantObject>(v); + return memoryManager->allocate<VariantObject>(v); } -Heap::Object *ExecutionEngine::newForEachIteratorObject(Object *o) +Heap::Object *ExecutionEngine::newForInIteratorObject(Object *o) { Scope scope(this); - ScopedObject obj(scope, memoryManager->allocObject<ForEachIteratorObject>(o)); + ScopedObject obj(scope, memoryManager->allocate<ForInIteratorObject>(o)); return obj->d(); } -Heap::QmlContext *ExecutionEngine::qmlContext() const +Heap::Object *ExecutionEngine::newMapIteratorObject(Object *o) { - Heap::ExecutionContext *ctx = current; + return memoryManager->allocate<MapIteratorObject>(o->d(), this); +} - // get the correct context when we're within a builtin function - if (ctx->type == Heap::ExecutionContext::Type_SimpleCallContext && !ctx->outer) - ctx = parentContext(currentContext)->d(); +Heap::Object *ExecutionEngine::newSetIteratorObject(Object *o) +{ + return memoryManager->allocate<SetIteratorObject>(o->d(), this); +} + +Heap::Object *ExecutionEngine::newArrayIteratorObject(Object *o) +{ + return memoryManager->allocate<ArrayIteratorObject>(o->d(), this); +} + +Heap::QmlContext *ExecutionEngine::qmlContext() const +{ + if (!currentStackFrame) + return nullptr; + Heap::ExecutionContext *ctx = currentContext()->d(); if (ctx->type != Heap::ExecutionContext::Type_QmlContext && !ctx->outer) - return 0; + return nullptr; while (ctx->outer && ctx->outer->type != Heap::ExecutionContext::Type_GlobalContext) ctx = ctx->outer; Q_ASSERT(ctx); if (ctx->type != Heap::ExecutionContext::Type_QmlContext) - return 0; + return nullptr; return static_cast<Heap::QmlContext *>(ctx); } @@ -799,91 +1003,46 @@ QObject *ExecutionEngine::qmlScopeObject() const { Heap::QmlContext *ctx = qmlContext(); if (!ctx) - return 0; - - return ctx->qml->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); + return nullptr; - if (QObject *qobjectSingleton = siinfo->qobjectApi(e)) - return QV4::QObjectWrapper::wrap(this, qobjectSingleton); - return QJSValuePrivate::convertedToValue(this, siinfo->scriptApi(e)); + return ctx->qml()->scopeObject; } QQmlContextData *ExecutionEngine::callingQmlContext() const { Heap::QmlContext *ctx = qmlContext(); if (!ctx) - return 0; + return nullptr; - return ctx->qml->context->contextData(); + return ctx->qml()->context->contextData(); } -QVector<StackFrame> ExecutionEngine::stackTrace(int frameLimit) const +StackTrace ExecutionEngine::stackTrace(int frameLimit) const { Scope scope(const_cast<ExecutionEngine *>(this)); ScopedString name(scope); - QVector<StackFrame> stack; - - ExecutionContext *c = currentContext; - while (c && frameLimit) { - QV4::Function *function = c->getFunction(); - if (function) { - StackFrame frame; - frame.source = function->sourceFile(); - name = function->name(); - frame.function = name->toQString(); - - // line numbers can be negative for places where you can't set a real breakpoint - frame.line = qAbs(c->d()->lineNumber); - frame.column = -1; - + StackTrace stack; + + CppStackFrame *f = currentStackFrame; + while (f && frameLimit) { + QV4::StackFrame frame; + frame.source = f->source(); + frame.function = f->function(); + frame.line = qAbs(f->lineNumber()); + frame.column = -1; + stack.append(frame); + if (f->isTailCalling) { + QV4::StackFrame frame; + frame.function = QStringLiteral("[elided tail calls]"); stack.append(frame); - --frameLimit; } - c = parentContext(c); + --frameLimit; + f = f->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<StackFrame> 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) @@ -924,18 +1083,17 @@ QUrl ExecutionEngine::resolvedUrl(const QString &file) return src; QUrl base; - ExecutionContext *c = currentContext; - while (c) { - SimpleCallContext *callCtx = c->asSimpleCallContext(); - if (callCtx && callCtx->d()->v4Function) { - base.setUrl(callCtx->d()->v4Function->sourceFile()); + CppStackFrame *f = currentStackFrame; + while (f) { + if (f->v4Function) { + base = f->v4Function->finalUrl(); break; } - c = parentContext(c); + f = f->parent; } if (base.isEmpty() && globalCode) - base.setUrl(globalCode->sourceFile()); + base = globalCode->finalUrl(); if (base.isEmpty()) return src; @@ -943,49 +1101,19 @@ QUrl ExecutionEngine::resolvedUrl(const QString &file) 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; - } - ExecutionContext *global = rootContext(); - for (int i = oldSize; i < nArgumentsAccessors; ++i) { - argumentsAccessors[i].value = ScopedValue(scope, memoryManager->allocObject<ArgumentsGetterFunction>(global, i)); - argumentsAccessors[i].set = ScopedValue(scope, memoryManager->allocObject<ArgumentsSetterFunction>(global, i)); - } - } -} - void ExecutionEngine::markObjects(MarkStack *markStack) { - identifierTable->mark(markStack); + for (int i = 0; i < NClasses; ++i) + if (classes[i]) + classes[i]->mark(markStack); + markStack->drain(); - for (int i = 0; i < nArgumentsAccessors; ++i) { - const Property &pd = argumentsAccessors[i]; - if (Heap::FunctionObject *getter = pd.getter()) - getter->mark(markStack); - if (Heap::FunctionObject *setter = pd.setter()) - setter->mark(markStack); - } + identifierTable->markObjects(markStack); - classPool->markObjects(markStack); - - for (QSet<CompiledData::CompilationUnit*>::ConstIterator it = compilationUnits.constBegin(), end = compilationUnits.constEnd(); - it != end; ++it) - (*it)->markObjects(markStack); + for (auto compilationUnit: compilationUnits) { + compilationUnit->markObjects(markStack); + markStack->drain(); + } } ReturnedValue ExecutionEngine::throwError(const Value &value) @@ -1020,7 +1148,7 @@ ReturnedValue ExecutionEngine::catchException(StackTrace *trace) exceptionStackTrace.clear(); hasException = false; ReturnedValue res = exceptionValue->asReturnedValue(); - *exceptionValue = Primitive::emptyValue(); + *exceptionValue = Value::emptyValue(); return res; } @@ -1123,12 +1251,7 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError() error.setColumn(frame.column); } QV4::Scoped<QV4::ErrorObject> 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()); + error.setDescription(exception->toQStringNoThrow()); return error; } @@ -1137,11 +1260,12 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError() typedef QSet<QV4::Heap::Object *> V4ObjectSet; static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int typeHint, bool createJSValueForObjects, V4ObjectSet *visitedObjects); static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &value); -static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects = 0); +static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr); static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &value, const QByteArray &targetType, void **result); static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVariantList &lst); +static QV4::ReturnedValue sequentialIterableToJS(QV4::ExecutionEngine *v4, const QSequentialIterable &lst); static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVariantMap &vmap); static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant &value) { @@ -1151,7 +1275,7 @@ static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant & QVariant ExecutionEngine::toVariant(const Value &value, int typeHint, bool createJSValueForObjects) { - return ::toVariant(this, value, typeHint, createJSValueForObjects, 0); + return ::toVariant(this, value, typeHint, createJSValueForObjects, nullptr); } @@ -1187,8 +1311,11 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int return v->toVariant(); } else if (QV4::QmlListWrapper *l = object->as<QV4::QmlListWrapper>()) { return l->toVariant(); - } else if (object->isListType()) +#if QT_CONFIG(qml_sequence_object) + } else if (object->isListType()) { return QV4::SequencePrototype::toVariant(object); +#endif + } } if (value.as<ArrayObject>()) { @@ -1198,7 +1325,7 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int uint length = a->getLength(); QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope); for (uint ii = 0; ii < length; ++ii) { - qobjectWrapper = a->getIndexed(ii); + qobjectWrapper = a->get(ii); if (!!qobjectWrapper) { list << qobjectWrapper->object(); } else { @@ -1211,10 +1338,12 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int return QVariant::fromValue(QV4::JsonObject::toJsonArray(a)); } +#if QT_CONFIG(qml_sequence_object) bool succeeded = false; QVariant retn = QV4::SequencePrototype::toVariant(value, typeHint, &succeeded); if (succeeded) return retn; +#endif } if (value.isUndefined()) @@ -1234,8 +1363,10 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int return str.at(0); return str; } +#if QT_CONFIG(qml_locale) if (const QV4::QQmlLocaleData *ld = value.as<QV4::QQmlLocaleData>()) return *ld->d()->locale; +#endif if (const QV4::DateObject *d = value.as<DateObject>()) return d->toQDateTime(); if (const ArrayBuffer *d = value.as<ArrayBuffer>()) @@ -1245,8 +1376,13 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int QV4::ScopedObject o(scope, value); Q_ASSERT(o); - if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>()) + if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>()) { +#if QT_CONFIG(regularexpression) + if (typeHint != QMetaType::QRegExp) + return re->toQRegularExpression(); +#endif return re->toQRegExp(); + } if (createJSValueForObjects) return QVariant::fromValue(QJSValue(scope.engine, o->asReturnedValue())); @@ -1281,7 +1417,7 @@ static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V int length = a->getLength(); for (int ii = 0; ii < length; ++ii) { - v = a->getIndexed(ii); + v = a->get(ii); list << ::toVariant(e, v, -1, /*createJSValueForObjects*/false, visitedObjects); } @@ -1308,38 +1444,6 @@ static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V 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(); @@ -1384,13 +1488,18 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) case QMetaType::QDateTime: return QV4::Encode(newDateObject(*reinterpret_cast<const QDateTime *>(ptr))); case QMetaType::QDate: - return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast<const QDate *>(ptr)))); + return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast<const QDate *>(ptr), QTime(0, 0, 0), Qt::UTC))); case QMetaType::QTime: return QV4::Encode(newDateObjectFromTime(*reinterpret_cast<const QTime *>(ptr))); case QMetaType::QRegExp: return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegExp *>(ptr))); +#if QT_CONFIG(regularexpression) + case QMetaType::QRegularExpression: + return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegularExpression *>(ptr))); +#endif case QMetaType::QObjectStar: return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(ptr)); +#if QT_CONFIG(qml_sequence_object) case QMetaType::QStringList: { bool succeeded = false; @@ -1400,18 +1509,21 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) return retn->asReturnedValue(); return QV4::Encode(newArrayObject(*reinterpret_cast<const QStringList *>(ptr))); } +#endif case QMetaType::QVariantList: - return arrayFromVariantList(this, *reinterpret_cast<const QVariantList *>(ptr)); + return variantListToJS(this, *reinterpret_cast<const QVariantList *>(ptr)); case QMetaType::QVariantMap: - return objectFromVariantMap(this, *reinterpret_cast<const QVariantMap *>(ptr)); + return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(ptr)); case QMetaType::QJsonValue: return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast<const QJsonValue *>(ptr)); case QMetaType::QJsonObject: return QV4::JsonObject::fromJsonObject(this, *reinterpret_cast<const QJsonObject *>(ptr)); case QMetaType::QJsonArray: return QV4::JsonObject::fromJsonArray(this, *reinterpret_cast<const QJsonArray *>(ptr)); +#if QT_CONFIG(qml_locale) case QMetaType::QLocale: return QQmlLocale::wrap(this, *reinterpret_cast<const QLocale*>(ptr)); +#endif default: break; } @@ -1451,10 +1563,17 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) if (objOk) return QV4::QObjectWrapper::wrap(this, obj); +#if QT_CONFIG(qml_sequence_object) bool succeeded = false; QV4::ScopedValue retn(scope, QV4::SequencePrototype::fromVariant(this, variant, &succeeded)); if (succeeded) return retn->asReturnedValue(); +#endif + + if (QMetaType::hasRegisteredConverterFunction(type, qMetaTypeId<QtMetaTypePrivate::QSequentialIterableImpl>())) { + QSequentialIterable lst = variant.value<QSequentialIterable>(); + return sequentialIterableToJS(this, lst); + } if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(type)) return QV4::QQmlValueTypeWrapper::create(this, variant, vtmo, type); @@ -1489,6 +1608,22 @@ static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVaria return a.asReturnedValue(); } +// Converts a QSequentialIterable to JS. +// The result is a new Array object with length equal to the length +// of the QSequentialIterable, and the elements being the QSequentialIterable's +// elements converted to JS, recursively. +static QV4::ReturnedValue sequentialIterableToJS(QV4::ExecutionEngine *v4, const QSequentialIterable &lst) +{ + 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 @@ -1498,11 +1633,13 @@ static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVarian 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()); - uint idx = s->asArrayIndex(); + uint idx = key->asArrayIndex(); if (idx < UINT_MAX) o->arraySet(idx, v); else @@ -1515,97 +1652,142 @@ static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVarian // Returns the value if conversion succeeded, an empty handle otherwise. QV4::ReturnedValue ExecutionEngine::metaTypeToJS(int type, const void *data) { - Q_ASSERT(data != 0); + Q_ASSERT(data != nullptr); - // check if it's one of the types we know - switch (QMetaType::Type(type)) { - case QMetaType::UnknownType: - case QMetaType::Void: - return QV4::Encode::undefined(); - case QMetaType::Nullptr: - case QMetaType::VoidStar: - return QV4::Encode::null(); - case QMetaType::Bool: - return QV4::Encode(*reinterpret_cast<const bool*>(data)); - case QMetaType::Int: - return QV4::Encode(*reinterpret_cast<const int*>(data)); - case QMetaType::UInt: - return QV4::Encode(*reinterpret_cast<const uint*>(data)); - case QMetaType::LongLong: - return QV4::Encode(double(*reinterpret_cast<const qlonglong*>(data))); - case QMetaType::ULongLong: -#if defined(Q_OS_WIN) && defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 12008804 -#pragma message("** NOTE: You need the Visual Studio Processor Pack to compile support for 64bit unsigned integers.") - return QV4::Encode(double((qlonglong)*reinterpret_cast<const qulonglong*>(data))); -#elif defined(Q_CC_MSVC) && !defined(Q_CC_MSVC_NET) - return QV4::Encode(double((qlonglong)*reinterpret_cast<const qulonglong*>(data))); -#else - return QV4::Encode(double(*reinterpret_cast<const qulonglong*>(data))); -#endif - case QMetaType::Double: - return QV4::Encode(*reinterpret_cast<const double*>(data)); - case QMetaType::QString: - return newString(*reinterpret_cast<const QString*>(data))->asReturnedValue(); - case QMetaType::QByteArray: - return newArrayBuffer(*reinterpret_cast<const QByteArray*>(data))->asReturnedValue(); - case QMetaType::Float: - return QV4::Encode(*reinterpret_cast<const float*>(data)); - case QMetaType::Short: - return QV4::Encode((int)*reinterpret_cast<const short*>(data)); - case QMetaType::UShort: - return QV4::Encode((int)*reinterpret_cast<const unsigned short*>(data)); - case QMetaType::Char: - return QV4::Encode((int)*reinterpret_cast<const char*>(data)); - case QMetaType::UChar: - return QV4::Encode((int)*reinterpret_cast<const unsigned char*>(data)); - case QMetaType::QChar: - return QV4::Encode((int)(*reinterpret_cast<const QChar*>(data)).unicode()); - case QMetaType::QStringList: - return QV4::Encode(newArrayObject(*reinterpret_cast<const QStringList *>(data))); - case QMetaType::QVariantList: - return variantListToJS(this, *reinterpret_cast<const QVariantList *>(data)); - case QMetaType::QVariantMap: - return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(data)); - case QMetaType::QDateTime: - return QV4::Encode(newDateObject(*reinterpret_cast<const QDateTime *>(data))); - case QMetaType::QDate: - return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast<const QDate *>(data)))); - case QMetaType::QRegExp: - return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegExp *>(data))); - case QMetaType::QObjectStar: - return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(data)); - case QMetaType::QVariant: + QVariant variant(type, data); + if (QMetaType::Type(variant.type()) == QMetaType::QVariant) { + // unwrap it: this is tested in QJSEngine, and makes the most sense for + // end-user code too. return variantToJS(this, *reinterpret_cast<const QVariant*>(data)); - case QMetaType::QJsonValue: - return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast<const QJsonValue *>(data)); - case QMetaType::QJsonObject: - return QV4::JsonObject::fromJsonObject(this, *reinterpret_cast<const QJsonObject *>(data)); - case QMetaType::QJsonArray: - return QV4::JsonObject::fromJsonArray(this, *reinterpret_cast<const QJsonArray *>(data)); - default: - if (type == qMetaTypeId<QJSValue>()) { - return QJSValuePrivate::convertedToValue(this, *reinterpret_cast<const QJSValue*>(data)); + } + return fromVariant(variant); +} + +ReturnedValue ExecutionEngine::global() +{ + return globalObject->asReturnedValue(); +} + +QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(const QUrl &url) +{ + 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<CompiledData::CompilationUnit> ExecutionEngine::compileModule(const QUrl &url, const QString &sourceCode, const QDateTime &sourceTimeStamp) +{ + QList<QQmlJS::DiagnosticMessage> diagnostics; + auto unit = compileModule(/*debugMode*/debugger() != nullptr, url.toString(), sourceCode, sourceTimeStamp, &diagnostics); + for (const QQmlJS::DiagnosticMessage &m : diagnostics) { + if (m.isError()) { + throwSyntaxError(m.message, url.toString(), m.loc.startLine, m.loc.startColumn); + return nullptr; } else { - QByteArray typeName = QMetaType::typeName(type); - if (typeName.endsWith('*') && !*reinterpret_cast<void* const *>(data)) { - return QV4::Encode::null(); - } - QMetaType mt(type); - if (mt.flags() & QMetaType::IsGadget) { - Q_ASSERT(mt.metaObject()); - return QV4::QQmlValueTypeWrapper::create(this, QVariant(type, data), mt.metaObject(), type); - } - // Fall back to wrapping in a QVariant. - return QV4::Encode(newVariantObject(QVariant(type, data))); + qWarning() << url << ':' << m.loc.startLine << ':' << m.loc.startColumn + << ": warning: " << m.message; } } - Q_UNREACHABLE(); - return 0; + return unit; +} + +#endif // ifndef V4_BOOTSTRAP + +QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::compileModule(bool debugMode, const QString &url, const QString &sourceCode, + const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics) +{ + QQmlJS::Engine ee; + QQmlJS::Lexer lexer(&ee); + lexer.setCode(sourceCode, /*line*/1, /*qml mode*/false); + QQmlJS::Parser parser(&ee); + + const bool parsed = parser.parseModule(); + + if (diagnostics) + *diagnostics = parser.diagnosticMessages(); + + if (!parsed) + return nullptr; + + QQmlJS::AST::ESModule *moduleNode = QQmlJS::AST::cast<QQmlJS::AST::ESModule*>(parser.rootNode()); + if (!moduleNode) { + // if parsing was successful, and we have no module, then + // the file was empty. + if (diagnostics) + diagnostics->clear(); + return nullptr; + } + + using namespace QV4::Compiler; + Compiler::Module compilerModule(debugMode); + compilerModule.unitFlags |= CompiledData::Unit::IsESModule; + compilerModule.sourceTimeStamp = sourceTimeStamp; + JSUnitGenerator jsGenerator(&compilerModule); + Codegen cg(&jsGenerator, /*strictMode*/true); + cg.generateFromModule(url, url, sourceCode, moduleNode, &compilerModule); + auto errors = cg.errors(); + if (diagnostics) + *diagnostics << errors; + + if (!errors.isEmpty()) + return nullptr; + + return cg.generateCompilationUnit(); } -void ExecutionEngine::failStackLimitCheck(Scope &scope) +#ifndef V4_BOOTSTRAP + +void ExecutionEngine::injectModule(const QQmlRefPointer<CompiledData::CompilationUnit> &moduleUnit) { - scope.result = throwRangeError(QStringLiteral("Maximum call stack size exceeded.")); + // Injection can happen from the QML type loader thread for example, but instantiation and + // evaluation must be limited to the ExecutionEngine's thread. + QMutexLocker moduleGuard(&moduleMutex); + modules.insert(moduleUnit->finalUrl(), moduleUnit); +} + +QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::moduleForUrl(const QUrl &_url, const CompiledData::CompilationUnit *referrer) const +{ + QUrl url = QQmlTypeLoader::normalize(_url); + if (referrer) + url = referrer->finalUrl().resolved(url); + + QMutexLocker moduleGuard(&moduleMutex); + auto existingModule = modules.find(url); + if (existingModule == modules.end()) + return nullptr; + return *existingModule; +} + +QQmlRefPointer<CompiledData::CompilationUnit> ExecutionEngine::loadModule(const QUrl &_url, const CompiledData::CompilationUnit *referrer) +{ + QUrl url = QQmlTypeLoader::normalize(_url); + if (referrer) + url = referrer->finalUrl().resolved(url); + + QMutexLocker moduleGuard(&moduleMutex); + auto existingModule = modules.find(url); + if (existingModule != modules.end()) + return *existingModule; + + moduleGuard.unlock(); + + auto newModule = compileModule(url); + if (newModule) { + moduleGuard.relock(); + modules.insert(url, newModule); + } + + return newModule; } // Converts a JS value to a meta-type. @@ -1683,6 +1865,13 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) *reinterpret_cast<QRegExp *>(data) = r->toQRegExp(); return true; } break; +#if QT_CONFIG(regularexpression) + case QMetaType::QRegularExpression: + if (const QV4::RegExpObject *r = value->as<QV4::RegExpObject>()) { + *reinterpret_cast<QRegularExpression *>(data) = r->toQRegularExpression(); + return true; + } break; +#endif case QMetaType::QObjectStar: { const QV4::QObjectWrapper *qobjectWrapper = value->as<QV4::QObjectWrapper>(); if (qobjectWrapper || value->isNull()) { @@ -1777,7 +1966,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) } else if (Object *o = value->objectValue()) { // Look in the prototype chain. QV4::Scope scope(this); - QV4::ScopedObject proto(scope, o->prototype()); + QV4::ScopedObject proto(scope, o->getPrototypeOf()); while (proto) { bool canCast = false; if (QV4::VariantObject *vo = proto->as<QV4::VariantObject>()) { @@ -1788,7 +1977,7 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) 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; + canCast = qobject->qt_metacast(className) != nullptr; } if (canCast) { QByteArray varTypeName = QMetaType::typeName(var.userType()); @@ -1798,11 +1987,11 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) *reinterpret_cast<void* *>(data) = var.data(); return true; } - proto = proto->prototype(); + proto = proto->getPrototypeOf(); } } } else if (value->isNull() && name.endsWith('*')) { - *reinterpret_cast<void* *>(data) = 0; + *reinterpret_cast<void* *>(data) = nullptr; return true; } else if (type == qMetaTypeId<QJSValue>()) { *reinterpret_cast<QJSValue*>(data) = QJSValue(this, value->asReturnedValue()); @@ -1830,7 +2019,7 @@ static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &va static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &value) { if (!value.isObject()) - return 0; + return nullptr; QV4::Scope scope(engine); QV4::Scoped<QV4::VariantObject> v(scope, value); @@ -1843,9 +2032,10 @@ static QObject *qtObjectFromJS(QV4::ExecutionEngine *engine, const QV4::Value &v } QV4::Scoped<QV4::QObjectWrapper> wrapper(scope, value); if (!wrapper) - return 0; + return nullptr; return wrapper->object(); } +#endif // ifndef V4_BOOTSTRAP QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 16a043dda5..3735c24601 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -51,14 +51,17 @@ // #include "qv4global_p.h" -#include "private/qv4isel_p.h" #include "qv4managed_p.h" #include "qv4context_p.h" #include <private/qintrusivelist_p.h> #include "qv4enginebase_p.h" +#include <private/qqmlrefcount_p.h> +#include <private/qqmljsengine_p.h> #ifndef V4_BOOTSTRAP +# include "qv4function_p.h" # include <private/qv8engine_p.h> +# include <private/qv4compileddata_p.h> #endif namespace WTF { @@ -85,8 +88,15 @@ namespace CompiledData { struct CompilationUnit; } -struct InternalClass; -struct InternalClassPool; +namespace Heap { +struct Module; +}; + +struct Function; + +namespace Promise { +class ReactionHandler; +}; struct Q_QML_EXPORT ExecutionEngine : public EngineBase { @@ -99,7 +109,6 @@ private: public: ExecutableAllocator *executableAllocator; ExecutableAllocator *regExpAllocator; - QScopedPointer<EvalISelFactory> iselFactory; WTF::BumpPointerAllocator *bumperPointerAllocator; // Used by Yarr Regex engine. @@ -114,8 +123,6 @@ public: QML_NEARLY_ALWAYS_INLINE Value *jsAlloca(int nValues) { Value *ptr = jsStackTop; jsStackTop = ptr + nValues; - for (int i = 0; i < nValues; ++i) - ptr[i] = Primitive::undefinedValue(); return ptr; } @@ -125,22 +132,27 @@ public: QJSEngine *jsEngine() const; QQmlEngine *qmlEngine() const; #else // !V4_BOOTSTRAP - QJSEngine *jsEngine() const { return v8Engine->publicEngine(); } + QJSEngine *jsEngine() const { return publicEngine; } QQmlEngine *qmlEngine() const { return v8Engine ? v8Engine->engine() : nullptr; } #endif // V4_BOOTSTRAP QV8Engine *v8Engine; + QJSEngine *publicEngine; enum JSObjects { RootContext, + ScriptContext, IntegerNull, // Has to come after the RootContext to make the context stack safe ObjectProto, + SymbolProto, ArrayProto, + ArrayProtoValues, PropertyListProto, StringProto, NumberProto, BooleanProto, DateProto, FunctionProto, + GeneratorProto, RegExpProto, ErrorProto, EvalErrorProto, @@ -149,19 +161,36 @@ public: SyntaxErrorProto, TypeErrorProto, URIErrorProto, + PromiseProto, VariantProto, +#if QT_CONFIG(qml_sequence_object) SequenceProto, +#endif + SharedArrayBufferProto, ArrayBufferProto, DataViewProto, + WeakSetProto, + SetProto, + WeakMapProto, + MapProto, + IntrinsicTypedArrayProto, ValueTypeProto, SignalHandlerProto, + IteratorProto, + ForInIteratorProto, + SetIteratorProto, + MapIteratorProto, + ArrayIteratorProto, + StringIteratorProto, Object_Ctor, String_Ctor, + Symbol_Ctor, Number_Ctor, Boolean_Ctor, Array_Ctor, Function_Ctor, + GeneratorFunction_Ctor, Date_Ctor, RegExp_Ctor, Error_Ctor, @@ -171,8 +200,17 @@ public: SyntaxError_Ctor, TypeError_Ctor, URIError_Ctor, + SharedArrayBuffer_Ctor, + Promise_Ctor, ArrayBuffer_Ctor, DataView_Ctor, + WeakSet_Ctor, + Set_Ctor, + WeakMap_Ctor, + Map_Ctor, + IntrinsicTypedArray_Ctor, + + GetSymbolSpecies, Eval_Function, GetStack_Function, @@ -182,13 +220,17 @@ public: Value *jsObjects; enum { NTypedArrayTypes = 9 }; // == TypedArray::NValues, avoid header dependency - GlobalContext *rootContext() const { return reinterpret_cast<GlobalContext *>(jsObjects + RootContext); } + ExecutionContext *rootContext() const { return reinterpret_cast<ExecutionContext *>(jsObjects + RootContext); } + ExecutionContext *scriptContext() const { return reinterpret_cast<ExecutionContext *>(jsObjects + ScriptContext); } + void setScriptContext(ReturnedValue c) { jsObjects[ScriptContext] = c; } FunctionObject *objectCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Object_Ctor); } FunctionObject *stringCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + String_Ctor); } + FunctionObject *symbolCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Symbol_Ctor); } FunctionObject *numberCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Number_Ctor); } FunctionObject *booleanCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Boolean_Ctor); } FunctionObject *arrayCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Array_Ctor); } FunctionObject *functionCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Function_Ctor); } + FunctionObject *generatorFunctionCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + GeneratorFunction_Ctor); } FunctionObject *dateCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Date_Ctor); } FunctionObject *regExpCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + RegExp_Ctor); } FunctionObject *errorCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Error_Ctor); } @@ -198,18 +240,30 @@ public: FunctionObject *syntaxErrorCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + SyntaxError_Ctor); } FunctionObject *typeErrorCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + TypeError_Ctor); } FunctionObject *uRIErrorCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + URIError_Ctor); } + FunctionObject *sharedArrayBufferCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + SharedArrayBuffer_Ctor); } + FunctionObject *promiseCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Promise_Ctor); } FunctionObject *arrayBufferCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + ArrayBuffer_Ctor); } FunctionObject *dataViewCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + DataView_Ctor); } + FunctionObject *weakSetCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + WeakSet_Ctor); } + FunctionObject *setCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Set_Ctor); } + FunctionObject *weakMapCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + WeakMap_Ctor); } + FunctionObject *mapCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + Map_Ctor); } + FunctionObject *intrinsicTypedArrayCtor() const { return reinterpret_cast<FunctionObject *>(jsObjects + IntrinsicTypedArray_Ctor); } FunctionObject *typedArrayCtors; + FunctionObject *getSymbolSpecies() const { return reinterpret_cast<FunctionObject *>(jsObjects + GetSymbolSpecies); } + Object *objectPrototype() const { return reinterpret_cast<Object *>(jsObjects + ObjectProto); } + Object *symbolPrototype() const { return reinterpret_cast<Object *>(jsObjects + SymbolProto); } Object *arrayPrototype() const { return reinterpret_cast<Object *>(jsObjects + ArrayProto); } + Object *arrayProtoValues() const { return reinterpret_cast<Object *>(jsObjects + ArrayProtoValues); } Object *propertyListPrototype() const { return reinterpret_cast<Object *>(jsObjects + PropertyListProto); } Object *stringPrototype() const { return reinterpret_cast<Object *>(jsObjects + StringProto); } Object *numberPrototype() const { return reinterpret_cast<Object *>(jsObjects + NumberProto); } Object *booleanPrototype() const { return reinterpret_cast<Object *>(jsObjects + BooleanProto); } Object *datePrototype() const { return reinterpret_cast<Object *>(jsObjects + DateProto); } Object *functionPrototype() const { return reinterpret_cast<Object *>(jsObjects + FunctionProto); } + Object *generatorPrototype() const { return reinterpret_cast<Object *>(jsObjects + GeneratorProto); } Object *regExpPrototype() const { return reinterpret_cast<Object *>(jsObjects + RegExpProto); } Object *errorPrototype() const { return reinterpret_cast<Object *>(jsObjects + ErrorProto); } Object *evalErrorPrototype() const { return reinterpret_cast<Object *>(jsObjects + EvalErrorProto); } @@ -218,24 +272,35 @@ public: Object *syntaxErrorPrototype() const { return reinterpret_cast<Object *>(jsObjects + SyntaxErrorProto); } Object *typeErrorPrototype() const { return reinterpret_cast<Object *>(jsObjects + TypeErrorProto); } Object *uRIErrorPrototype() const { return reinterpret_cast<Object *>(jsObjects + URIErrorProto); } + Object *promisePrototype() const { return reinterpret_cast<Object *>(jsObjects + PromiseProto); } Object *variantPrototype() const { return reinterpret_cast<Object *>(jsObjects + VariantProto); } +#if QT_CONFIG(qml_sequence_object) Object *sequencePrototype() const { return reinterpret_cast<Object *>(jsObjects + SequenceProto); } +#endif + Object *sharedArrayBufferPrototype() const { return reinterpret_cast<Object *>(jsObjects + SharedArrayBufferProto); } Object *arrayBufferPrototype() const { return reinterpret_cast<Object *>(jsObjects + ArrayBufferProto); } Object *dataViewPrototype() const { return reinterpret_cast<Object *>(jsObjects + DataViewProto); } + Object *weakSetPrototype() const { return reinterpret_cast<Object *>(jsObjects + WeakSetProto); } + Object *setPrototype() const { return reinterpret_cast<Object *>(jsObjects + SetProto); } + Object *weakMapPrototype() const { return reinterpret_cast<Object *>(jsObjects + WeakMapProto); } + Object *mapPrototype() const { return reinterpret_cast<Object *>(jsObjects + MapProto); } + Object *intrinsicTypedArrayPrototype() const { return reinterpret_cast<Object *>(jsObjects + IntrinsicTypedArrayProto); } Object *typedArrayPrototype; Object *valueTypeWrapperPrototype() const { return reinterpret_cast<Object *>(jsObjects + ValueTypeProto); } Object *signalHandlerPrototype() const { return reinterpret_cast<Object *>(jsObjects + SignalHandlerProto); } + Object *iteratorPrototype() const { return reinterpret_cast<Object *>(jsObjects + IteratorProto); } + Object *forInIteratorPrototype() const { return reinterpret_cast<Object *>(jsObjects + ForInIteratorProto); } + Object *setIteratorPrototype() const { return reinterpret_cast<Object *>(jsObjects + SetIteratorProto); } + Object *mapIteratorPrototype() const { return reinterpret_cast<Object *>(jsObjects + MapIteratorProto); } + Object *arrayIteratorPrototype() const { return reinterpret_cast<Object *>(jsObjects + ArrayIteratorProto); } + Object *stringIteratorPrototype() const { return reinterpret_cast<Object *>(jsObjects + StringIteratorProto); } - InternalClassPool *classPool; EvalFunction *evalFunction() const { return reinterpret_cast<EvalFunction *>(jsObjects + Eval_Function); } FunctionObject *getStackFunction() const { return reinterpret_cast<FunctionObject *>(jsObjects + GetStack_Function); } FunctionObject *thrower() const { return reinterpret_cast<FunctionObject *>(jsObjects + ThrowerObject); } - Property *argumentsAccessors; - int nArgumentsAccessors; - enum JSStrings { String_Empty, String_undefined, @@ -245,6 +310,8 @@ public: String_boolean, String_number, String_string, + String_default, + String_symbol, String_object, String_function, String_length, @@ -267,16 +334,46 @@ public: String_index, String_input, String_toString, + String_toLocaleString, String_destroy, String_valueOf, String_byteLength, String_byteOffset, String_buffer, String_lastIndex, + String_next, + String_done, + String_return, + String_throw, + String_global, + String_ignoreCase, + String_multiline, + String_unicode, + String_sticky, + String_source, + String_flags, + NJSStrings }; Value *jsStrings; + enum JSSymbols { + Symbol_hasInstance, + Symbol_isConcatSpreadable, + Symbol_iterator, + Symbol_match, + Symbol_replace, + Symbol_search, + Symbol_species, + Symbol_split, + Symbol_toPrimitive, + Symbol_toStringTag, + Symbol_unscopables, + Symbol_revokableProxy, + NJSSymbols + }; + Value *jsSymbols; + String *id_empty() const { return reinterpret_cast<String *>(jsStrings + String_Empty); } String *id_undefined() const { return reinterpret_cast<String *>(jsStrings + String_undefined); } String *id_null() const { return reinterpret_cast<String *>(jsStrings + String_null); } @@ -285,6 +382,8 @@ public: String *id_boolean() const { return reinterpret_cast<String *>(jsStrings + String_boolean); } String *id_number() const { return reinterpret_cast<String *>(jsStrings + String_number); } String *id_string() const { return reinterpret_cast<String *>(jsStrings + String_string); } + String *id_default() const { return reinterpret_cast<String *>(jsStrings + String_default); } + String *id_symbol() const { return reinterpret_cast<String *>(jsStrings + String_symbol); } String *id_object() const { return reinterpret_cast<String *>(jsStrings + String_object); } String *id_function() const { return reinterpret_cast<String *>(jsStrings + String_function); } String *id_length() const { return reinterpret_cast<String *>(jsStrings + String_length); } @@ -307,14 +406,41 @@ public: String *id_index() const { return reinterpret_cast<String *>(jsStrings + String_index); } String *id_input() const { return reinterpret_cast<String *>(jsStrings + String_input); } String *id_toString() const { return reinterpret_cast<String *>(jsStrings + String_toString); } + String *id_toLocaleString() const { return reinterpret_cast<String *>(jsStrings + String_toLocaleString); } String *id_destroy() const { return reinterpret_cast<String *>(jsStrings + String_destroy); } String *id_valueOf() const { return reinterpret_cast<String *>(jsStrings + String_valueOf); } String *id_byteLength() const { return reinterpret_cast<String *>(jsStrings + String_byteLength); } String *id_byteOffset() const { return reinterpret_cast<String *>(jsStrings + String_byteOffset); } String *id_buffer() const { return reinterpret_cast<String *>(jsStrings + String_buffer); } String *id_lastIndex() const { return reinterpret_cast<String *>(jsStrings + String_lastIndex); } + String *id_next() const { return reinterpret_cast<String *>(jsStrings + String_next); } + String *id_done() const { return reinterpret_cast<String *>(jsStrings + String_done); } + String *id_return() const { return reinterpret_cast<String *>(jsStrings + String_return); } + String *id_throw() const { return reinterpret_cast<String *>(jsStrings + String_throw); } + String *id_global() const { return reinterpret_cast<String *>(jsStrings + String_global); } + String *id_ignoreCase() const { return reinterpret_cast<String *>(jsStrings + String_ignoreCase); } + String *id_multiline() const { return reinterpret_cast<String *>(jsStrings + String_multiline); } + String *id_unicode() const { return reinterpret_cast<String *>(jsStrings + String_unicode); } + String *id_sticky() const { return reinterpret_cast<String *>(jsStrings + String_sticky); } + String *id_source() const { return reinterpret_cast<String *>(jsStrings + String_source); } + String *id_flags() const { return reinterpret_cast<String *>(jsStrings + String_flags); } + + Symbol *symbol_hasInstance() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_hasInstance); } + Symbol *symbol_isConcatSpreadable() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_isConcatSpreadable); } + Symbol *symbol_iterator() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_iterator); } + Symbol *symbol_match() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_match); } + Symbol *symbol_replace() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_replace); } + Symbol *symbol_search() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_search); } + Symbol *symbol_species() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_species); } + Symbol *symbol_split() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_split); } + Symbol *symbol_toPrimitive() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_toPrimitive); } + Symbol *symbol_toStringTag() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_toStringTag); } + Symbol *symbol_unscopables() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_unscopables); } + Symbol *symbol_revokableProxy() const { return reinterpret_cast<Symbol *>(jsSymbols + Symbol_revokableProxy); } - QSet<CompiledData::CompilationUnit*> compilationUnits; +#ifndef V4_BOOTSTRAP + QIntrusiveList<CompiledData::CompilationUnit, &CompiledData::CompilationUnit::nextCompilationUnit> compilationUnits; +#endif quint32 m_engineId; @@ -338,46 +464,51 @@ public: // but any time a QObject is wrapped a second time in another engine, we have to do // bookkeeping. MultiplyWrappedQObjectMap *m_multiplyWrappedQObjects; +#if defined(V4_ENABLE_JIT) && !defined(V4_BOOTSTRAP) + const bool m_canAllocateExecutableMemory; +#endif + + quintptr protoIdCount = 1; - ExecutionEngine(EvalISelFactory *iselFactory = 0); + ExecutionEngine(QJSEngine *jsEngine = nullptr); ~ExecutionEngine(); -#ifdef QT_NO_QML_DEBUGGER +#if !QT_CONFIG(qml_debug) QV4::Debugging::Debugger *debugger() const { return nullptr; } QV4::Profiling::Profiler *profiler() const { return nullptr; } void setDebugger(Debugging::Debugger *) {} void setProfiler(Profiling::Profiler *) {} #else - QV4::Debugging::Debugger *debugger() const { return m_debugger; } - QV4::Profiling::Profiler *profiler() const { return m_profiler; } + QV4::Debugging::Debugger *debugger() const { return m_debugger.data(); } + QV4::Profiling::Profiler *profiler() const { return m_profiler.data(); } void setDebugger(Debugging::Debugger *debugger); void setProfiler(Profiling::Profiler *profiler); -#endif // QT_NO_QML_DEBUGGER +#endif // QT_CONFIG(qml_debug) - ExecutionContext *pushGlobalContext(); - void pushContext(Heap::ExecutionContext *context); - void pushContext(ExecutionContext *context); - void popContext(); - ExecutionContext *parentContext(ExecutionContext *context) const; + ExecutionContext *currentContext() const; - InternalClass *newInternalClass(const VTable *vtable, Object *prototype); + // ensure we always get odd prototype IDs. This helps make marking in QV4::Lookup fast + quintptr newProtoId() { return (protoIdCount += 2); } + + Heap::InternalClass *newInternalClass(const VTable *vtable, Object *prototype); Heap::Object *newObject(); - Heap::Object *newObject(InternalClass *internalClass, Object *prototype); + Heap::Object *newObject(Heap::InternalClass *internalClass); Heap::String *newString(const QString &s = QString()); Heap::String *newIdentifier(const QString &text); Heap::Object *newStringObject(const String *string); + Heap::Object *newSymbolObject(const Symbol *symbol); Heap::Object *newNumberObject(double value); Heap::Object *newBooleanObject(bool b); Heap::ArrayObject *newArrayObject(int count = 0); Heap::ArrayObject *newArrayObject(const Value *values, int length); Heap::ArrayObject *newArrayObject(const QStringList &list); - Heap::ArrayObject *newArrayObject(InternalClass *ic, Object *prototype); + Heap::ArrayObject *newArrayObject(Heap::InternalClass *ic); Heap::ArrayBuffer *newArrayBuffer(const QByteArray &array); Heap::ArrayBuffer *newArrayBuffer(size_t length); @@ -387,46 +518,53 @@ public: Heap::DateObject *newDateObjectFromTime(const QTime &t); Heap::RegExpObject *newRegExpObject(const QString &pattern, int flags); - Heap::RegExpObject *newRegExpObject(RegExp *re, bool global); + Heap::RegExpObject *newRegExpObject(RegExp *re); Heap::RegExpObject *newRegExpObject(const QRegExp &re); +#if QT_CONFIG(regularexpression) + Heap::RegExpObject *newRegExpObject(const QRegularExpression &re); +#endif Heap::Object *newErrorObject(const Value &value); + Heap::Object *newErrorObject(const QString &message); Heap::Object *newSyntaxErrorObject(const QString &message, const QString &fileName, int line, int column); Heap::Object *newSyntaxErrorObject(const QString &message); Heap::Object *newReferenceErrorObject(const QString &message); Heap::Object *newReferenceErrorObject(const QString &message, const QString &fileName, int line, int column); Heap::Object *newTypeErrorObject(const QString &message); Heap::Object *newRangeErrorObject(const QString &message); + Heap::Object *newURIErrorObject(const QString &message); Heap::Object *newURIErrorObject(const Value &message); + Heap::Object *newEvalErrorObject(const QString &message); + + Heap::PromiseObject *newPromiseObject(); + Heap::Object *newPromiseObject(const QV4::FunctionObject *thisObject, const QV4::PromiseCapability *capability); + Promise::ReactionHandler *getPromiseReactionHandler(); Heap::Object *newVariantObject(const QVariant &v); - Heap::Object *newForEachIteratorObject(Object *o); + Heap::Object *newForInIteratorObject(Object *o); + Heap::Object *newSetIteratorObject(Object *o); + Heap::Object *newMapIteratorObject(Object *o); + Heap::Object *newArrayIteratorObject(Object *o); Heap::QmlContext *qmlContext() const; QObject *qmlScopeObject() const; - ReturnedValue qmlSingletonWrapper(String *name); QQmlContextData *callingQmlContext() const; StackTrace stackTrace(int frameLimit = -1) const; - StackFrame currentStackFrame() const; QUrl resolvedUrl(const QString &file); - void requireArgumentsAccessors(int n); - void markObjects(MarkStack *markStack); void initRootContext(); - InternalClass *newClass(const InternalClass &other); + Heap::InternalClass *newClass(Heap::InternalClass *other); - // Exception handling - Value *exceptionValue; StackTrace exceptionStackTrace; ReturnedValue throwError(const Value &value); - ReturnedValue catchException(StackTrace *trace = 0); + ReturnedValue catchException(StackTrace *trace = nullptr); ReturnedValue throwError(const QString &message); ReturnedValue throwSyntaxError(const QString &message); @@ -452,93 +590,50 @@ public: bool metaTypeFromJS(const Value *value, int type, void *data); QV4::ReturnedValue metaTypeToJS(int type, const void *data); - bool checkStackLimits(Scope &scope); + bool checkStackLimits(); -private: - void failStackLimitCheck(Scope &scope); - -#ifndef QT_NO_QML_DEBUGGER - QV4::Debugging::Debugger *m_debugger; - QV4::Profiling::Profiler *m_profiler; -#endif -}; - -// This is a trick to tell the code generators that functions taking a NoThrowContext won't -// throw exceptions and therefore don't need a check after the call. -#ifndef V4_BOOTSTRAP -struct NoThrowEngine : public ExecutionEngine -{ -}; + bool canJIT(Function *f = nullptr) + { +#if defined(V4_ENABLE_JIT) && !defined(V4_BOOTSTRAP) + if (!m_canAllocateExecutableMemory) + return false; + if (f) + return !f->isGenerator() && f->interpreterCallCount >= jitCallCountThreshold; + return true; #else -struct NoThrowEngine; + Q_UNUSED(f); + return false; #endif + } + QV4::ReturnedValue global(); -inline void ExecutionEngine::pushContext(Heap::ExecutionContext *context) -{ - Q_ASSERT(currentContext && context); - Value *v = jsAlloca(2); - v[0] = Encode(context); - v[1] = Encode((int)(v - static_cast<Value *>(currentContext))); - currentContext = static_cast<ExecutionContext *>(v); - current = currentContext->d(); -} - -inline void ExecutionEngine::pushContext(ExecutionContext *context) -{ - pushContext(context->d()); -} - - -inline void ExecutionEngine::popContext() -{ - Q_ASSERT(jsStackTop > currentContext); - QV4::Value *offset = (currentContext + 1); - Q_ASSERT(offset->isInteger()); - int o = offset->integerValue(); - Q_ASSERT(o); - currentContext -= o; - current = currentContext->d(); -} + double localTZA = 0.0; // local timezone, initialized at startup -inline ExecutionContext *ExecutionEngine::parentContext(ExecutionContext *context) const -{ - Value *offset = static_cast<Value *>(context) + 1; - Q_ASSERT(offset->isInteger()); - int o = offset->integerValue(); - return o ? context - o : 0; -} - -inline -void Heap::Base::mark(QV4::MarkStack *markStack) -{ - Q_ASSERT(inUse()); - const HeapItem *h = reinterpret_cast<const HeapItem *>(this); - Chunk *c = h->chunk(); - size_t index = h - c->realBase(); - Q_ASSERT(!Chunk::testBit(c->extendsBitmap, index)); - quintptr *bitmap = c->blackBitmap + Chunk::bitmapIndex(index); - quintptr bit = Chunk::bitForIndex(index); - if (!(*bitmap & bit)) { - *bitmap |= bit; - markStack->push(this); - } -} + static QQmlRefPointer<CompiledData::CompilationUnit> compileModule(bool debugMode, const QString &url, const QString &sourceCode, const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics); +#ifndef V4_BOOTSTRAP + QQmlRefPointer<CompiledData::CompilationUnit> compileModule(const QUrl &url); + QQmlRefPointer<CompiledData::CompilationUnit> compileModule(const QUrl &url, const QString &sourceCode, const QDateTime &sourceTimeStamp); + + mutable QMutex moduleMutex; + QHash<QUrl, QQmlRefPointer<CompiledData::CompilationUnit>> modules; + void injectModule(const QQmlRefPointer<CompiledData::CompilationUnit> &moduleUnit); + QQmlRefPointer<CompiledData::CompilationUnit> moduleForUrl(const QUrl &_url, const CompiledData::CompilationUnit *referrer = nullptr) const; + QQmlRefPointer<CompiledData::CompilationUnit> loadModule(const QUrl &_url, const CompiledData::CompilationUnit *referrer = nullptr); +#endif -inline void Value::mark(MarkStack *markStack) -{ - Heap::Base *o = heapObject(); - if (o) - o->mark(markStack); -} +private: +#if QT_CONFIG(qml_debug) + QScopedPointer<QV4::Debugging::Debugger> m_debugger; + QScopedPointer<QV4::Profiling::Profiler> m_profiler; +#endif + int jitCallCountThreshold; -inline void Managed::mark(MarkStack *markStack) -{ - Q_ASSERT(m()); - m()->mark(markStack); -} + // used by generated Promise objects to handle 'then' events + QScopedPointer<QV4::Promise::ReactionHandler> m_reactionHandler; +}; -#define CHECK_STACK_LIMITS(v4, scope) if ((v4)->checkStackLimits(scope)) return; \ +#define CHECK_STACK_LIMITS(v4) if ((v4)->checkStackLimits()) return Encode::undefined(); \ ExecutionEngineCallDepthRecorder _executionEngineCallDepthRecorder(v4); struct ExecutionEngineCallDepthRecorder @@ -549,10 +644,10 @@ struct ExecutionEngineCallDepthRecorder ~ExecutionEngineCallDepthRecorder() { --ee->callDepth; } }; -inline bool ExecutionEngine::checkStackLimits(Scope &scope) +inline bool ExecutionEngine::checkStackLimits() { if (Q_UNLIKELY((jsStackTop > jsStackLimit) || (callDepth >= maxCallDepth))) { - failStackLimitCheck(scope); + throwRangeError(QStringLiteral("Maximum call stack size exceeded.")); return true; } diff --git a/src/qml/jsruntime/qv4enginebase_p.h b/src/qml/jsruntime/qv4enginebase_p.h index 88f9dfd85c..b5cfea8863 100644 --- a/src/qml/jsruntime/qv4enginebase_p.h +++ b/src/qml/jsruntime/qv4enginebase_p.h @@ -57,46 +57,57 @@ QT_BEGIN_NAMESPACE namespace QV4 { +struct CppStackFrame; + // Base class for the execution engine #if defined(Q_CC_MSVC) || defined(Q_CC_GNU) #pragma pack(push, 1) #endif -struct EngineBase { - Heap::ExecutionContext *current = 0; +struct Q_QML_EXPORT EngineBase { + + CppStackFrame *currentStackFrame = nullptr; - Value *jsStackTop = 0; + Value *jsStackTop = nullptr; quint8 hasException = false; quint8 writeBarrierActive = false; quint16 unused = 0; -#if QT_POINTER_SIZE == 8 - quint8 padding[4]; -#endif - MemoryManager *memoryManager = 0; + quint8 isExecutingInRegExpJIT = false; + quint8 padding[3]; + MemoryManager *memoryManager = nullptr; Runtime runtime; qint32 callDepth = 0; - Value *jsStackLimit = 0; - Value *jsStackBase = 0; + Value *jsStackLimit = nullptr; + Value *jsStackBase = nullptr; + + IdentifierTable *identifierTable = nullptr; + Object *globalObject = nullptr; - ExecutionContext *currentContext = 0; - IdentifierTable *identifierTable = 0; - Object *globalObject = 0; + // Exception handling + Value *exceptionValue = nullptr; - enum { + enum InternalClassType { Class_Empty, Class_String, Class_MemberData, Class_SimpleArrayData, Class_SparseArrayData, Class_ExecutionContext, - Class_SimpleCallContext, + Class_CallContext, + Class_QmlContext, Class_Object, Class_ArrayObject, Class_FunctionObject, + Class_ArrowFunction, + Class_GeneratorFunction, + Class_GeneratorObject, Class_StringObject, + Class_SymbolObject, Class_ScriptFunction, - Class_BuiltinFunction, + Class_ConstructorFunction, + Class_MemberFunction, + Class_MemberGeneratorFunction, Class_ObjectProto, Class_RegExp, Class_RegExpObject, @@ -106,19 +117,24 @@ struct EngineBase { Class_ErrorObject, Class_ErrorObjectWithMessage, Class_ErrorProto, + Class_QmlContextWrapper, + Class_ProxyObject, + Class_ProxyFunctionObject, + Class_Symbol, NClasses }; - InternalClass *internalClasses[NClasses]; + Heap::InternalClass *classes[NClasses]; + Heap::InternalClass *internalClasses(InternalClassType icType) { return classes[icType]; } }; #if defined(Q_CC_MSVC) || defined(Q_CC_GNU) #pragma pack(pop) #endif Q_STATIC_ASSERT(std::is_standard_layout<EngineBase>::value); -Q_STATIC_ASSERT(offsetof(EngineBase, current) == 0); -Q_STATIC_ASSERT(offsetof(EngineBase, jsStackTop) == offsetof(EngineBase, current) + QT_POINTER_SIZE); +Q_STATIC_ASSERT(offsetof(EngineBase, currentStackFrame) == 0); +Q_STATIC_ASSERT(offsetof(EngineBase, jsStackTop) == offsetof(EngineBase, currentStackFrame) + QT_POINTER_SIZE); Q_STATIC_ASSERT(offsetof(EngineBase, hasException) == offsetof(EngineBase, jsStackTop) + QT_POINTER_SIZE); -Q_STATIC_ASSERT(offsetof(EngineBase, memoryManager) == offsetof(EngineBase, hasException) + QT_POINTER_SIZE); +Q_STATIC_ASSERT(offsetof(EngineBase, memoryManager) == offsetof(EngineBase, hasException) + 8); Q_STATIC_ASSERT(offsetof(EngineBase, runtime) == offsetof(EngineBase, memoryManager) + QT_POINTER_SIZE); } diff --git a/src/qml/jsruntime/qv4errorobject.cpp b/src/qml/jsruntime/qv4errorobject.cpp index b3bd28e18b..c6d6c77d11 100644 --- a/src/qml/jsruntime/qv4errorobject.cpp +++ b/src/qml/jsruntime/qv4errorobject.cpp @@ -47,11 +47,6 @@ #include "qv4string_p.h" #include <private/qv4mm_p.h> -#include <private/qqmljsengine_p.h> -#include <private/qqmljslexer_p.h> -#include <private/qqmljsparser_p.h> -#include <private/qqmljsast_p.h> -#include <qv4jsir_p.h> #include <qv4codegen_p.h> #ifndef Q_OS_WIN @@ -75,13 +70,13 @@ void Heap::ErrorObject::init() Scope scope(internalClass->engine); Scoped<QV4::ErrorObject> e(scope, this); - if (internalClass == scope.engine->internalClasses[EngineBase::Class_ErrorProto]) + if (internalClass == scope.engine->internalClasses(EngineBase::Class_ErrorProto)) return; setProperty(scope.engine, QV4::ErrorObject::Index_Stack, scope.engine->getStackFunction()->d()); - setProperty(scope.engine, QV4::ErrorObject::Index_Stack + QV4::Object::SetterOffset, Primitive::undefinedValue()); - setProperty(scope.engine, QV4::ErrorObject::Index_FileName, Primitive::undefinedValue()); - setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Primitive::undefinedValue()); + setProperty(scope.engine, QV4::ErrorObject::Index_StackSetter, Value::undefinedValue()); + setProperty(scope.engine, QV4::ErrorObject::Index_FileName, Value::undefinedValue()); + setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Value::undefinedValue()); } void Heap::ErrorObject::init(const Value &message, ErrorType t) @@ -93,12 +88,12 @@ void Heap::ErrorObject::init(const Value &message, ErrorType t) Scoped<QV4::ErrorObject> e(scope, this); setProperty(scope.engine, QV4::ErrorObject::Index_Stack, scope.engine->getStackFunction()->d()); - setProperty(scope.engine, QV4::ErrorObject::Index_Stack + QV4::Object::SetterOffset, Primitive::undefinedValue()); + setProperty(scope.engine, QV4::ErrorObject::Index_StackSetter, Value::undefinedValue()); e->d()->stackTrace = new StackTrace(scope.engine->stackTrace()); if (!e->d()->stackTrace->isEmpty()) { setProperty(scope.engine, QV4::ErrorObject::Index_FileName, scope.engine->newString(e->d()->stackTrace->at(0).source)); - setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Primitive::fromInt32(e->d()->stackTrace->at(0).line)); + setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Value::fromInt32(e->d()->stackTrace->at(0).line)); } if (!message.isUndefined()) @@ -107,6 +102,7 @@ void Heap::ErrorObject::init(const Value &message, ErrorType t) void Heap::ErrorObject::init(const Value &message, const QString &fileName, int line, int column, ErrorObject::ErrorType t) { + Q_UNUSED(fileName); // #### Object::init(); errorType = t; @@ -114,7 +110,7 @@ void Heap::ErrorObject::init(const Value &message, const QString &fileName, int Scoped<QV4::ErrorObject> e(scope, this); setProperty(scope.engine, QV4::ErrorObject::Index_Stack, scope.engine->getStackFunction()->d()); - setProperty(scope.engine, QV4::ErrorObject::Index_Stack + QV4::Object::SetterOffset, Primitive::undefinedValue()); + setProperty(scope.engine, QV4::ErrorObject::Index_StackSetter, Value::undefinedValue()); e->d()->stackTrace = new StackTrace(scope.engine->stackTrace()); StackFrame frame; @@ -123,10 +119,9 @@ void Heap::ErrorObject::init(const Value &message, const QString &fileName, int frame.column = column; e->d()->stackTrace->prepend(frame); - if (!e->d()->stackTrace->isEmpty()) { - setProperty(scope.engine, QV4::ErrorObject::Index_FileName, scope.engine->newString(e->d()->stackTrace->at(0).source)); - setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Primitive::fromInt32(e->d()->stackTrace->at(0).line)); - } + Q_ASSERT(!e->d()->stackTrace->isEmpty()); + setProperty(scope.engine, QV4::ErrorObject::Index_FileName, scope.engine->newString(e->d()->stackTrace->at(0).source)); + setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Value::fromInt32(e->d()->stackTrace->at(0).line)); if (!message.isUndefined()) setProperty(scope.engine, QV4::ErrorObject::Index_Message, message); @@ -153,11 +148,12 @@ const char *ErrorObject::className(Heap::ErrorObject::ErrorType t) Q_UNREACHABLE(); } -void ErrorObject::method_get_stack(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ErrorObject::method_get_stack(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<ErrorObject> This(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + const ErrorObject *This = thisObject->as<ErrorObject>(); if (!This) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); if (!This->d()->stack) { QString trace; for (int i = 0; i < This->d()->stackTrace->count(); ++i) { @@ -168,9 +164,9 @@ void ErrorObject::method_get_stack(const BuiltinFunction *, Scope &scope, CallDa if (frame.line >= 0) trace += QLatin1Char(':') + QString::number(frame.line); } - This->d()->stack.set(scope.engine, scope.engine->newString(trace)); + This->d()->stack.set(v4, v4->newString(trace)); } - scope.result = This->d()->stack; + return This->d()->stack->asReturnedValue(); } DEFINE_OBJECT_VTABLE(ErrorObject); @@ -233,81 +229,81 @@ void Heap::ErrorCtor::init(QV4::ExecutionContext *scope, const QString &name) Heap::FunctionObject::init(scope, name); } -void ErrorCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue ErrorCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ScopedValue v(scope, callData->argument(0)); - scope.result = ErrorObject::create<ErrorObject>(scope.engine, v); + Value v = argc ? *argv : Value::undefinedValue(); + return ErrorObject::create<ErrorObject>(f->engine(), v, newTarget)->asReturnedValue(); } -void ErrorCtor::call(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue ErrorCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc) { - static_cast<const Object *>(that)->construct(scope, callData); + return f->callAsConstructor(argv, argc); } void Heap::EvalErrorCtor::init(QV4::ExecutionContext *scope) { - Heap::ErrorCtor::init(scope, QStringLiteral("EvalError")); + Heap::FunctionObject::init(scope, QStringLiteral("EvalError")); } -void EvalErrorCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue EvalErrorCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ScopedValue v(scope, callData->argument(0)); - scope.result = ErrorObject::create<EvalErrorObject>(scope.engine, v); + Value v = argc ? *argv : Value::undefinedValue(); + return ErrorObject::create<EvalErrorObject>(f->engine(), v, newTarget)->asReturnedValue(); } void Heap::RangeErrorCtor::init(QV4::ExecutionContext *scope) { - Heap::ErrorCtor::init(scope, QStringLiteral("RangeError")); + Heap::FunctionObject::init(scope, QStringLiteral("RangeError")); } -void RangeErrorCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue RangeErrorCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ScopedValue v(scope, callData->argument(0)); - scope.result = ErrorObject::create<RangeErrorObject>(scope.engine, v); + Value v = argc ? *argv : Value::undefinedValue(); + return ErrorObject::create<RangeErrorObject>(f->engine(), v, newTarget)->asReturnedValue(); } void Heap::ReferenceErrorCtor::init(QV4::ExecutionContext *scope) { - Heap::ErrorCtor::init(scope, QStringLiteral("ReferenceError")); + Heap::FunctionObject::init(scope, QStringLiteral("ReferenceError")); } -void ReferenceErrorCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue ReferenceErrorCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ScopedValue v(scope, callData->argument(0)); - scope.result = ErrorObject::create<ReferenceErrorObject>(scope.engine, v); + Value v = argc ? *argv : Value::undefinedValue(); + return ErrorObject::create<ReferenceErrorObject>(f->engine(), v, newTarget)->asReturnedValue(); } void Heap::SyntaxErrorCtor::init(QV4::ExecutionContext *scope) { - Heap::ErrorCtor::init(scope, QStringLiteral("SyntaxError")); + Heap::FunctionObject::init(scope, QStringLiteral("SyntaxError")); } -void SyntaxErrorCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue SyntaxErrorCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ScopedValue v(scope, callData->argument(0)); - scope.result = ErrorObject::create<SyntaxErrorObject>(scope.engine, v); + Value v = argc ? *argv : Value::undefinedValue(); + return ErrorObject::create<SyntaxErrorObject>(f->engine(), v, newTarget)->asReturnedValue(); } void Heap::TypeErrorCtor::init(QV4::ExecutionContext *scope) { - Heap::ErrorCtor::init(scope, QStringLiteral("TypeError")); + Heap::FunctionObject::init(scope, QStringLiteral("TypeError")); } -void TypeErrorCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue TypeErrorCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ScopedValue v(scope, callData->argument(0)); - scope.result = ErrorObject::create<TypeErrorObject>(scope.engine, v); + Value v = argc ? *argv : Value::undefinedValue(); + return ErrorObject::create<TypeErrorObject>(f->engine(), v, newTarget)->asReturnedValue(); } void Heap::URIErrorCtor::init(QV4::ExecutionContext *scope) { - Heap::ErrorCtor::init(scope, QStringLiteral("URIError")); + Heap::FunctionObject::init(scope, QStringLiteral("URIError")); } -void URIErrorCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue URIErrorCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ScopedValue v(scope, callData->argument(0)); - scope.result = ErrorObject::create<URIErrorObject>(scope.engine, v); + Value v = argc ? *argv : Value::undefinedValue(); + return ErrorObject::create<URIErrorObject>(f->engine(), v, newTarget)->asReturnedValue(); } void ErrorPrototype::init(ExecutionEngine *engine, Object *ctor, Object *obj, Heap::ErrorObject::ErrorType t) @@ -316,19 +312,21 @@ void ErrorPrototype::init(ExecutionEngine *engine, Object *ctor, Object *obj, He ScopedString s(scope); ScopedObject o(scope); ctor->defineReadonlyProperty(engine->id_prototype(), (o = obj)); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); obj->setProperty(Index_Constructor, ctor->d()); obj->setProperty(Index_Message, engine->id_empty()->d()); obj->setProperty(Index_Name, engine->newString(QString::fromLatin1(ErrorObject::className(t)))); obj->defineDefaultProperty(engine->id_toString(), method_toString, 0); } -void ErrorPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ErrorPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Object *o = callData->thisObject.as<Object>(); + ExecutionEngine *v4 = b->engine(); + const Object *o = thisObject->as<Object>(); if (!o) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); + Scope scope(v4); ScopedValue name(scope, o->get(scope.engine->id_name())); QString qname; if (name->isUndefined()) @@ -351,5 +349,5 @@ void ErrorPrototype::method_toString(const BuiltinFunction *, Scope &scope, Call str = qname + QLatin1String(": ") + qmessage; } - scope.result = scope.engine->newString(str)->asReturnedValue(); + return scope.engine->newString(str)->asReturnedValue(); } diff --git a/src/qml/jsruntime/qv4errorobject_p.h b/src/qml/jsruntime/qv4errorobject_p.h index d556617b48..139bcc9754 100644 --- a/src/qml/jsruntime/qv4errorobject_p.h +++ b/src/qml/jsruntime/qv4errorobject_p.h @@ -67,7 +67,7 @@ namespace Heap { Member(class, Pointer, String *, stack) DECLARE_HEAP_OBJECT(ErrorObject, Object) { - DECLARE_MARK_TABLE(ErrorObject); + DECLARE_MARKOBJECTS(ErrorObject); enum ErrorType { Error, EvalError, @@ -115,7 +115,7 @@ struct URIErrorObject : ErrorObject { void init(const Value &message); }; -struct ErrorCtor : Heap::FunctionObject { +struct ErrorCtor : FunctionObject { void init(QV4::ExecutionContext *scope); void init(QV4::ExecutionContext *scope, const QString &name); }; @@ -153,6 +153,7 @@ struct ErrorObject: Object { enum { Index_Stack = 0, // Accessor Property + Index_StackSetter = 1, // Accessor Property Index_FileName = 2, Index_LineNumber = 3, Index_Message = 4 @@ -165,7 +166,7 @@ struct ErrorObject: Object { V4_NEEDS_DESTROY template <typename T> - static Heap::Object *create(ExecutionEngine *e, const Value &message); + static Heap::Object *create(ExecutionEngine *e, const Value &message, const Value *newTarget); template <typename T> static Heap::Object *create(ExecutionEngine *e, const QString &message); template <typename T> @@ -175,12 +176,12 @@ struct ErrorObject: Object { static const char *className(Heap::ErrorObject::ErrorType t); - static void method_get_stack(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_get_stack(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; template<> inline const ErrorObject *Value::as() const { - return isManaged() && m()->vtable()->isErrorObject ? reinterpret_cast<const ErrorObject *>(this) : 0; + return isManaged() && m()->internalClass->vtable->isErrorObject ? reinterpret_cast<const ErrorObject *>(this) : nullptr; } struct EvalErrorObject: ErrorObject { @@ -229,54 +230,60 @@ struct ErrorCtor: FunctionObject { V4_OBJECT2(ErrorCtor, FunctionObject) - static void construct(const Managed *, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; struct EvalErrorCtor: ErrorCtor { - V4_OBJECT2(EvalErrorCtor, ErrorCtor) + V4_OBJECT2(EvalErrorCtor, FunctionObject) + V4_PROTOTYPE(errorCtor) - static void construct(const Managed *m, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); }; struct RangeErrorCtor: ErrorCtor { - V4_OBJECT2(RangeErrorCtor, ErrorCtor) + V4_OBJECT2(RangeErrorCtor, FunctionObject) + V4_PROTOTYPE(errorCtor) - static void construct(const Managed *, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); }; struct ReferenceErrorCtor: ErrorCtor { - V4_OBJECT2(ReferenceErrorCtor, ErrorCtor) + V4_OBJECT2(ReferenceErrorCtor, FunctionObject) + V4_PROTOTYPE(errorCtor) - static void construct(const Managed *m, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); }; struct SyntaxErrorCtor: ErrorCtor { - V4_OBJECT2(SyntaxErrorCtor, ErrorCtor) + V4_OBJECT2(SyntaxErrorCtor, FunctionObject) + V4_PROTOTYPE(errorCtor) - static void construct(const Managed *m, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); }; struct TypeErrorCtor: ErrorCtor { - V4_OBJECT2(TypeErrorCtor, ErrorCtor) + V4_OBJECT2(TypeErrorCtor, FunctionObject) + V4_PROTOTYPE(errorCtor) - static void construct(const Managed *m, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); }; struct URIErrorCtor: ErrorCtor { - V4_OBJECT2(URIErrorCtor, ErrorCtor) + V4_OBJECT2(URIErrorCtor, FunctionObject) + V4_PROTOTYPE(errorCtor) - static void construct(const Managed *m, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); }; -struct ErrorPrototype : ErrorObject +struct ErrorPrototype : Object { enum { Index_Constructor = 0, @@ -286,35 +293,35 @@ struct ErrorPrototype : ErrorObject void init(ExecutionEngine *engine, Object *ctor) { init(engine, ctor, this, Heap::ErrorObject::Error); } static void init(ExecutionEngine *engine, Object *ctor, Object *obj, Heap::ErrorObject::ErrorType t); - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; -struct EvalErrorPrototype : ErrorObject +struct EvalErrorPrototype : Object { void init(ExecutionEngine *engine, Object *ctor) { ErrorPrototype::init(engine, ctor, this, Heap::ErrorObject::EvalError); } }; -struct RangeErrorPrototype : ErrorObject +struct RangeErrorPrototype : Object { void init(ExecutionEngine *engine, Object *ctor) { ErrorPrototype::init(engine, ctor, this, Heap::ErrorObject::RangeError); } }; -struct ReferenceErrorPrototype : ErrorObject +struct ReferenceErrorPrototype : Object { void init(ExecutionEngine *engine, Object *ctor) { ErrorPrototype::init(engine, ctor, this, Heap::ErrorObject::ReferenceError); } }; -struct SyntaxErrorPrototype : ErrorObject +struct SyntaxErrorPrototype : Object { void init(ExecutionEngine *engine, Object *ctor) { ErrorPrototype::init(engine, ctor, this, Heap::ErrorObject::SyntaxError); } }; -struct TypeErrorPrototype : ErrorObject +struct TypeErrorPrototype : Object { void init(ExecutionEngine *engine, Object *ctor) { ErrorPrototype::init(engine, ctor, this, Heap::ErrorObject::TypeError); } }; -struct URIErrorPrototype : ErrorObject +struct URIErrorPrototype : Object { void init(ExecutionEngine *engine, Object *ctor) { ErrorPrototype::init(engine, ctor, this, Heap::ErrorObject::URIError); } }; @@ -322,31 +329,33 @@ struct URIErrorPrototype : ErrorObject inline SyntaxErrorObject *ErrorObject::asSyntaxError() { - return d()->errorType == QV4::Heap::ErrorObject::SyntaxError ? static_cast<SyntaxErrorObject *>(this) : 0; + return d()->errorType == QV4::Heap::ErrorObject::SyntaxError ? static_cast<SyntaxErrorObject *>(this) : nullptr; } template <typename T> -Heap::Object *ErrorObject::create(ExecutionEngine *e, const Value &message) { - InternalClass *ic = e->internalClasses[message.isUndefined() ? EngineBase::Class_ErrorObject : EngineBase::Class_ErrorObjectWithMessage]; - ic = ic->changePrototype(T::defaultPrototype(e)->d()); - return e->memoryManager->allocObject<T>(ic, T::defaultPrototype(e), message); +Heap::Object *ErrorObject::create(ExecutionEngine *e, const Value &message, const Value *newTarget) { + EngineBase::InternalClassType klass = message.isUndefined() ? EngineBase::Class_ErrorObject : EngineBase::Class_ErrorObjectWithMessage; + Scope scope(e); + ScopedObject proto(scope, static_cast<const Object *>(newTarget)->get(scope.engine->id_prototype())); + Scoped<InternalClass> ic(scope, e->internalClasses(klass)->changePrototype(proto->d())); + return e->memoryManager->allocObject<T>(ic->d(), message); } template <typename T> Heap::Object *ErrorObject::create(ExecutionEngine *e, const QString &message) { Scope scope(e); ScopedValue v(scope, message.isEmpty() ? Encode::undefined() : e->newString(message)->asReturnedValue()); - InternalClass *ic = e->internalClasses[v->isUndefined() ? EngineBase::Class_ErrorObject : EngineBase::Class_ErrorObjectWithMessage]; - ic = ic->changePrototype(T::defaultPrototype(e)->d()); - return e->memoryManager->allocObject<T>(ic, T::defaultPrototype(e), v); + EngineBase::InternalClassType klass = v->isUndefined() ? EngineBase::Class_ErrorObject : EngineBase::Class_ErrorObjectWithMessage; + Scoped<InternalClass> ic(scope, e->internalClasses(klass)->changePrototype(T::defaultPrototype(e)->d())); + return e->memoryManager->allocObject<T>(ic->d(), v); } template <typename T> Heap::Object *ErrorObject::create(ExecutionEngine *e, const QString &message, const QString &filename, int line, int column) { Scope scope(e); ScopedValue v(scope, message.isEmpty() ? Encode::undefined() : e->newString(message)->asReturnedValue()); - InternalClass *ic = e->internalClasses[v->isUndefined() ? EngineBase::Class_ErrorObject : EngineBase::Class_ErrorObjectWithMessage]; - ic = ic->changePrototype(T::defaultPrototype(e)->d()); - return e->memoryManager->allocObject<T>(ic, T::defaultPrototype(e), v, filename, line, column); + EngineBase::InternalClassType klass = v->isUndefined() ? EngineBase::Class_ErrorObject : EngineBase::Class_ErrorObjectWithMessage; + Scoped<InternalClass> ic(scope, e->internalClasses(klass)->changePrototype(T::defaultPrototype(e)->d())); + return e->memoryManager->allocObject<T>(ic->d(), v, filename, line, column); } diff --git a/src/qml/jsruntime/qv4estable.cpp b/src/qml/jsruntime/qv4estable.cpp new file mode 100644 index 0000000000..99f6bf6aa0 --- /dev/null +++ b/src/qml/jsruntime/qv4estable.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4estable_p.h" +#include "qv4object_p.h" + +using namespace QV4; + +// The ES spec requires that Map/Set be implemented using a data structure that +// is a little different from most; it requires nonlinear access, and must also +// preserve the order of insertion of items in a deterministic way. +// +// This class implements those requirements, except for fast access: that +// will be addressed in a followup patch. + +ESTable::ESTable() + : m_capacity(8) +{ + m_keys = (Value*)malloc(m_capacity * sizeof(Value)); + m_values = (Value*)malloc(m_capacity * sizeof(Value)); + memset(m_keys, 0, m_capacity); + memset(m_values, 0, m_capacity); +} + +ESTable::~ESTable() +{ + free(m_keys); + free(m_values); + m_size = 0; + m_capacity = 0; + m_keys = nullptr; + m_values = nullptr; +} + +void ESTable::markObjects(MarkStack *s, bool isWeakMap) +{ + for (uint i = 0; i < m_size; ++i) { + if (!isWeakMap) + m_keys[i].mark(s); + m_values[i].mark(s); + } +} + +// Pretends that there's nothing in the table. Doesn't actually free memory, as +// it will almost certainly be reused again anyway. +void ESTable::clear() +{ + m_size = 0; +} + +// Update the table to contain \a value for a given \a key. The key is +// normalized, as required by the ES spec. +void ESTable::set(const Value &key, const Value &value) +{ + for (uint i = 0; i < m_size; ++i) { + if (m_keys[i].sameValueZero(key)) { + m_values[i] = value; + return; + } + } + + if (m_capacity == m_size) { + uint oldCap = m_capacity; + m_capacity *= 2; + m_keys = (Value*)realloc(m_keys, m_capacity * sizeof(Value)); + m_values = (Value*)realloc(m_values, m_capacity * sizeof(Value)); + memset(m_keys + oldCap, 0, m_capacity - oldCap); + memset(m_values + oldCap, 0, m_capacity - oldCap); + } + + Value nk = key; + if (nk.isDouble()) { + if (nk.doubleValue() == 0 && std::signbit(nk.doubleValue())) + nk = Value::fromDouble(+0); + } + + m_keys[m_size] = nk; + m_values[m_size] = value; + + m_size++; +} + +// Returns true if the table contains \a key, false otherwise. +bool ESTable::has(const Value &key) const +{ + for (uint i = 0; i < m_size; ++i) { + if (m_keys[i].sameValueZero(key)) + return true; + } + + return false; +} + +// Fetches the value for the given \a key, and if \a hasValue is passed in, +// it is set depending on whether or not the given key was found. +ReturnedValue ESTable::get(const Value &key, bool *hasValue) const +{ + for (uint i = 0; i < m_size; ++i) { + if (m_keys[i].sameValueZero(key)) { + if (hasValue) + *hasValue = true; + return m_values[i].asReturnedValue(); + } + } + + if (hasValue) + *hasValue = false; + return Encode::undefined(); +} + +// Removes the given \a key from the table +bool ESTable::remove(const Value &key) +{ + bool found = false; + uint idx = 0; + for (; idx < m_size; ++idx) { + if (m_keys[idx].sameValueZero(key)) { + found = true; + break; + } + } + + if (found == true) { + memmove(m_keys + idx, m_keys + idx + 1, (m_size - idx)*sizeof(Value)); + memmove(m_values + idx, m_values + idx + 1, (m_size - idx)*sizeof(Value)); + m_size--; + } + return found; +} + +// Returns the size of the table. Note that the size may not match the underlying allocation. +uint ESTable::size() const +{ + return m_size; +} + +// Retrieves a key and value for a given \a idx, and places them in \a key and +// \a value. They must be valid pointers. +void ESTable::iterate(uint idx, Value *key, Value *value) +{ + Q_ASSERT(idx < m_size); + Q_ASSERT(key); + Q_ASSERT(value); + *key = m_keys[idx]; + *value = m_values[idx]; +} + +void ESTable::removeUnmarkedKeys() +{ + uint idx = 0; + uint toIdx = 0; + for (; idx < m_size; ++idx) { + Q_ASSERT(m_keys[idx].isObject()); + Object &o = static_cast<Object &>(m_keys[idx]); + if (o.d()->isMarked()) { + m_keys[toIdx] = m_keys[idx]; + m_values[toIdx] = m_values[idx]; + ++toIdx; + } + } + m_size = toIdx; +} + diff --git a/src/qml/jsruntime/qv4estable_p.h b/src/qml/jsruntime/qv4estable_p.h new file mode 100644 index 0000000000..f54fc37a7b --- /dev/null +++ b/src/qml/jsruntime/qv4estable_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QV4ESTABLE_P_H +#define QV4ESTABLE_P_H + +#include "qv4value_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 +{ + +class ESTable +{ +public: + ESTable(); + ~ESTable(); + + void markObjects(MarkStack *s, bool isWeakMap); + void clear(); + void set(const Value &k, const Value &v); + bool has(const Value &k) const; + ReturnedValue get(const Value &k, bool *hasValue = nullptr) const; + bool remove(const Value &k); + uint size() const; + void iterate(uint idx, Value *k, Value *v); + + void removeUnmarkedKeys(); + +private: + Value *m_keys = nullptr; + Value *m_values = nullptr; + uint m_size = 0; + uint m_capacity = 0; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/jsruntime/qv4executableallocator.cpp b/src/qml/jsruntime/qv4executableallocator.cpp index 64ac1267ce..c836d121e3 100644 --- a/src/qml/jsruntime/qv4executableallocator.cpp +++ b/src/qml/jsruntime/qv4executableallocator.cpp @@ -38,17 +38,23 @@ ****************************************************************************/ #include "qv4executableallocator_p.h" +#include "qv4functiontable_p.h" #include <wtf/StdLibExtras.h> #include <wtf/PageAllocation.h> using namespace QV4; -void *ExecutableAllocator::Allocation::start() const +void *ExecutableAllocator::Allocation::exceptionHandler() const { return reinterpret_cast<void*>(addr); } +void *ExecutableAllocator::Allocation::start() const +{ + return reinterpret_cast<void*>(addr + exceptionHandlerSize()); +} + void ExecutableAllocator::Allocation::deallocate(ExecutableAllocator *allocator) { if (isValid()) @@ -159,10 +165,10 @@ ExecutableAllocator::~ExecutableAllocator() ExecutableAllocator::Allocation *ExecutableAllocator::allocate(size_t size) { QMutexLocker locker(&mutex); - Allocation *allocation = 0; + Allocation *allocation = nullptr; // Code is best aligned to 16-byte boundaries. - size = WTF::roundUpToMultipleOf(16, size); + size = WTF::roundUpToMultipleOf(16, size + exceptionHandlerSize()); QMultiMap<size_t, Allocation*>::Iterator it = freeAllocations.lowerBound(size); if (it != freeAllocations.end()) { @@ -217,7 +223,7 @@ void ExecutableAllocator::free(Allocation *allocation) if (!merged) freeAllocations.insert(allocation->size, allocation); - allocation = 0; + allocation = nullptr; if (!chunk->firstAllocation->next) { freeAllocations.remove(chunk->firstAllocation->size, chunk->firstAllocation); @@ -235,7 +241,7 @@ ExecutableAllocator::ChunkOfPages *ExecutableAllocator::chunkForAllocation(Alloc if (it != chunks.begin()) --it; if (it == chunks.end()) - return 0; + return nullptr; return *it; } diff --git a/src/qml/jsruntime/qv4executableallocator_p.h b/src/qml/jsruntime/qv4executableallocator_p.h index 353a6eacff..013c6d7120 100644 --- a/src/qml/jsruntime/qv4executableallocator_p.h +++ b/src/qml/jsruntime/qv4executableallocator_p.h @@ -82,13 +82,11 @@ public: struct Allocation { Allocation() - : addr(0) - , size(0) + : size(0) , free(true) - , next(0) - , prev(0) {} + void *exceptionHandler() const; void *start() const; void invalidate() { addr = 0; } bool isValid() const { return addr != 0; } @@ -103,11 +101,11 @@ public: bool mergeNext(ExecutableAllocator *allocator); bool mergePrevious(ExecutableAllocator *allocator); - quintptr addr; + quintptr addr = 0; uint size : 31; // More than 2GB of function code? nah :) uint free : 1; - Allocation *next; - Allocation *prev; + Allocation *next = nullptr; + Allocation *prev = nullptr; }; // for debugging / unit-testing @@ -117,13 +115,12 @@ public: struct ChunkOfPages { ChunkOfPages() - : pages(0) - , firstAllocation(0) + {} ~ChunkOfPages(); - WTF::PageAllocation *pages; - Allocation *firstAllocation; + WTF::PageAllocation *pages = nullptr; + Allocation *firstAllocation = nullptr; bool contains(Allocation *alloc) const; }; diff --git a/src/qml/jsruntime/qv4function.cpp b/src/qml/jsruntime/qv4function.cpp index 4c8117527c..7702939e23 100644 --- a/src/qml/jsruntime/qv4function.cpp +++ b/src/qml/jsruntime/qv4function.cpp @@ -38,84 +38,173 @@ ****************************************************************************/ #include "qv4function_p.h" +#include "qv4functionobject_p.h" #include "qv4managed_p.h" #include "qv4string_p.h" #include "qv4value_p.h" #include "qv4engine_p.h" #include "qv4lookup_p.h" #include <private/qv4mm_p.h> +#include <private/qv4identifiertable_p.h> +#include <private/qv4functiontable_p.h> +#include <assembler/MacroAssemblerCodeRef.h> +#include <private/qv4vme_moth_p.h> +#include <private/qqmlglobal_p.h> QT_BEGIN_NAMESPACE using namespace QV4; -Function::Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function, - ReturnedValue (*codePtr)(ExecutionEngine *, const uchar *)) - : compiledFunction(function) - , compilationUnit(unit) - , code(codePtr) - , codeData(0) - , hasQmlDependencies(function->hasQmlDependencies()) +ReturnedValue Function::call(const Value *thisObject, const Value *argv, int argc, const ExecutionContext *context) { + ExecutionEngine *engine = context->engine(); + CppStackFrame frame; + frame.init(engine, this, argv, argc); + frame.setupJSFrame(engine->jsStackTop, Value::undefinedValue(), context->d(), + thisObject ? *thisObject : Value::undefinedValue(), + Value::undefinedValue()); + + frame.push(); + engine->jsStackTop += frame.requiredJSStackFrameSize(); + + ReturnedValue result = Moth::VME::exec(&frame, engine); + + frame.pop(); + + return result; +} + +Function *Function::create(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function) { - Q_UNUSED(engine); + quint16 traceSlotCount = 0; +#if QT_CONFIG(qml_tracing) + traceSlotCount = function->nTraceInfos == CompiledData::Function::NoTracing() + ? 1 + : function->nTraceInfos; +#endif + quint8 *storage = new quint8[sizeof(Function) + traceSlotCount]; + return new(storage) Function(engine, unit, function); +} + +void Function::destroy() +{ + delete[] reinterpret_cast<quint8 *>(this); +} - internalClass = engine->internalClasses[EngineBase::Class_Empty]; - const CompiledData::LEUInt32 *formalsIndices = compiledFunction->formalsTable(); - // iterate backwards, so we get the right ordering for duplicate names +Function::Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function) + : FunctionData(unit) + , compiledFunction(function) + , codeData(function->code()) + , jittedCode(nullptr) + , codeRef(nullptr) + , hasQmlDependencies(function->hasQmlDependencies()) +{ Scope scope(engine); - ScopedString arg(scope); - for (int i = static_cast<int>(compiledFunction->nFormals - 1); i >= 0; --i) { - arg = compilationUnit->runtimeStrings[formalsIndices[i]]; - while (1) { - InternalClass *newClass = internalClass->addMember(arg, Attr_NotConfigurable); - if (newClass != internalClass) { - internalClass = newClass; - break; - } - // duplicate arguments, need some trick to store them - MemoryManager *mm = engine->memoryManager; - arg = mm->alloc<String>(arg->d(), engine->newString(QString(0xfffe))); - } - } - nFormals = compiledFunction->nFormals; + Scoped<InternalClass> ic(scope, engine->internalClasses(EngineBase::Class_CallContext)); - const CompiledData::LEUInt32 *localsIndices = compiledFunction->localsTable(); + // first locals + const quint32_le *localsIndices = compiledFunction->localsTable(); for (quint32 i = 0; i < compiledFunction->nLocals; ++i) - internalClass = internalClass->addMember(compilationUnit->runtimeStrings[localsIndices[i]]->identifier, Attr_NotConfigurable); + ic = ic->addMember(engine->identifierTable->asPropertyKey(compilationUnit->runtimeStrings[localsIndices[i]]), Attr_NotConfigurable); + + const quint32_le *formalsIndices = compiledFunction->formalsTable(); + for (quint32 i = 0; i < compiledFunction->nFormals; ++i) + ic = ic->addMember(engine->identifierTable->asPropertyKey(compilationUnit->runtimeStrings[formalsIndices[i]]), Attr_NotConfigurable); + internalClass = ic->d(); - canUseSimpleCall = compiledFunction->flags & CompiledData::Function::CanUseSimpleCall; + nFormals = compiledFunction->nFormals; + +#if QT_CONFIG(qml_tracing) + if (tracingEnabled()) { + for (uint i = 0; i < function->nTraceInfos; ++i) + *traceInfo(i) = 0; + } +#endif } Function::~Function() { + if (codeRef) { + destroyFunctionTable(this, codeRef); + delete codeRef; + } } void Function::updateInternalClass(ExecutionEngine *engine, const QList<QByteArray> ¶meters) { - internalClass = engine->internalClasses[EngineBase::Class_Empty]; + QStringList parameterNames; - // iterate backwards, so we get the right ordering for duplicate names - Scope scope(engine); - ScopedString arg(scope); - for (int i = parameters.size() - 1; i >= 0; --i) { - arg = engine->newString(QString::fromUtf8(parameters.at(i))); - while (1) { - InternalClass *newClass = internalClass->addMember(arg, Attr_NotConfigurable); - if (newClass != internalClass) { - internalClass = newClass; + // Resolve duplicate parameter names: + for (int i = 0, ei = parameters.count(); i != ei; ++i) { + const QByteArray ¶m = parameters.at(i); + int duplicate = -1; + + for (int j = i - 1; j >= 0; --j) { + const QByteArray &prevParam = parameters.at(j); + if (param == prevParam) { + duplicate = j; break; } - // duplicate arguments, need some trick to store them - arg = engine->memoryManager->alloc<String>(arg->d(), engine->newString(QString(0xfffe))); } + + if (duplicate == -1) { + parameterNames.append(QString::fromUtf8(param)); + } else { + const QString &dup = parameterNames[duplicate]; + parameterNames.append(dup); + parameterNames[duplicate] = + QString(0xfffe) + QString::number(duplicate) + dup; + } + } - nFormals = parameters.size(); - const CompiledData::LEUInt32 *localsIndices = compiledFunction->localsTable(); + internalClass = engine->internalClasses(EngineBase::Class_CallContext); + + // first locals + const quint32_le *localsIndices = compiledFunction->localsTable(); for (quint32 i = 0; i < compiledFunction->nLocals; ++i) - internalClass = internalClass->addMember(compilationUnit->runtimeStrings[localsIndices[i]]->identifier, Attr_NotConfigurable); + internalClass = internalClass->addMember(engine->identifierTable->asPropertyKey(compilationUnit->runtimeStrings[localsIndices[i]]), Attr_NotConfigurable); - canUseSimpleCall = false; + Scope scope(engine); + ScopedString arg(scope); + for (const QString ¶meterName : parameterNames) { + arg = engine->newIdentifier(parameterName); + internalClass = internalClass->addMember(arg->propertyKey(), Attr_NotConfigurable); + } + nFormals = parameters.size(); +} + +QString Function::prettyName(const Function *function, const void *code) +{ + QString prettyName = function ? function->name()->toQString() : QString(); + if (prettyName.isEmpty()) { + prettyName = QString::number(reinterpret_cast<quintptr>(code), 16); + prettyName.prepend(QLatin1String("QV4::Function(0x")); + prettyName.append(QLatin1Char(')')); + } + return prettyName; +} + +QQmlSourceLocation Function::sourceLocation() const +{ + return QQmlSourceLocation(sourceFile(), compiledFunction->location.line, compiledFunction->location.column); +} + +QString Function::traceInfoToString() +{ + QString info = QLatin1String("=== Trace information for ") + name()->toQString() + QLatin1Char(':'); + if (!tracingEnabled()) + return info + QStringLiteral(" disabled. Interpreter call count: %1\n").arg(interpreterCallCount); + if (compiledFunction->nTraceInfos == 0) + return info + QLatin1String(" none.\n"); + + info += QLatin1Char('\n'); + for (uint i = 0, ei = compiledFunction->nTraceInfos; i < ei; ++i) { + auto bits = QString::number(*traceInfo(i), 2); + if (bits.size() < 8) + bits.prepend(QString(8 - bits.size(), '0')); + info += QStringLiteral(" %1: %2\n").arg(QString::number(i), bits); + } + return info; } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4function_p.h b/src/qml/jsruntime/qv4function_p.h index b11c8af94a..374e46b929 100644 --- a/src/qml/jsruntime/qv4function_p.h +++ b/src/qml/jsruntime/qv4function_p.h @@ -51,58 +51,105 @@ // #include "qv4global_p.h" -#include <private/qqmlglobal_p.h> #include <private/qv4compileddata_p.h> #include <private/qv4context_p.h> +namespace JSC { +class MacroAssemblerCodeRef; +} + QT_BEGIN_NAMESPACE +struct QQmlSourceLocation; + namespace QV4 { -struct Q_QML_EXPORT Function { - const CompiledData::Function *compiledFunction; +struct Q_QML_EXPORT FunctionData { CompiledData::CompilationUnit *compilationUnit; - ReturnedValue (*code)(ExecutionEngine *, const uchar *); - const uchar *codeData; + FunctionData(CompiledData::CompilationUnit *compilationUnit) + : compilationUnit(compilationUnit) + {} +}; +// Make sure this class can be accessed through offsetof (done by the assemblers): +Q_STATIC_ASSERT(std::is_standard_layout< FunctionData >::value); + +struct Q_QML_EXPORT Function : public FunctionData { +private: + Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function); + ~Function(); + +public: + const CompiledData::Function *compiledFunction; + + ReturnedValue call(const Value *thisObject, const Value *argv, int argc, const ExecutionContext *context); + + const char *codeData; + + typedef ReturnedValue (*JittedCode)(CppStackFrame *, ExecutionEngine *); + JittedCode jittedCode; + JSC::MacroAssemblerCodeRef *codeRef; // first nArguments names in internalClass are the actual arguments - InternalClass *internalClass; + Heap::InternalClass *internalClass; uint nFormals; + int interpreterCallCount = 0; bool hasQmlDependencies; - bool canUseSimpleCall; + bool isEval = false; - Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function, - ReturnedValue (*codePtr)(ExecutionEngine *, const uchar *)); - ~Function(); + static Function *create(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function); + void destroy(); // used when dynamically assigning signal handlers (QQmlConnection) void updateInternalClass(ExecutionEngine *engine, const QList<QByteArray> ¶meters); - inline Heap::String *name() { + inline Heap::String *name() const { return compilationUnit->runtimeStrings[compiledFunction->nameIndex]; } + + static QString prettyName(const Function *function, const void *address); + inline QString sourceFile() const { return compilationUnit->fileName(); } + inline QUrl finalUrl() const { return compilationUnit->finalUrl(); } - inline bool usesArgumentsObject() const { return compiledFunction->flags & CompiledData::Function::UsesArgumentsObject; } inline bool isStrict() const { return compiledFunction->flags & CompiledData::Function::IsStrict; } - inline bool isNamedExpression() const { return compiledFunction->flags & CompiledData::Function::IsNamedExpression; } + inline bool isArrowFunction() const { return compiledFunction->flags & CompiledData::Function::IsArrowFunction; } + inline bool isGenerator() const { return compiledFunction->flags & CompiledData::Function::IsGenerator; } - inline bool canUseSimpleFunction() const { return canUseSimpleCall; } + QQmlSourceLocation sourceLocation() const; - QQmlSourceLocation sourceLocation() const + Function *nestedFunction() const { - return QQmlSourceLocation(sourceFile(), compiledFunction->location.line, compiledFunction->location.column); + if (compiledFunction->nestedFunctionIndex == std::numeric_limits<uint32_t>::max()) + return nullptr; + return compilationUnit->runtimeFunctions[compiledFunction->nestedFunctionIndex]; } -}; + Q_NEVER_INLINE QString traceInfoToString(); + quint8 *traceInfo(uint i) + { +#if QT_CONFIG(qml_tracing) + Q_ASSERT((tracingEnabled() && i < traceInfoCount()) || (i == 0)); + return reinterpret_cast<quint8 *>(this) + sizeof(Function) + i; +#else + Q_UNUSED(i); + return nullptr; +#endif + } -inline unsigned int Heap::SimpleCallContext::formalParameterCount() const -{ - return v4Function ? v4Function->nFormals : 0; -} + quint32 traceInfoCount() const + { return compiledFunction->nTraceInfos; } + bool tracingEnabled() const + { +#if QT_CONFIG(qml_tracing) + return traceInfoCount() != CompiledData::Function::NoTracing(); +#else + return false; +#endif + } +}; } diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index 45fdde98f7..41a21ba379 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -38,11 +38,10 @@ ****************************************************************************/ #include "qv4object_p.h" -#include "qv4jsir_p.h" -#include "qv4isel_p.h" #include "qv4objectproto_p.h" #include "qv4stringobject_p.h" #include "qv4function_p.h" +#include "qv4symbol_p.h" #include <private/qv4mm_p.h> #include "qv4arrayobject_p.h" @@ -55,9 +54,11 @@ #include <private/qqmljsast_p.h> #include <private/qqmljavascriptexpression_p.h> #include <private/qqmlengine_p.h> -#include <qv4codegen_p.h> +#include <qv4runtimecodegen_p.h> #include "private/qlocale_tools_p.h" #include "private/qqmlbuiltinfunctions_p.h" +#include <private/qv4jscall_p.h> +#include <private/qv4vme_moth_p.h> #include <QtCore/QDebug> #include <algorithm> @@ -69,47 +70,74 @@ using namespace QV4; DEFINE_OBJECT_VTABLE(FunctionObject); -Q_STATIC_ASSERT((Heap::FunctionObject::markTable & Heap::Object::markTable) == Heap::Object::markTable); - -void Heap::FunctionObject::init(QV4::ExecutionContext *scope, QV4::String *name, bool createProto) +void Heap::FunctionObject::init(QV4::ExecutionContext *scope, QV4::String *name, VTable::Call call) { + jsCall = call; + jsConstruct = nullptr; + Object::init(); - function = nullptr; this->scope.set(scope->engine(), scope->d()); Scope s(scope->engine()); ScopedFunctionObject f(s, this); - f->init(name, createProto); + if (name) + f->setName(name); +} + +void Heap::FunctionObject::init(QV4::ExecutionContext *scope, QV4::String *name) +{ + ExecutionEngine *e = scope->engine(); + + jsCall = vtable()->call; + jsConstruct = vtable()->callAsConstructor; + + Object::init(); + this->scope.set(scope->engine(), scope->d()); + Scope s(e); + ScopedFunctionObject f(s, this); + if (name) + f->setName(name); } -void Heap::FunctionObject::init(QV4::ExecutionContext *scope, Function *function, bool createProto) + + +void Heap::FunctionObject::init(QV4::ExecutionContext *scope, Function *function, QV4::String *n) { + jsCall = vtable()->call; + jsConstruct = vtable()->callAsConstructor; + Object::init(); - this->function = function; - function->compilationUnit->addref(); + setFunction(function); this->scope.set(scope->engine(), scope->d()); Scope s(scope->engine()); - ScopedString name(s, function->name()); + ScopedString name(s, n ? n->d() : function->name()); ScopedFunctionObject f(s, this); - f->init(name, createProto); + if (name) + f->setName(name); } -void Heap::FunctionObject::init(QV4::ExecutionContext *scope, const QString &name, bool createProto) +void Heap::FunctionObject::init(QV4::ExecutionContext *scope, const QString &name) { Scope valueScope(scope); ScopedString s(valueScope, valueScope.engine->newString(name)); - init(scope, s, createProto); + init(scope, s); } void Heap::FunctionObject::init() { + jsCall = vtable()->call; + jsConstruct = vtable()->callAsConstructor; + Object::init(); - function = nullptr; this->scope.set(internalClass->engine, internalClass->engine->rootContext()->d()); - Q_ASSERT(internalClass && internalClass->find(internalClass->engine->id_prototype()) == Index_Prototype); - setProperty(internalClass->engine, Index_Prototype, Primitive::undefinedValue()); } - +void Heap::FunctionObject::setFunction(Function *f) +{ + if (f) { + function = f; + function->compilationUnit->addref(); + } +} void Heap::FunctionObject::destroy() { if (function) @@ -117,23 +145,15 @@ void Heap::FunctionObject::destroy() Object::destroy(); } -void FunctionObject::init(String *n, bool createProto) +void FunctionObject::createDefaultPrototypeProperty(uint protoConstructorSlot) { - Scope s(internalClass()->engine); - ScopedValue protectThis(s, this); + Scope s(this); - Q_ASSERT(internalClass() && internalClass()->find(s.engine->id_prototype()) == Heap::FunctionObject::Index_Prototype); - if (createProto) { - ScopedObject proto(s, s.engine->newObject(s.engine->internalClasses[EngineBase::Class_ObjectProto], s.engine->objectPrototype())); - Q_ASSERT(s.engine->internalClasses[EngineBase::Class_ObjectProto]->find(s.engine->id_constructor()) == Heap::FunctionObject::Index_ProtoConstructor); - proto->setProperty(Heap::FunctionObject::Index_ProtoConstructor, d()); - setProperty(Heap::FunctionObject::Index_Prototype, proto); - } else { - setProperty(Heap::FunctionObject::Index_Prototype, Primitive::undefinedValue()); - } + Q_ASSERT(s.engine->internalClasses(EngineBase::Class_ObjectProto)->verifyIndex(s.engine->id_constructor()->propertyKey(), protoConstructorSlot)); - if (n) - defineReadonlyConfigurableProperty(s.engine->id_name(), *n); + ScopedObject proto(s, s.engine->newObject(s.engine->internalClasses(EngineBase::Class_ObjectProto))); + proto->setProperty(protoConstructorSlot, d()); + defineDefaultProperty(s.engine->id_prototype(), proto, Attr_NotEnumerable|Attr_NotConfigurable); } ReturnedValue FunctionObject::name() const @@ -141,29 +161,59 @@ ReturnedValue FunctionObject::name() const return get(scope()->internalClass->engine->id_name()); } -void FunctionObject::construct(const Managed *that, Scope &scope, CallData *) +ReturnedValue FunctionObject::virtualCall(const FunctionObject *, const Value *, const Value *, int) { - scope.result = static_cast<const FunctionObject *>(that)->engine()->throwTypeError(); + return Encode::undefined(); } -void FunctionObject::call(const Managed *, Scope &scope, CallData *) +Heap::FunctionObject *FunctionObject::createScriptFunction(ExecutionContext *scope, Function *function) { - scope.result = Encode::undefined(); + if (function->isArrowFunction()) + return scope->engine()->memoryManager->allocate<ArrowFunction>(scope, function); + return scope->engine()->memoryManager->allocate<ScriptFunction>(scope, function); } -Heap::FunctionObject *FunctionObject::createScriptFunction(ExecutionContext *scope, Function *function) +Heap::FunctionObject *FunctionObject::createConstructorFunction(ExecutionContext *scope, Function *function, Object *homeObject, bool isDerivedConstructor) { - return scope->engine()->memoryManager->allocObject<ScriptFunction>(scope, function); + if (!function) { + Heap::DefaultClassConstructorFunction *c = scope->engine()->memoryManager->allocate<DefaultClassConstructorFunction>(scope); + c->isDerivedConstructor = isDerivedConstructor; + return c; + } + Heap::ConstructorFunction *c = scope->engine()->memoryManager->allocate<ConstructorFunction>(scope, function); + c->homeObject.set(scope->engine(), homeObject->d()); + c->isDerivedConstructor = isDerivedConstructor; + return c; } -bool FunctionObject::isBinding() const +Heap::FunctionObject *FunctionObject::createMemberFunction(ExecutionContext *scope, Function *function, Object *homeObject, QV4::String *name) { - return d()->vtable() == QQmlBindingFunction::staticVTable(); + Heap::MemberFunction *m = scope->engine()->memoryManager->allocate<MemberFunction>(scope, function, name); + m->homeObject.set(scope->engine(), homeObject->d()); + return m; } -bool FunctionObject::isBoundFunction() const +Heap::FunctionObject *FunctionObject::createBuiltinFunction(ExecutionEngine *engine, StringOrSymbol *nameOrSymbol, VTable::Call code, int argumentCount) { - return d()->vtable() == BoundFunction::staticVTable(); + Scope scope(engine); + ScopedString name(scope, nameOrSymbol); + if (!name) + name = engine->newString(QChar::fromLatin1('[') + nameOrSymbol->toQString().midRef(1) + QChar::fromLatin1(']')); + + ScopedFunctionObject function(scope, engine->memoryManager->allocate<FunctionObject>(engine->rootContext(), name, code)); + function->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(argumentCount)); + return function->d(); +} + +ReturnedValue FunctionObject::getHomeObject() const +{ + const MemberFunction *m = as<MemberFunction>(); + if (m) + return m->d()->homeObject->asReturnedValue(); + const ConstructorFunction *c = as<ConstructorFunction>(); + if (c) + return c->d()->homeObject->asReturnedValue(); + return Encode::undefined(); } QQmlSourceLocation FunctionObject::sourceLocation() const @@ -179,64 +229,77 @@ void Heap::FunctionCtor::init(QV4::ExecutionContext *scope) } // 15.3.2 -void FunctionCtor::construct(const Managed *that, Scope &scope, CallData *callData) +QQmlRefPointer<CompiledData::CompilationUnit> FunctionCtor::parse(ExecutionEngine *engine, const Value *argv, int argc, Type t) { - Scoped<FunctionCtor> f(scope, static_cast<const FunctionCtor *>(that)); - QString arguments; QString body; - if (callData->argc > 0) { - for (int i = 0; i < callData->argc - 1; ++i) { + if (argc > 0) { + for (int i = 0, ei = argc - 1; i < ei; ++i) { if (i) arguments += QLatin1String(", "); - arguments += callData->args[i].toQString(); + arguments += argv[i].toQString(); } - body = callData->args[callData->argc - 1].toQString(); - } - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; + body = argv[argc - 1].toQString(); } + if (engine->hasException) + return nullptr; - QString function = QLatin1String("function(") + arguments + QLatin1String("){") + body + QLatin1Char('}'); + QString function = (t == Type_Function ? QLatin1String("function anonymous(") : QLatin1String("function* anonymous(")) + arguments + QLatin1String("\n){") + body + QLatin1String("\n}"); - QQmlJS::Engine ee, *engine = ⅇ - QQmlJS::Lexer lexer(engine); + QQmlJS::Engine ee; + QQmlJS::Lexer lexer(&ee); lexer.setCode(function, 1, false); - QQmlJS::Parser parser(engine); + QQmlJS::Parser parser(&ee); const bool parsed = parser.parseExpression(); if (!parsed) { - scope.result = scope.engine->throwSyntaxError(QLatin1String("Parse error")); - return; + engine->throwSyntaxError(QLatin1String("Parse error")); + return nullptr; } - using namespace QQmlJS::AST; - FunctionExpression *fe = QQmlJS::AST::cast<FunctionExpression *>(parser.rootNode()); + QQmlJS::AST::FunctionExpression *fe = QQmlJS::AST::cast<QQmlJS::AST::FunctionExpression *>(parser.rootNode()); if (!fe) { - scope.result = scope.engine->throwSyntaxError(QLatin1String("Parse error")); - return; + engine->throwSyntaxError(QLatin1String("Parse error")); + return nullptr; } - IR::Module module(scope.engine->debugger() != 0); + Compiler::Module module(engine->debugger() != nullptr); - QQmlJS::RuntimeCodegen cg(scope.engine, f->strictMode()); + Compiler::JSUnitGenerator jsGenerator(&module); + RuntimeCodegen cg(engine, &jsGenerator, false); cg.generateFromFunctionExpression(QString(), function, fe, &module); - Compiler::JSUnitGenerator jsGenerator(&module); - QScopedPointer<EvalInstructionSelection> isel(scope.engine->iselFactory->create(QQmlEnginePrivate::get(scope.engine), scope.engine->executableAllocator, &module, &jsGenerator)); - QQmlRefPointer<CompiledData::CompilationUnit> compilationUnit = isel->compile(); - Function *vmf = compilationUnit->linkToEngine(scope.engine); + if (engine->hasException) + return nullptr; - ExecutionContext *global = scope.engine->rootContext(); - scope.result = FunctionObject::createScriptFunction(global, vmf); + return cg.generateCompilationUnit(); +} + +ReturnedValue FunctionCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + ExecutionEngine *engine = f->engine(); + + QQmlRefPointer<CompiledData::CompilationUnit> compilationUnit = parse(engine, argv, argc, Type_Function); + if (engine->hasException) + return Encode::undefined(); + + Function *vmf = compilationUnit->linkToEngine(engine); + ExecutionContext *global = engine->scriptContext(); + ReturnedValue o = Encode(FunctionObject::createScriptFunction(global, vmf)); + + if (!newTarget) + return o; + Scope scope(engine); + ScopedObject obj(scope, o); + obj->setProtoFromNewTarget(newTarget); + return obj->asReturnedValue(); } // 15.3.1: This is equivalent to new Function(...) -void FunctionCtor::call(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue FunctionCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc) { - construct(that, scope, callData); + return virtualCallAsConstructor(f, argv, argc, f); } DEFINE_OBJECT_VTABLE(FunctionPrototype); @@ -251,253 +314,361 @@ void FunctionPrototype::init(ExecutionEngine *engine, Object *ctor) Scope scope(engine); ScopedObject o(scope); - ctor->defineReadonlyConfigurableProperty(engine->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); - defineReadonlyConfigurableProperty(engine->id_length(), Primitive::fromInt32(0)); + defineReadonlyConfigurableProperty(engine->id_name(), *engine->id_empty()); + defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0)); defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); defineDefaultProperty(engine->id_toString(), method_toString, 0); defineDefaultProperty(QStringLiteral("apply"), method_apply, 2); defineDefaultProperty(QStringLiteral("call"), method_call, 1); defineDefaultProperty(QStringLiteral("bind"), method_bind, 1); - + defineDefaultProperty(engine->symbol_hasInstance(), method_hasInstance, 1, Attr_ReadOnly); } -void FunctionPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue FunctionPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - FunctionObject *fun = callData->thisObject.as<FunctionObject>(); + ExecutionEngine *v4 = b->engine(); + const FunctionObject *fun = thisObject->as<FunctionObject>(); if (!fun) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); - scope.result = scope.engine->newString(QStringLiteral("function() { [code] }")); -} + const Scope scope(fun->engine()); + const ScopedString scopedFunctionName(scope, fun->name()); + const QString functionName(scopedFunctionName ? scopedFunctionName->toQString() : QString()); + QString functionAsString = QStringLiteral("function"); -void FunctionPrototype::method_apply(const BuiltinFunction *, Scope &scope, CallData *callData) -{ - FunctionObject *o = callData->thisObject.as<FunctionObject>(); - if (!o) - THROW_TYPE_ERROR(); + // If fun->name() is empty, then there is no function name + // to append because the function is anonymous. + if (!functionName.isEmpty()) + functionAsString.append(QLatin1Char(' ') + functionName); - ScopedValue arg(scope, callData->argument(1)); + functionAsString.append(QStringLiteral("() { [native code] }")); - ScopedObject arr(scope, arg); + return Encode(v4->newString(functionAsString)); +} - quint32 len; - if (!arr) { - len = 0; - if (!arg->isNullOrUndefined()) - THROW_TYPE_ERROR(); - } else { - len = arr->getLength(); - } +ReturnedValue FunctionPrototype::method_apply(const QV4::FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *v4 = b->engine(); + const FunctionObject *f = thisObject->as<FunctionObject>(); + if (!f) + return v4->throwTypeError(); + thisObject = argc ? argv : nullptr; + if (argc < 2 || argv[1].isNullOrUndefined()) + return f->call(thisObject, argv, 0); + + Object *arr = argv[1].objectValue(); + if (!arr) + return v4->throwTypeError(); - ScopedCallData cData(scope, len); + uint len = arr->getLength(); + Scope scope(v4); + Value *arguments = scope.alloc<Scope::Uninitialized>(len); if (len) { if (ArgumentsObject::isNonStrictArgumentsObject(arr) && !arr->cast<ArgumentsObject>()->fullyCreated()) { QV4::ArgumentsObject *a = arr->cast<ArgumentsObject>(); - int l = qMin(len, (uint)a->d()->context->callData->argc); - memcpy(cData->args, a->d()->context->callData->args, l*sizeof(Value)); + int l = qMin(len, (uint)a->d()->context->argc()); + memcpy(arguments, a->d()->context->args(), l*sizeof(Value)); for (quint32 i = l; i < len; ++i) - cData->args[i] = Primitive::undefinedValue(); + arguments[i] = Value::undefinedValue(); } else if (arr->arrayType() == Heap::ArrayData::Simple && !arr->protoHasArray()) { auto sad = static_cast<Heap::SimpleArrayData *>(arr->arrayData()); uint alen = sad ? sad->values.size : 0; if (alen > len) alen = len; for (uint i = 0; i < alen; ++i) - cData->args[i] = sad->data(i); + arguments[i] = sad->data(i); for (quint32 i = alen; i < len; ++i) - cData->args[i] = Primitive::undefinedValue(); + arguments[i] = Value::undefinedValue(); } else { + // need to init the arguments array, as the get() calls below can have side effects + memset(arguments, 0, len*sizeof(Value)); for (quint32 i = 0; i < len; ++i) - cData->args[i] = arr->getIndexed(i); + arguments[i] = arr->get(i); } } - cData->thisObject = callData->argument(0); - o->call(scope, cData); + return f->call(thisObject, arguments, len); } -void FunctionPrototype::method_call(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue FunctionPrototype::method_call(const QV4::FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - FunctionObject *o = callData->thisObject.as<FunctionObject>(); - if (!o) - THROW_TYPE_ERROR(); + if (!thisObject->isFunctionObject()) + return b->engine()->throwTypeError(); - ScopedCallData cData(scope, callData->argc ? callData->argc - 1 : 0); - if (callData->argc) { - for (int i = 1; i < callData->argc; ++i) - cData->args[i - 1] = callData->args[i]; + const FunctionObject *f = static_cast<const FunctionObject *>(thisObject); + + thisObject = argc ? argv : nullptr; + if (argc) { + ++argv; + --argc; + } + return f->call(thisObject, argv, argc); +} + +ReturnedValue FunctionPrototype::method_bind(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + QV4::Scope scope(b); + ScopedFunctionObject target(scope, thisObject); + if (!target || target->isBinding()) + return scope.engine->throwTypeError(); + + ScopedValue boundThis(scope, argc ? argv[0] : Value::undefinedValue()); + Scoped<MemberData> boundArgs(scope, (Heap::MemberData *)nullptr); + + int nArgs = (argc - 1 >= 0) ? argc - 1 : 0; + if (target->isBoundFunction()) { + BoundFunction *bound = static_cast<BoundFunction *>(target.getPointer()); + Scoped<MemberData> oldArgs(scope, bound->boundArgs()); + boundThis = bound->boundThis(); + int oldSize = !oldArgs ? 0 : oldArgs->size(); + if (oldSize + nArgs) { + boundArgs = MemberData::allocate(scope.engine, oldSize + nArgs); + boundArgs->d()->values.size = oldSize + nArgs; + for (uint i = 0; i < static_cast<uint>(oldSize); ++i) + boundArgs->set(scope.engine, i, oldArgs->data()[i]); + for (uint i = 0; i < static_cast<uint>(nArgs); ++i) + boundArgs->set(scope.engine, oldSize + i, argv[i + 1]); + } + target = bound->target(); + } else if (nArgs) { + boundArgs = MemberData::allocate(scope.engine, nArgs); + boundArgs->d()->values.size = nArgs; + for (uint i = 0, ei = static_cast<uint>(nArgs); i < ei; ++i) + boundArgs->set(scope.engine, i, argv[i + 1]); } - cData->thisObject = callData->argument(0); - o->call(scope, cData); + ScopedContext ctx(scope, target->scope()); + Heap::BoundFunction *bound = BoundFunction::create(ctx, target, boundThis, boundArgs); + bound->setFunction(target->function()); + return bound->asReturnedValue(); } -void FunctionPrototype::method_bind(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue FunctionPrototype::method_hasInstance(const FunctionObject *, const Value *thisObject, const Value *argv, int argc) { - FunctionObject *target = callData->thisObject.as<FunctionObject>(); - if (!target) - THROW_TYPE_ERROR(); - - ScopedValue boundThis(scope, callData->argument(0)); - Scoped<MemberData> boundArgs(scope, (Heap::MemberData *)0); - if (callData->argc > 1) { - boundArgs = MemberData::allocate(scope.engine, callData->argc - 1); - boundArgs->d()->values.size = callData->argc - 1; - for (uint i = 0; i < static_cast<uint>(callData->argc - 1); ++i) - boundArgs->set(scope.engine, i, callData->args[i + 1]); - } + if (!argc) + return Encode(false); + const Object *o = thisObject->as<Object>(); + if (!o) + return Encode(false); - ExecutionContext *global = scope.engine->rootContext(); - scope.result = BoundFunction::create(global, target, boundThis, boundArgs); + return Object::virtualInstanceOf(o, argv[0]); } DEFINE_OBJECT_VTABLE(ScriptFunction); -void ScriptFunction::construct(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue ScriptFunction::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *newTarget) { - ExecutionEngine *v4 = scope.engine; - if (Q_UNLIKELY(v4->hasException)) { - scope.result = Encode::undefined(); - return; - } - CHECK_STACK_LIMITS(v4, scope); + ExecutionEngine *v4 = fo->engine(); + const ScriptFunction *f = static_cast<const ScriptFunction *>(fo); + Q_ASSERT(newTarget->isFunctionObject()); + const FunctionObject *nt = static_cast<const FunctionObject *>(newTarget); - ExecutionContextSaver ctxSaver(scope); + Scope scope(v4); + Scoped<InternalClass> ic(scope); + if (nt->d() == f->d()) { + ic = f->classForConstructor(); + } else { + ScopedObject o(scope, nt->protoProperty()); + ic = scope.engine->internalClasses(EngineBase::Class_Object); + if (o) + ic = ic->changePrototype(o->d()); + } + ScopedValue thisObject(scope, v4->memoryManager->allocObject<Object>(ic)); - Scoped<ScriptFunction> f(scope, static_cast<const ScriptFunction *>(that)); + CppStackFrame frame; + frame.init(v4, f->function(), argv, argc); + frame.setupJSFrame(v4->jsStackTop, *f, f->scope(), + thisObject, + newTarget ? *newTarget : Value::undefinedValue()); - InternalClass *ic = f->classForConstructor(); - ScopedObject proto(scope, ic->prototype); - ScopedObject obj(scope, v4->newObject(ic, proto)); - callData->thisObject = obj.asReturnedValue(); + frame.push(); + v4->jsStackTop += frame.requiredJSStackFrameSize(); - QV4::Function *v4Function = f->function(); - Q_ASSERT(v4Function); + ReturnedValue result = Moth::VME::exec(&frame, v4); - ScopedContext c(scope, f->scope()); - if (v4Function->canUseSimpleCall) - c->simpleCall(scope, callData, v4Function); - else - c->call(scope, callData, v4Function, f); + frame.pop(); - if (Q_UNLIKELY(v4->hasException)) { - scope.result = Encode::undefined(); - } else if (!scope.result.isObject()) { - scope.result = obj.asReturnedValue(); - } + if (Q_UNLIKELY(v4->hasException)) + return Encode::undefined(); + else if (!Value::fromReturnedValue(result).isObject()) + return thisObject->asReturnedValue(); + return result; } -void ScriptFunction::call(const Managed *that, Scope &scope, CallData *callData) +DEFINE_OBJECT_VTABLE(ArrowFunction); + +ReturnedValue ArrowFunction::virtualCall(const FunctionObject *fo, const Value *thisObject, const Value *argv, int argc) { - ExecutionEngine *v4 = scope.engine; - if (Q_UNLIKELY(v4->hasException)) { - scope.result = Encode::undefined(); - return; - } - CHECK_STACK_LIMITS(v4, scope); + ExecutionEngine *engine = fo->engine(); + CppStackFrame frame; + frame.init(engine, fo->function(), argv, argc, true); + frame.setupJSFrame(engine->jsStackTop, *fo, fo->scope(), + thisObject ? *thisObject : Value::undefinedValue(), + Value::undefinedValue()); - Scoped<ScriptFunction> f(scope, static_cast<const ScriptFunction *>(that)); + frame.push(); + engine->jsStackTop += frame.requiredJSStackFrameSize(); - QV4::Function *v4Function = f->function(); - Q_ASSERT(v4Function); + ReturnedValue result; - ScopedContext c(scope, f->scope()); - if (v4Function->canUseSimpleCall) - c->simpleCall(scope, callData, v4Function); - else - c->call(scope, callData, v4Function, f); + do { + frame.pendingTailCall = false; + result = Moth::VME::exec(&frame, engine); + frame.isTailCalling = true; + } while (frame.pendingTailCall); + + frame.pop(); + + return result; } -void Heap::ScriptFunction::init(QV4::ExecutionContext *scope, Function *function) +void Heap::ArrowFunction::init(QV4::ExecutionContext *scope, Function *function, QV4::String *n) { FunctionObject::init(); this->scope.set(scope->engine(), scope->d()); - this->function = function; - function->compilationUnit->addref(); + setFunction(function); Q_ASSERT(function); - Q_ASSERT(function->code); Scope s(scope); ScopedFunctionObject f(s, this); - ScopedString name(s, function->name()); - f->init(name, true); - Q_ASSERT(internalClass && internalClass->find(s.engine->id_length()) == Index_Length); - setProperty(s.engine, Index_Length, Primitive::fromInt32(f->formalParameterCount())); - - if (scope->d()->strictMode) { - ScopedProperty pd(s); - pd->value = s.engine->thrower(); - pd->set = s.engine->thrower(); - f->insertMember(s.engine->id_caller(), pd, Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable); - f->insertMember(s.engine->id_arguments(), pd, Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable); - } + ScopedString name(s, n ? n->d() : function->name()); + if (name) + f->setName(name); + + Q_ASSERT(internalClass && internalClass->verifyIndex(s.engine->id_length()->propertyKey(), Index_Length)); + setProperty(s.engine, Index_Length, Value::fromInt32(int(function->compiledFunction->length))); + canBeTailCalled = true; } -InternalClass *ScriptFunction::classForConstructor() const +void Heap::ScriptFunction::init(QV4::ExecutionContext *scope, Function *function) { - const Object *o = d()->protoProperty(); - InternalClass *ic = d()->cachedClassForConstructor; - if (ic && ic->prototype == o->d()) - return ic; + ArrowFunction::init(scope, function); + Q_ASSERT(!function->isArrowFunction()); - ic = engine()->internalClasses[EngineBase::Class_Object]; - if (o) - ic = ic->changePrototype(o->d()); - d()->cachedClassForConstructor = ic; - - return ic; + Scope s(scope); + ScopedFunctionObject f(s, this); + f->createDefaultPrototypeProperty(Heap::FunctionObject::Index_ProtoConstructor); } -DEFINE_OBJECT_VTABLE(BuiltinFunction); - -void Heap::BuiltinFunction::init(QV4::ExecutionContext *scope, QV4::String *name, void (*code)(const QV4::BuiltinFunction *, Scope &, CallData *)) +Heap::InternalClass *ScriptFunction::classForConstructor() const { - Heap::FunctionObject::init(scope, name); - this->code = code; + Scope scope(engine()); + ScopedValue o(scope, protoProperty()); + if (d()->cachedClassForConstructor && d()->cachedClassForConstructor->prototype == o->heapObject()) + return d()->cachedClassForConstructor; + + Scoped<InternalClass> ic(scope, engine()->internalClasses(EngineBase::Class_Object)); + ScopedObject p(scope, o); + if (p) + ic = ic->changePrototype(p->d()); + d()->cachedClassForConstructor.set(scope.engine, ic->d()); + + return ic->d(); } -void BuiltinFunction::construct(const Managed *f, Scope &scope, CallData *) +DEFINE_OBJECT_VTABLE(ConstructorFunction); + +ReturnedValue ConstructorFunction::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - scope.result = static_cast<const BuiltinFunction *>(f)->internalClass()->engine->throwTypeError(); + const ConstructorFunction *c = static_cast<const ConstructorFunction *>(f); + if (!c->d()->isDerivedConstructor) + return ScriptFunction::virtualCallAsConstructor(f, argv, argc, newTarget); + + ExecutionEngine *v4 = f->engine(); + + CppStackFrame frame; + frame.init(v4, f->function(), argv, argc); + frame.setupJSFrame(v4->jsStackTop, *f, f->scope(), + Value::emptyValue(), + newTarget ? *newTarget : Value::undefinedValue()); + + frame.push(); + v4->jsStackTop += frame.requiredJSStackFrameSize(); + + ReturnedValue result = Moth::VME::exec(&frame, v4); + ReturnedValue thisObject = frame.jsFrame->thisObject.asReturnedValue(); + + frame.pop(); + + if (Q_UNLIKELY(v4->hasException)) + return Encode::undefined(); + else if (Value::fromReturnedValue(result).isObject()) + return result; + else if (!Value::fromReturnedValue(result).isUndefined()) + return v4->throwTypeError(); + else if (Value::fromReturnedValue(thisObject).isEmpty()) { + Scope scope(v4); + ScopedString s(scope, v4->newString(QStringLiteral("this"))); + return v4->throwReferenceError(s); + } + return thisObject; } -void BuiltinFunction::call(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue ConstructorFunction::virtualCall(const FunctionObject *f, const Value *, const Value *, int) { - const BuiltinFunction *f = static_cast<const BuiltinFunction *>(that); - ExecutionEngine *v4 = scope.engine; - if (v4->hasException) { - scope.result = Encode::undefined(); - return; - } - f->d()->code(f, scope, callData); + return f->engine()->throwTypeError(QStringLiteral("Cannot call a class constructor without |new|")); } +DEFINE_OBJECT_VTABLE(MemberFunction); -void IndexedBuiltinFunction::call(const Managed *that, Scope &scope, CallData *callData) +DEFINE_OBJECT_VTABLE(DefaultClassConstructorFunction); + +ReturnedValue DefaultClassConstructorFunction::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - const IndexedBuiltinFunction *f = static_cast<const IndexedBuiltinFunction *>(that); - ExecutionEngine *v4 = scope.engine; - if (v4->hasException) { - scope.result = Encode::undefined(); - return; + const DefaultClassConstructorFunction *c = static_cast<const DefaultClassConstructorFunction *>(f); + ExecutionEngine *v4 = f->engine(); + + Scope scope(v4); + + if (!c->d()->isDerivedConstructor) { + ScopedObject proto(scope, static_cast<const Object *>(newTarget)->get(scope.engine->id_prototype())); + ScopedObject c(scope, scope.engine->newObject()); + c->setPrototypeUnchecked(proto); + return c->asReturnedValue(); } - CHECK_STACK_LIMITS(v4, scope); - ExecutionContextSaver ctxSaver(scope); + ScopedFunctionObject super(scope, f->getPrototypeOf()); + Q_ASSERT(super->isFunctionObject()); + + CppStackFrame frame; + frame.init(v4, nullptr, argv, argc); + frame.setupJSFrame(v4->jsStackTop, *f, f->scope(), + Value::undefinedValue(), + newTarget ? *newTarget : Value::undefinedValue(), argc, argc); + + frame.push(); + v4->jsStackTop += frame.requiredJSStackFrameSize(argc); + + // Do a super call + ReturnedValue result = super->callAsConstructor(argv, argc, newTarget); + ReturnedValue thisObject = frame.jsFrame->thisObject.asReturnedValue(); + + frame.pop(); + + if (Q_UNLIKELY(v4->hasException)) + return Encode::undefined(); + else if (Value::fromReturnedValue(result).isObject()) + return result; + else if (!Value::fromReturnedValue(result).isUndefined()) + return v4->throwTypeError(); + else if (Value::fromReturnedValue(thisObject).isEmpty()) { + Scope scope(v4); + ScopedString s(scope, v4->newString(QStringLiteral("this"))); + return v4->throwReferenceError(s); + } - SimpleCallContext::Data *ctx = v4->memoryManager->allocSimpleCallContext(); - ctx->strictMode = f->scope()->strictMode; // ### needed? scope or parent context? - ctx->callData = callData; - v4->pushContext(ctx); - Q_ASSERT(v4->current == ctx); + return thisObject; +} - scope.result = f->d()->code(static_cast<QV4::CallContext *>(v4->currentContext), f->d()->index); - v4->memoryManager->freeSimpleCallContext(); +ReturnedValue DefaultClassConstructorFunction::virtualCall(const FunctionObject *f, const Value *, const Value *, int) +{ + return f->engine()->throwTypeError(QStringLiteral("Cannot call a class constructor without |new|")); } DEFINE_OBJECT_VTABLE(IndexedBuiltinFunction); @@ -510,9 +681,12 @@ void Heap::BoundFunction::init(QV4::ExecutionContext *scope, QV4::FunctionObject Scope s(scope); Heap::FunctionObject::init(scope, QStringLiteral("__bound function__")); this->target.set(s.engine, target->d()); - this->boundArgs.set(s.engine, boundArgs ? boundArgs->d() : 0); + this->boundArgs.set(s.engine, boundArgs ? boundArgs->d() : nullptr); this->boundThis.set(scope->engine(), boundThis); + if (!target->isConstructor()) + jsConstruct = nullptr; + ScopedObject f(s, this); ScopedValue l(s, target->get(s.engine->id_length())); @@ -521,7 +695,7 @@ void Heap::BoundFunction::init(QV4::ExecutionContext *scope, QV4::FunctionObject len -= boundArgs->size(); if (len < 0) len = 0; - f->defineReadonlyConfigurableProperty(s.engine->id_length(), Primitive::fromInt32(len)); + f->defineReadonlyConfigurableProperty(s.engine->id_length(), Value::fromInt32(len)); ScopedProperty pd(s); pd->value = s.engine->thrower(); @@ -530,43 +704,43 @@ void Heap::BoundFunction::init(QV4::ExecutionContext *scope, QV4::FunctionObject f->insertMember(s.engine->id_caller(), pd, Attr_Accessor|Attr_NotConfigurable|Attr_NotEnumerable); } -void BoundFunction::call(const Managed *that, Scope &scope, CallData *dd) +ReturnedValue BoundFunction::virtualCall(const FunctionObject *fo, const Value *, const Value *argv, int argc) { - const BoundFunction *f = static_cast<const BoundFunction *>(that); - if (scope.hasException()) { - scope.result = Encode::undefined(); - return; - } + const BoundFunction *f = static_cast<const BoundFunction *>(fo); + Scope scope(f->engine()); + + if (scope.hasException()) + return Encode::undefined(); Scoped<MemberData> boundArgs(scope, f->boundArgs()); - ScopedCallData callData(scope, (boundArgs ? boundArgs->size() : 0) + dd->argc); - callData->thisObject = f->boundThis(); - Value *argp = callData->args; + ScopedFunctionObject target(scope, f->target()); + JSCallData jsCallData(scope, (boundArgs ? boundArgs->size() : 0) + argc); + *jsCallData->thisObject = f->boundThis(); + Value *argp = jsCallData->args; if (boundArgs) { memcpy(argp, boundArgs->data(), boundArgs->size()*sizeof(Value)); argp += boundArgs->size(); } - memcpy(argp, dd->args, dd->argc*sizeof(Value)); - ScopedFunctionObject t(scope, f->target()); - t->call(scope, callData); + memcpy(argp, argv, argc*sizeof(Value)); + return target->call(jsCallData); } -void BoundFunction::construct(const Managed *that, Scope &scope, CallData *dd) +ReturnedValue BoundFunction::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *) { - const BoundFunction *f = static_cast<const BoundFunction *>(that); - if (scope.hasException()) { - scope.result = Encode::undefined(); - return; - } + const BoundFunction *f = static_cast<const BoundFunction *>(fo); + Scope scope(f->engine()); + + if (scope.hasException()) + return Encode::undefined(); Scoped<MemberData> boundArgs(scope, f->boundArgs()); - ScopedCallData callData(scope, (boundArgs ? boundArgs->size() : 0) + dd->argc); - Value *argp = callData->args; + ScopedFunctionObject target(scope, f->target()); + JSCallData jsCallData(scope, (boundArgs ? boundArgs->size() : 0) + argc); + Value *argp = jsCallData->args; if (boundArgs) { memcpy(argp, boundArgs->data(), boundArgs->size()*sizeof(Value)); argp += boundArgs->size(); } - memcpy(argp, dd->args, dd->argc*sizeof(Value)); - ScopedFunctionObject t(scope, f->target()); - t->construct(scope, callData); + memcpy(argp, argv, argc*sizeof(Value)); + return target->callAsConstructor(jsCallData); } diff --git a/src/qml/jsruntime/qv4functionobject_p.h b/src/qml/jsruntime/qv4functionobject_p.h index 6ce5734b6d..e03d49c74d 100644 --- a/src/qml/jsruntime/qv4functionobject_p.h +++ b/src/qml/jsruntime/qv4functionobject_p.h @@ -61,31 +61,42 @@ struct QQmlSourceLocation; namespace QV4 { -struct BuiltinFunction; +struct IndexedBuiltinFunction; +struct JSCallData; namespace Heap { + #define FunctionObjectMembers(class, Member) \ Member(class, Pointer, ExecutionContext *, scope) \ - Member(class, NoMark, Function *, function) + Member(class, NoMark, Function *, function) \ + Member(class, NoMark, VTable::Call, jsCall) \ + Member(class, NoMark, VTable::CallAsConstructor, jsConstruct) \ + Member(class, NoMark, bool, canBeTailCalled) DECLARE_HEAP_OBJECT(FunctionObject, Object) { - DECLARE_MARK_TABLE(FunctionObject); + DECLARE_MARKOBJECTS(FunctionObject); enum { + Index_ProtoConstructor = 0, Index_Prototype = 0, - Index_ProtoConstructor = 0 + Index_HasInstance = 1, }; - void init(QV4::ExecutionContext *scope, QV4::String *name = 0, bool createProto = false); - void init(QV4::ExecutionContext *scope, QV4::Function *function, bool createProto = false); - void init(QV4::ExecutionContext *scope, const QString &name, bool createProto = false); + bool isConstructor() const { + return jsConstruct != nullptr; + } + + Q_QML_PRIVATE_EXPORT void init(QV4::ExecutionContext *scope, QV4::String *name, VTable::Call call); + void init(QV4::ExecutionContext *scope, QV4::String *name = nullptr); + void init(QV4::ExecutionContext *scope, QV4::Function *function, QV4::String *n = nullptr); + void init(QV4::ExecutionContext *scope, const QString &name); void init(); void destroy(); + void setFunction(Function *f); + unsigned int formalParameterCount() { return function ? function->nFormals : 0; } unsigned int varCount() { return function ? function->compiledFunction->nLocals : 0; } - - const QV4::Object *protoProperty() const { return propertyData(Index_Prototype)->as<QV4::Object>(); } }; struct FunctionCtor : FunctionObject { @@ -96,30 +107,49 @@ struct FunctionPrototype : FunctionObject { void init(); }; -struct Q_QML_EXPORT OldBuiltinFunction : FunctionObject { - void init(QV4::ExecutionContext *scope, QV4::String *name, ReturnedValue (*code)(QV4::CallContext *)); - ReturnedValue (*code)(QV4::CallContext *); -}; - -struct Q_QML_EXPORT BuiltinFunction : FunctionObject { - void init(QV4::ExecutionContext *scope, QV4::String *name, void (*code)(const QV4::BuiltinFunction *, Scope &, CallData *)); - void (*code)(const QV4::BuiltinFunction *, Scope &, CallData *); -}; - struct IndexedBuiltinFunction : FunctionObject { - inline void init(QV4::ExecutionContext *scope, uint index, ReturnedValue (*code)(QV4::CallContext *ctx, uint index)); - ReturnedValue (*code)(QV4::CallContext *, uint index); + inline void init(QV4::ExecutionContext *scope, uint index, VTable::Call call); uint index; }; -struct ScriptFunction : FunctionObject { +struct ArrowFunction : FunctionObject { enum { - Index_Name = FunctionObject::Index_Prototype + 1, + Index_Name = Index_HasInstance + 1, Index_Length }; + void init(QV4::ExecutionContext *scope, Function *function, QV4::String *name = nullptr); +}; + +#define ScriptFunctionMembers(class, Member) \ + Member(class, Pointer, InternalClass *, cachedClassForConstructor) + +DECLARE_HEAP_OBJECT(ScriptFunction, ArrowFunction) { + DECLARE_MARKOBJECTS(ScriptFunction) void init(QV4::ExecutionContext *scope, Function *function); +}; - QV4::InternalClass *cachedClassForConstructor; +#define MemberFunctionMembers(class, Member) \ + Member(class, Pointer, Object *, homeObject) + +DECLARE_HEAP_OBJECT(MemberFunction, ArrowFunction) { + DECLARE_MARKOBJECTS(MemberFunction) + + void init(QV4::ExecutionContext *scope, Function *function, QV4::String *name = nullptr) { + ArrowFunction::init(scope, function, name); + } +}; + +#define ConstructorFunctionMembers(class, Member) \ + Member(class, Pointer, Object *, homeObject) + +DECLARE_HEAP_OBJECT(ConstructorFunction, ScriptFunction) { + DECLARE_MARKOBJECTS(ConstructorFunction) + bool isDerivedConstructor; +}; + +struct DefaultClassConstructorFunction : FunctionObject +{ + bool isDerivedConstructor; }; #define BoundFunctionMembers(class, Member) \ @@ -128,7 +158,7 @@ struct ScriptFunction : FunctionObject { Member(class, Pointer, MemberData *, boundArgs) DECLARE_HEAP_OBJECT(BoundFunction, FunctionObject) { - DECLARE_MARK_TABLE(BoundFunction); + DECLARE_MARKOBJECTS(BoundFunction); void init(QV4::ExecutionContext *scope, QV4::FunctionObject *target, const Value &boundThis, QV4::MemberData *boundArgs); }; @@ -144,7 +174,9 @@ struct Q_QML_EXPORT FunctionObject: Object { V4_INTERNALCLASS(FunctionObject) V4_PROTOTYPE(functionPrototype) V4_NEEDS_DESTROY + enum { NInlineProperties = 1 }; + bool canBeTailCalled() const { return d()->canBeTailCalled; } Heap::ExecutionContext *scope() const { return d()->scope; } Function *function() const { return d()->function; } @@ -152,25 +184,52 @@ struct Q_QML_EXPORT FunctionObject: Object { unsigned int formalParameterCount() const { return d()->formalParameterCount(); } unsigned int varCount() const { return d()->varCount(); } - void init(String *name, bool createProto); + void setName(String *name) { + defineReadonlyConfigurableProperty(engine()->id_name(), *name); + } + void createDefaultPrototypeProperty(uint protoConstructorSlot); - using Object::construct; - using Object::call; - static void construct(const Managed *that, Scope &scope, CallData *); - static void call(const Managed *that, Scope &scope, CallData *d); + inline ReturnedValue callAsConstructor(const JSCallData &data) const; + ReturnedValue callAsConstructor(const Value *argv, int argc, const Value *newTarget = nullptr) const { + if (!d()->jsConstruct) + return engine()->throwTypeError(QStringLiteral("Function is not a constructor.")); + return d()->jsConstruct(this, argv, argc, newTarget ? newTarget : this); + } + inline ReturnedValue call(const JSCallData &data) const; + ReturnedValue call(const Value *thisObject, const Value *argv, int argc) const { + if (!d()->jsCall) + return engine()->throwTypeError(QStringLiteral("Function can only be called with |new|.")); + return d()->jsCall(this, thisObject, argv, argc); + } + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); static Heap::FunctionObject *createScriptFunction(ExecutionContext *scope, Function *function); + static Heap::FunctionObject *createConstructorFunction(ExecutionContext *scope, Function *function, Object *homeObject, bool isDerivedConstructor); + static Heap::FunctionObject *createMemberFunction(ExecutionContext *scope, Function *function, Object *homeObject, String *name); + static Heap::FunctionObject *createBuiltinFunction(ExecutionEngine *engine, StringOrSymbol *nameOrSymbol, VTable::Call code, int argumentCount); bool strictMode() const { return d()->function ? d()->function->isStrict() : false; } bool isBinding() const; bool isBoundFunction() const; + bool isConstructor() const { + return d()->isConstructor(); + } + + ReturnedValue getHomeObject() const; + + ReturnedValue protoProperty() const { + return getValueByIndex(Heap::FunctionObject::Index_Prototype); + } + bool hasHasInstanceProperty() const { + return !internalClass()->propertyData.at(Heap::FunctionObject::Index_HasInstance).isEmpty(); + } QQmlSourceLocation sourceLocation() const; }; template<> inline const FunctionObject *Value::as() const { - return isManaged() && m()->vtable()->isFunctionObject ? reinterpret_cast<const FunctionObject *>(this) : 0; + return isManaged() && m()->internalClass->vtable->isFunctionObject ? reinterpret_cast<const FunctionObject *>(this) : nullptr; } @@ -178,8 +237,14 @@ struct FunctionCtor: FunctionObject { V4_OBJECT2(FunctionCtor, FunctionObject) - static void construct(const Managed *that, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +protected: + enum Type { + Type_Function, + Type_Generator + }; + static QQmlRefPointer<CompiledData::CompilationUnit> parse(ExecutionEngine *engine, const Value *argv, int argc, Type t = Type_Function); }; struct FunctionPrototype: FunctionObject @@ -188,73 +253,82 @@ struct FunctionPrototype: FunctionObject void init(ExecutionEngine *engine, Object *ctor); - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_apply(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_call(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_bind(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_apply(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_call(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_bind(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_hasInstance(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; -struct Q_QML_EXPORT BuiltinFunction : FunctionObject { - V4_OBJECT2(BuiltinFunction, FunctionObject) - V4_INTERNALCLASS(BuiltinFunction) - - static Heap::BuiltinFunction *create(ExecutionContext *scope, String *name, void (*code)(const BuiltinFunction *, Scope &, CallData *)) - { - return scope->engine()->memoryManager->allocObject<BuiltinFunction>(scope, name, code); - } - - static void construct(const Managed *, Scope &scope, CallData *); - static void call(const Managed *that, Scope &scope, CallData *callData); -}; - -struct IndexedBuiltinFunction: FunctionObject +struct IndexedBuiltinFunction : FunctionObject { V4_OBJECT2(IndexedBuiltinFunction, FunctionObject) - - static void construct(const Managed *m, Scope &scope, CallData *) - { - scope.result = static_cast<const IndexedBuiltinFunction *>(m)->engine()->throwTypeError(); - } - - static void call(const Managed *that, Scope &scope, CallData *callData); }; -void Heap::IndexedBuiltinFunction::init(QV4::ExecutionContext *scope, uint index, - ReturnedValue (*code)(QV4::CallContext *ctx, uint index)) +void Heap::IndexedBuiltinFunction::init(QV4::ExecutionContext *scope, uint index, VTable::Call call) { Heap::FunctionObject::init(scope); + this->jsCall = call; this->index = index; - this->code = code; } +struct ArrowFunction : FunctionObject { + V4_OBJECT2(ArrowFunction, FunctionObject) + V4_INTERNALCLASS(ArrowFunction) + enum { NInlineProperties = 3 }; + + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; -struct ScriptFunction : FunctionObject { - V4_OBJECT2(ScriptFunction, FunctionObject) +struct ScriptFunction : ArrowFunction { + V4_OBJECT2(ScriptFunction, ArrowFunction) V4_INTERNALCLASS(ScriptFunction) - static void construct(const Managed *, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *, const Value *argv, int argc, const Value *); - InternalClass *classForConstructor() const; + Heap::InternalClass *classForConstructor() const; }; +struct MemberFunction : ArrowFunction { + V4_OBJECT2(MemberFunction, ArrowFunction) + V4_INTERNALCLASS(MemberFunction) +}; + +struct ConstructorFunction : ScriptFunction { + V4_OBJECT2(ConstructorFunction, ScriptFunction) + V4_INTERNALCLASS(ConstructorFunction) + static ReturnedValue virtualCallAsConstructor(const FunctionObject *, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct DefaultClassConstructorFunction : FunctionObject { + V4_OBJECT2(DefaultClassConstructorFunction, FunctionObject) + static ReturnedValue virtualCallAsConstructor(const FunctionObject *, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; struct BoundFunction: FunctionObject { V4_OBJECT2(BoundFunction, FunctionObject) static Heap::BoundFunction *create(ExecutionContext *scope, FunctionObject *target, const Value &boundThis, QV4::MemberData *boundArgs) { - return scope->engine()->memoryManager->allocObject<BoundFunction>(scope, target, boundThis, boundArgs); + return scope->engine()->memoryManager->allocate<BoundFunction>(scope, target, boundThis, boundArgs); } Heap::FunctionObject *target() const { return d()->target; } Value boundThis() const { return d()->boundThis; } Heap::MemberData *boundArgs() const { return d()->boundArgs; } - static void construct(const Managed *, Scope &scope, CallData *d); - static void call(const Managed *that, Scope &scope, CallData *dd); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; +inline bool FunctionObject::isBoundFunction() const +{ + return d()->vtable() == BoundFunction::staticVTable(); +} + + } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4functiontable_noop.cpp b/src/qml/jsruntime/qv4functiontable_noop.cpp new file mode 100644 index 0000000000..31c198eb00 --- /dev/null +++ b/src/qml/jsruntime/qv4functiontable_noop.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4functiontable_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +void generateFunctionTable(Function *function, JSC::MacroAssemblerCodeRef *codeRef) +{ + Q_UNUSED(function); + Q_UNUSED(codeRef); +} + +void destroyFunctionTable(Function *function, JSC::MacroAssemblerCodeRef *codeRef) +{ + Q_UNUSED(function); + Q_UNUSED(codeRef); +} + +size_t exceptionHandlerSize() +{ + return 0; +} + +} // QV4 + +QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4functiontable_p.h b/src/qml/jsruntime/qv4functiontable_p.h new file mode 100644 index 0000000000..69e3d2bdd5 --- /dev/null +++ b/src/qml/jsruntime/qv4functiontable_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4FUNCTIONTABLE_P_H +#define QV4FUNCTIONTABLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4global_p.h" + +namespace JSC { +class MacroAssemblerCodeRef; +} + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +struct Function; + +void generateFunctionTable(Function *function, JSC::MacroAssemblerCodeRef *codeRef); +void destroyFunctionTable(Function *function, JSC::MacroAssemblerCodeRef *codeRef); + +size_t exceptionHandlerSize(); + +} + +QT_END_NAMESPACE + +#endif // QV4FUNCTIONTABLE_P_H diff --git a/src/qml/jsruntime/qv4functiontable_unix.cpp b/src/qml/jsruntime/qv4functiontable_unix.cpp new file mode 100644 index 0000000000..25b5c27161 --- /dev/null +++ b/src/qml/jsruntime/qv4functiontable_unix.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4functiontable_p.h" +#include "qv4function_p.h" + +#include <assembler/MacroAssemblerCodeRef.h> + +#include <QtCore/qfile.h> +#include <QtCore/qcoreapplication.h> + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +void generateFunctionTable(Function *function, JSC::MacroAssemblerCodeRef *codeRef) +{ + // This implements writing of JIT'd addresses so that perf can find the + // symbol names. + // + // Perf expects the mapping to be in a certain place and have certain + // content, for more information, see: + // https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/jit-interface.txt + static bool doProfile = !qEnvironmentVariableIsEmpty("QV4_PROFILE_WRITE_PERF_MAP"); + if (Q_UNLIKELY(doProfile)) { + static QFile perfMapFile(QString::fromLatin1("/tmp/perf-%1.map") + .arg(QCoreApplication::applicationPid())); + static const bool isOpen = perfMapFile.open(QIODevice::WriteOnly); + if (!isOpen) { + qWarning("QV4::JIT::Assembler: Cannot write perf map file."); + doProfile = false; + } else { + const void *address = codeRef->code().executableAddress(); + perfMapFile.write(QByteArray::number(reinterpret_cast<quintptr>(address), 16)); + perfMapFile.putChar(' '); + perfMapFile.write(QByteArray::number(static_cast<qsizetype>(codeRef->size()), 16)); + perfMapFile.putChar(' '); + perfMapFile.write(Function::prettyName(function, address).toUtf8()); + perfMapFile.putChar('\n'); + perfMapFile.flush(); + } + } +} + +void destroyFunctionTable(Function *function, JSC::MacroAssemblerCodeRef *codeRef) +{ + Q_UNUSED(function); + Q_UNUSED(codeRef); + + // It's not advisable to remove things from the perf map file, as it's primarily used to analyze + // a trace after the application has terminated. We want to know about all functions that were + // ever jitted then. If the memory ranges overlap, we will have a problem when analyzing the + // trace. The JIT should try to avoid this. +} + +size_t exceptionHandlerSize() +{ + return 0; +} + +} // QV4 + +QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4functiontable_win64.cpp b/src/qml/jsruntime/qv4functiontable_win64.cpp new file mode 100644 index 0000000000..fc13dc2602 --- /dev/null +++ b/src/qml/jsruntime/qv4functiontable_win64.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4functiontable_p.h" + +#include <assembler/MacroAssemblerCodeRef.h> + +#include <QtCore/qdebug.h> + +#include <windows.h> + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +enum UnwindOpcode: UINT8 +{ + UWOP_PUSH_NONVOL = 0, /* info == register number */ + UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */ + UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */ + UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */ + UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */ + UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */ + UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */ + UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */ + UWOP_PUSH_MACHFRAME /* info == 0: no error-code, 1: error-code */ +}; + +enum Register : UINT8 +{ + RAX = 0, + RCX, + RDX, + RBX, + RSP, + RBP, + RSI, + RDI, + NONE = 15 +}; + +struct UnwindCode +{ + UnwindCode(UINT8 offset, UnwindOpcode operation, Register info) + : offset(offset), operation(operation), info(info) + {} + + UINT8 offset; + UINT8 operation: 4; + UINT8 info: 4; +}; + +struct UnwindInfo +{ + UINT8 Version : 3; + UINT8 Flags : 5; + UINT8 SizeOfProlog; + UINT8 CountOfUnwindCodes; + UINT8 FrameRegister : 4; + UINT8 FrameRegisterOffset : 4; + UnwindCode UnwindCodes[2]; +}; + +struct ExceptionHandlerRecord +{ + RUNTIME_FUNCTION handler; + UnwindInfo info; +}; + +void generateFunctionTable(Function *, JSC::MacroAssemblerCodeRef *codeRef) +{ + ExceptionHandlerRecord *record = reinterpret_cast<ExceptionHandlerRecord *>( + codeRef->executableMemory()->exceptionHandler()); + + record->info.Version = 1; + record->info.Flags = 0; + record->info.SizeOfProlog = 4; + record->info.CountOfUnwindCodes = 2; + record->info.FrameRegister = RBP; + record->info.FrameRegisterOffset = 0; + + // Push frame pointer + record->info.UnwindCodes[1] = UnwindCode(1, UWOP_PUSH_NONVOL, RBP); + // Set frame pointer from stack pointer + record->info.UnwindCodes[0] = UnwindCode(4, UWOP_SET_FPREG, NONE); + + const quintptr codeStart = quintptr(codeRef->code().executableAddress()); + const quintptr codeSize = codeRef->size(); + + record->handler.BeginAddress = DWORD(codeStart - quintptr(record)); + record->handler.EndAddress = DWORD(codeStart + codeSize - quintptr(record)); + record->handler.UnwindData = offsetof(ExceptionHandlerRecord, info); + + if (!RtlAddFunctionTable(&record->handler, 1, DWORD64(record))) { + const unsigned int errorCode = GetLastError(); + qWarning() << "Failed to install win64 unwind hook. Error code:" << errorCode; + } +} + +void destroyFunctionTable(Function *, JSC::MacroAssemblerCodeRef *codeRef) +{ + ExceptionHandlerRecord *record = reinterpret_cast<ExceptionHandlerRecord *>( + codeRef->executableMemory()->exceptionHandler()); + if (!RtlDeleteFunctionTable(&record->handler)) { + const unsigned int errorCode = GetLastError(); + qWarning() << "Failed to remove win64 unwind hook. Error code:" << errorCode; + } +} + +size_t exceptionHandlerSize() +{ + return sizeof(ExceptionHandlerRecord); +} + +} // QV4 + +QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4generatorobject.cpp b/src/qml/jsruntime/qv4generatorobject.cpp new file mode 100644 index 0000000000..566db6fd4e --- /dev/null +++ b/src/qml/jsruntime/qv4generatorobject.cpp @@ -0,0 +1,258 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qv4generatorobject_p.h> +#include <qv4symbol_p.h> +#include <qv4iterator_p.h> +#include <qv4jscall_p.h> +#include <qv4vme_moth_p.h> + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(GeneratorFunctionCtor); +DEFINE_OBJECT_VTABLE(GeneratorFunction); +DEFINE_OBJECT_VTABLE(GeneratorObject); + +void Heap::GeneratorFunctionCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("GeneratorFunction")); +} + +ReturnedValue GeneratorFunctionCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + ExecutionEngine *engine = f->engine(); + + QQmlRefPointer<CompiledData::CompilationUnit> compilationUnit = parse(engine, argv, argc, Type_Generator); + if (engine->hasException) + return Encode::undefined(); + + Function *vmf = compilationUnit->linkToEngine(engine); + ExecutionContext *global = engine->scriptContext(); + ReturnedValue o = Encode(GeneratorFunction::create(global, vmf)); + + if (!newTarget) + return o; + Scope scope(engine); + ScopedObject obj(scope, o); + obj->setProtoFromNewTarget(newTarget); + return obj->asReturnedValue(); +} + +// 15.3.1: This is equivalent to new Function(...) +ReturnedValue GeneratorFunctionCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return virtualCallAsConstructor(f, argv, argc, f); +} + +Heap::FunctionObject *GeneratorFunction::create(ExecutionContext *context, Function *function) +{ + Scope scope(context); + Scoped<GeneratorFunction> g(scope, context->engine()->memoryManager->allocate<GeneratorFunction>(context, function)); + ScopedObject proto(scope, scope.engine->newObject()); + proto->setPrototypeOf(scope.engine->generatorPrototype()); + g->defineDefaultProperty(scope.engine->id_prototype(), proto, Attr_NotConfigurable|Attr_NotEnumerable); + g->setPrototypeOf(ScopedObject(scope, scope.engine->generatorFunctionCtor()->get(scope.engine->id_prototype()))); + return g->d(); +} + +ReturnedValue GeneratorFunction::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + const GeneratorFunction *gf = static_cast<const GeneratorFunction *>(f); + Function *function = gf->function(); + ExecutionEngine *engine = gf->engine(); + + // We need to set up a separate stack for the generator, as it's being re-entered + uint stackSize = argc // space for the original arguments + + CppStackFrame::requiredJSStackFrameSize(function); // space for the JS stack frame + + size_t requiredMemory = sizeof(GeneratorObject::Data) - sizeof(Value) + sizeof(Value) * stackSize; + + Scope scope(gf); + Scoped<GeneratorObject> g(scope, scope.engine->memoryManager->allocManaged<GeneratorObject>(requiredMemory, scope.engine->classes[EngineBase::Class_GeneratorObject])); + g->setPrototypeOf(ScopedObject(scope, gf->get(scope.engine->id_prototype()))); + + Heap::GeneratorObject *gp = g->d(); + gp->stack.size = stackSize; + gp->stack.alloc = stackSize; + + // copy original arguments + memcpy(gp->stack.values, argv, argc*sizeof(Value)); + gp->cppFrame.init(engine, function, gp->stack.values, argc); + gp->cppFrame.setupJSFrame(&gp->stack.values[argc], *gf, gf->scope(), + thisObject ? *thisObject : Value::undefinedValue(), + Value::undefinedValue()); + + gp->cppFrame.push(); + + Moth::VME::interpret(&gp->cppFrame, engine, function->codeData); + gp->state = GeneratorState::SuspendedStart; + + gp->cppFrame.pop(); + return g->asReturnedValue(); +} + + +void Heap::GeneratorPrototype::init() +{ + Heap::FunctionObject::init(); +} + + +void GeneratorPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedValue v(scope); + + ScopedObject ctorProto(scope, engine->newObject(engine->newInternalClass(Object::staticVTable(), engine->functionPrototype()))); + + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); + ctor->defineReadonlyProperty(engine->id_prototype(), ctorProto); + + ctorProto->defineDefaultProperty(QStringLiteral("constructor"), (v = ctor), Attr_ReadOnly_ButConfigurable); + ctorProto->defineDefaultProperty(engine->symbol_toStringTag(), (v = engine->newIdentifier(QStringLiteral("GeneratorFunction"))), Attr_ReadOnly_ButConfigurable); + ctorProto->defineDefaultProperty(engine->id_prototype(), (v = this), Attr_ReadOnly_ButConfigurable); + + setPrototypeOf(engine->iteratorPrototype()); + defineDefaultProperty(QStringLiteral("constructor"), ctorProto, Attr_ReadOnly_ButConfigurable); + defineDefaultProperty(QStringLiteral("next"), method_next, 1); + defineDefaultProperty(QStringLiteral("return"), method_return, 1); + defineDefaultProperty(QStringLiteral("throw"), method_throw, 1); + defineDefaultProperty(engine->symbol_toStringTag(), (v = engine->newString(QStringLiteral("Generator"))), Attr_ReadOnly_ButConfigurable); +} + +ReturnedValue GeneratorPrototype::method_next(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *engine = f->engine(); + const GeneratorObject *g = thisObject->as<GeneratorObject>(); + if (!g || g->d()->state == GeneratorState::Executing) + return engine->throwTypeError(); + Heap::GeneratorObject *gp = g->d(); + + if (gp->state == GeneratorState::Completed) + return IteratorPrototype::createIterResultObject(engine, Value::undefinedValue(), true); + + return g->resume(engine, argc ? argv[0] : Value::undefinedValue()); +} + +ReturnedValue GeneratorPrototype::method_return(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *engine = f->engine(); + const GeneratorObject *g = thisObject->as<GeneratorObject>(); + if (!g || g->d()->state == GeneratorState::Executing) + return engine->throwTypeError(); + + Heap::GeneratorObject *gp = g->d(); + + if (gp->state == GeneratorState::SuspendedStart) + gp->state = GeneratorState::Completed; + + if (gp->state == GeneratorState::Completed) + return IteratorPrototype::createIterResultObject(engine, argc ? argv[0] : Value::undefinedValue(), true); + + // the bytecode interpreter interprets an exception with empty value as + // a yield called with return() + engine->throwError(Value::emptyValue()); + + return g->resume(engine, argc ? argv[0]: Value::undefinedValue()); +} + +ReturnedValue GeneratorPrototype::method_throw(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *engine = f->engine(); + const GeneratorObject *g = thisObject->as<GeneratorObject>(); + if (!g || g->d()->state == GeneratorState::Executing) + return engine->throwTypeError(); + + Heap::GeneratorObject *gp = g->d(); + + engine->throwError(argc ? argv[0]: Value::undefinedValue()); + + if (gp->state == GeneratorState::SuspendedStart || gp->state == GeneratorState::Completed) { + gp->state = GeneratorState::Completed; + return Encode::undefined(); + } + + return g->resume(engine, Value::undefinedValue()); +} + +ReturnedValue GeneratorObject::resume(ExecutionEngine *engine, const Value &arg) const +{ + Heap::GeneratorObject *gp = d(); + gp->state = GeneratorState::Executing; + gp->cppFrame.parent = engine->currentStackFrame; + engine->currentStackFrame = &gp->cppFrame; + + Q_ASSERT(gp->cppFrame.yield != nullptr); + const char *code = gp->cppFrame.yield; + gp->cppFrame.yield = nullptr; + gp->cppFrame.jsFrame->accumulator = arg; + gp->cppFrame.yieldIsIterator = false; + + Scope scope(engine); + ScopedValue result(scope, Moth::VME::interpret(&gp->cppFrame, engine, code)); + + engine->currentStackFrame = gp->cppFrame.parent; + + bool done = (gp->cppFrame.yield == nullptr); + gp->state = done ? GeneratorState::Completed : GeneratorState::SuspendedYield; + if (engine->hasException) + return Encode::undefined(); + if (gp->cppFrame.yieldIsIterator) + return result->asReturnedValue(); + return IteratorPrototype::createIterResultObject(engine, result, done); +} + +DEFINE_OBJECT_VTABLE(MemberGeneratorFunction); + +Heap::FunctionObject *MemberGeneratorFunction::create(ExecutionContext *context, Function *function, Object *homeObject, String *name) +{ + Scope scope(context); + Scoped<MemberGeneratorFunction> g(scope, context->engine()->memoryManager->allocate<MemberGeneratorFunction>(context, function, name)); + g->d()->homeObject.set(scope.engine, homeObject->d()); + ScopedObject proto(scope, scope.engine->newObject()); + proto->setPrototypeOf(scope.engine->generatorPrototype()); + g->defineDefaultProperty(scope.engine->id_prototype(), proto, Attr_NotConfigurable|Attr_NotEnumerable); + g->setPrototypeOf(ScopedObject(scope, scope.engine->generatorFunctionCtor()->get(scope.engine->id_prototype()))); + return g->d(); +} + +ReturnedValue MemberGeneratorFunction::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + return GeneratorFunction::virtualCall(f, thisObject, argv, argc); +} diff --git a/src/qml/jsruntime/qv4generatorobject_p.h b/src/qml/jsruntime/qv4generatorobject_p.h new file mode 100644 index 0000000000..366319723d --- /dev/null +++ b/src/qml/jsruntime/qv4generatorobject_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4GENERATOROBJECT_P_H +#define QV4GENERATOROBJECT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4functionobject_p.h" +#include "qv4stackframe_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +enum class GeneratorState { + Undefined, + SuspendedStart, + SuspendedYield, + Executing, + Completed +}; + +namespace Heap { + +struct GeneratorFunctionCtor : FunctionObject { + void init(QV4::ExecutionContext *scope); +}; + +struct GeneratorFunction : ArrowFunction { + void init(QV4::ExecutionContext *scope, Function *function, QV4::String *name = nullptr) { + ArrowFunction::init(scope, function, name); + } +}; + +struct MemberGeneratorFunction : MemberFunction { +}; + +struct GeneratorPrototype : FunctionObject { + void init(); +}; + +#define GeneratorObjectMembers(class, Member) \ + Member(class, Pointer, ExecutionContext *, context) \ + Member(class, Pointer, GeneratorFunction *, function) \ + Member(class, NoMark, GeneratorState, state) \ + Member(class, NoMark, CppStackFrame, cppFrame) \ + Member(class, ValueArray, ValueArray, stack) + +DECLARE_HEAP_OBJECT(GeneratorObject, Object) { + DECLARE_MARKOBJECTS(GeneratorObject); +}; + +} + +struct GeneratorFunctionCtor : FunctionCtor +{ + V4_OBJECT2(GeneratorFunctionCtor, FunctionCtor) + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct GeneratorFunction : ArrowFunction +{ + V4_OBJECT2(GeneratorFunction, ArrowFunction) + V4_INTERNALCLASS(GeneratorFunction) + + static Heap::FunctionObject *create(ExecutionContext *scope, Function *function); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct MemberGeneratorFunction : MemberFunction +{ + V4_OBJECT2(MemberGeneratorFunction, MemberFunction) + V4_INTERNALCLASS(MemberGeneratorFunction) + + static Heap::FunctionObject *create(ExecutionContext *scope, Function *function, Object *homeObject, String *name); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct GeneratorPrototype : Object +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_next(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_return(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_throw(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + + +struct GeneratorObject : Object { + V4_OBJECT2(GeneratorObject, Object) + Q_MANAGED_TYPE(GeneratorObject) + V4_INTERNALCLASS(GeneratorObject) + V4_PROTOTYPE(generatorPrototype) + + ReturnedValue resume(ExecutionEngine *engine, const Value &arg) const; +}; + +} + +QT_END_NAMESPACE + +#endif // QV4GENERATORFUNCTION_P_H + diff --git a/src/qml/jsruntime/qv4global_p.h b/src/qml/jsruntime/qv4global_p.h index 8769519a59..44adac26cd 100644 --- a/src/qml/jsruntime/qv4global_p.h +++ b/src/qml/jsruntime/qv4global_p.h @@ -76,9 +76,6 @@ namespace std { inline bool isinf(double d) { return !_finite(d) && !_isnan(d); } inline bool isnan(double d) { return !!_isnan(d); } inline bool isfinite(double d) { return _finite(d); } -#if _MSC_VER < 1800 -inline bool signbit(double d) { return _copysign(1.0, d) < 0; } -#endif } // namespace std @@ -91,11 +88,11 @@ inline double trunc(double d) { return d > 0 ? floor(d) : ceil(d); } // // NOTE: This should match the logic in qv4targetplatform_p.h! -#if defined(Q_PROCESSOR_X86) && (QT_POINTER_SIZE == 4) \ +#if defined(Q_PROCESSOR_X86_32) && (QT_POINTER_SIZE == 4) \ && (defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_FREEBSD)) # define V4_ENABLE_JIT #elif defined(Q_PROCESSOR_X86_64) && (QT_POINTER_SIZE == 8) \ - && (defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)) + && (defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)) # define V4_ENABLE_JIT #elif defined(Q_PROCESSOR_ARM_32) && (QT_POINTER_SIZE == 4) # if defined(thumb2) || defined(__thumb2__) || ((defined(__thumb) || defined(__thumb__)) && __TARGET_ARCH_THUMB-0 == 4) @@ -104,11 +101,16 @@ inline double trunc(double d) { return d > 0 ? floor(d) : ceil(d); } # define V4_ENABLE_JIT # endif #elif defined(Q_PROCESSOR_ARM_64) && (QT_POINTER_SIZE == 8) -# if defined(Q_OS_LINUX) +# if defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_INTEGRITY) # define V4_ENABLE_JIT # endif -#elif defined(Q_PROCESSOR_MIPS_32) && defined(Q_OS_LINUX) -# define V4_ENABLE_JIT +//#elif defined(Q_PROCESSOR_MIPS_32) && defined(Q_OS_LINUX) +//# define V4_ENABLE_JIT +#endif + +// check FPU with double precision on ARM platform +#if (defined(Q_PROCESSOR_ARM_64) || defined(Q_PROCESSOR_ARM_32)) && defined(V4_ENABLE_JIT) && defined(__ARM_FP) && (__ARM_FP <= 0x04) +# undef V4_ENABLE_JIT #endif // Black list some platforms @@ -147,19 +149,33 @@ QT_BEGIN_NAMESPACE namespace QV4 { +namespace Compiler { + struct Module; + struct Context; + struct JSUnitGenerator; + class Codegen; +} + +namespace Moth { + class BytecodeGenerator; +} + namespace Heap { struct Base; struct MemberData; struct ArrayData; + struct StringOrSymbol; struct String; + struct Symbol; struct Object; struct ObjectPrototype; struct ExecutionContext; - struct GlobalContext; struct CallContext; + struct QmlContext; struct ScriptFunction; + struct InternalClass; struct BooleanObject; struct NumberObject; @@ -174,21 +190,33 @@ namespace Heap { struct RegExp; struct EvalFunction; + struct SharedArrayBuffer; struct ArrayBuffer; struct DataView; struct TypedArray; + struct MapObject; + struct SetObject; + + struct PromiseObject; + struct PromiseCapability; + template <typename T, size_t> struct Pointer; } +struct CppStackFrame; class MemoryManager; +class ExecutableAllocator; +struct PropertyKey; +struct StringOrSymbol; struct String; +struct Symbol; struct Object; struct ObjectPrototype; struct ObjectIterator; struct ExecutionContext; -struct GlobalContext; struct CallContext; +struct QmlContext; struct ScriptFunction; struct InternalClass; struct Property; @@ -206,7 +234,6 @@ struct StringObject; struct ArrayObject; struct DateObject; struct FunctionObject; -struct BuiltinFunction; struct ErrorObject; struct ArgumentsObject; struct Managed; @@ -216,10 +243,17 @@ struct RegExpObject; struct RegExp; struct EvalFunction; +struct SharedArrayBuffer; struct ArrayBuffer; struct DataView; struct TypedArray; +struct MapObject; +struct SetMapObject; + +struct PromiseObject; +struct PromiseCapability; + // ReturnedValue is used to return values from runtime methods // the type has to be a primitive type (no struct or union), so that the compiler // will return it in a register on all platforms. @@ -230,6 +264,7 @@ struct Scope; struct ScopedValue; template<typename T> struct Scoped; typedef Scoped<String> ScopedString; +typedef Scoped<StringOrSymbol> ScopedStringOrSymbol; typedef Scoped<Object> ScopedObject; typedef Scoped<ArrayObject> ScopedArrayObject; typedef Scoped<FunctionObject> ScopedFunctionObject; @@ -238,16 +273,25 @@ typedef Scoped<ExecutionContext> ScopedContext; struct PersistentValueStorage; class PersistentValue; class WeakValue; +struct MarkStack; struct IdentifierTable; class RegExpCache; class MultiplyWrappedQObjectMap; -namespace Global { - enum { - ReservedArgumentCount = 6 - }; -} +enum class ObservedTraceValues : quint8 { + Integer = 1 << 0, + Boolean = 1 << 1, + Double = 1 << 2, + Other = 1 << 3, + TypeMask = Integer | Boolean | Double | Other, + + TruePathTaken = 1 << 0, + FalsePathTaken = 1 << 1, + + ArrayWasAccessed = 1 << 7, + ArrayAccessNeededFallback = 1 << 6, +}; enum PropertyFlag { Attr_Data = 0, @@ -340,6 +384,7 @@ struct PropertyAttributes bool isEmpty() const { return !m_all; } uint flags() const { return m_flags; } + uint all() const { return m_all; } bool operator==(PropertyAttributes other) { return m_all == other.m_all; @@ -349,15 +394,31 @@ struct PropertyAttributes } }; -struct StackFrame { +struct Q_QML_EXPORT StackFrame { QString source; QString function; - int line; - int column; + int line = -1; + int column = -1; }; typedef QVector<StackFrame> StackTrace; -} +enum class ObjectLiteralArgument { + Value, + Method, + Getter, + Setter +}; + +namespace JIT { + +enum class CallResultDestination { + Ignore, + InAccumulator, +}; + +} // JIT namespace + +} // QV4 namespace Q_DECLARE_TYPEINFO(QV4::PropertyAttributes, Q_PRIMITIVE_TYPE); diff --git a/src/qml/jsruntime/qv4globalobject.cpp b/src/qml/jsruntime/qv4globalobject.cpp index 0916e8e110..becdc3bc55 100644 --- a/src/qml/jsruntime/qv4globalobject.cpp +++ b/src/qml/jsruntime/qv4globalobject.cpp @@ -47,12 +47,12 @@ #include "qv4script_p.h" #include "qv4scopedvalue_p.h" #include "qv4string_p.h" +#include "qv4jscall_p.h" #include <private/qqmljsengine_p.h> #include <private/qqmljslexer_p.h> #include <private/qqmljsparser_p.h> #include <private/qqmljsast_p.h> -#include <qv4jsir_p.h> #include <qv4codegen_p.h> #include "private/qlocale_tools_p.h" #include "private/qtools_p.h" @@ -335,75 +335,60 @@ void Heap::EvalFunction::init(QV4::ExecutionContext *scope) Scope s(scope); Heap::FunctionObject::init(scope, s.engine->id_eval()); ScopedFunctionObject f(s, this); - f->defineReadonlyProperty(s.engine->id_length(), Primitive::fromInt32(1)); + f->defineReadonlyConfigurableProperty(s.engine->id_length(), Value::fromInt32(1)); } -void EvalFunction::evalCall(Scope &scope, CallData *callData, bool directCall) const +ReturnedValue EvalFunction::evalCall(const Value *, const Value *argv, int argc, bool directCall) const { - if (callData->argc < 1) { - scope.result = Encode::undefined(); - return; - } + if (argc < 1) + return Encode::undefined(); ExecutionEngine *v4 = engine(); - ExecutionContextSaver ctxSaver(scope); + bool isStrict = v4->currentStackFrame->v4Function->isStrict(); - ExecutionContext *currentContext = v4->currentContext; - ExecutionContext *ctx = currentContext; + Scope scope(v4); + ScopedContext ctx(scope, v4->currentContext()); if (!directCall) { - // the context for eval should be the global scope, so we fake a root - // context - ctx = v4->pushGlobalContext(); + // the context for eval should be the global scope + ctx = v4->scriptContext(); } - String *scode = callData->args[0].stringValue(); - if (!scode) { - scope.result = callData->args[0].asReturnedValue(); - return; - } + String *scode = argv[0].stringValue(); + if (!scode) + return argv[0].asReturnedValue(); const QString code = scode->toQString(); - bool inheritContext = !ctx->d()->strictMode; + bool inheritContext = !isStrict; - Script script(ctx, code, QStringLiteral("eval code")); - script.strictMode = (directCall && currentContext->d()->strictMode); + Script script(ctx, QV4::Compiler::ContextType::Eval, code, QStringLiteral("eval code")); + script.strictMode = (directCall && isStrict); script.inheritContext = inheritContext; script.parse(); - if (v4->hasException) { - scope.result = Encode::undefined(); - return; - } + if (v4->hasException) + return Encode::undefined(); Function *function = script.function(); - if (!function) { - scope.result = Encode::undefined(); - return; - } + if (!function) + return Encode::undefined(); + function->isEval = true; - if (function->isStrict() || (ctx->d()->strictMode)) { + if (function->isStrict() || isStrict) { ScopedFunctionObject e(scope, FunctionObject::createScriptFunction(ctx, function)); - ScopedCallData callData(scope, 0); - callData->thisObject = ctx->thisObject(); - e->call(scope, callData); - return; + ScopedValue thisObject(scope, directCall ? scope.engine->currentStackFrame->thisObject() : scope.engine->globalObject->asReturnedValue()); + return e->call(thisObject, nullptr, 0); } - ContextStateSaver stateSaver(scope, ctx); - - // set the correct strict mode flag on the context - ctx->d()->strictMode = false; - ctx->d()->compilationUnit = function->compilationUnit; - ctx->d()->constantTable = function->compilationUnit->constants; + ScopedValue thisObject(scope, scope.engine->currentStackFrame->thisObject()); - scope.result = Q_V4_PROFILE(ctx->engine(), function); + return function->call(thisObject, nullptr, 0, ctx); } -void EvalFunction::call(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue EvalFunction::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { // indirect call - static_cast<const EvalFunction *>(that)->evalCall(scope, callData, false); + return static_cast<const EvalFunction *>(f)->evalCall(thisObject, argv, argc, false); } @@ -424,10 +409,11 @@ static inline int toInt(const QChar &qc, int R) } // parseInt [15.1.2.2] -void GlobalFunctions::method_parseInt(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_parseInt(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedValue inputString(scope, callData->argument(0)); - ScopedValue radix(scope, callData->argument(1)); + Scope scope(b); + ScopedValue inputString(scope, argc ? argv[0] : Value::undefinedValue()); + ScopedValue radix(scope, argc > 1 ? argv[1] : Value::undefinedValue()); int R = radix->isUndefined() ? 0 : radix->toInt32(); // [15.1.2.2] step by step: @@ -504,10 +490,11 @@ void GlobalFunctions::method_parseInt(const BuiltinFunction *, Scope &scope, Cal } // parseFloat [15.1.2.3] -void GlobalFunctions::method_parseFloat(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_parseFloat(const FunctionObject *b, const Value *, const Value *argv, int argc) { + Scope scope(b); // [15.1.2.3] step by step: - ScopedString inputString(scope, callData->argument(0), ScopedString::Convert); + ScopedString inputString(scope, argc ? argv[0] : Value::undefinedValue(), ScopedString::Convert); CHECK_EXCEPTION(); QString trimmed = inputString->toQString().trimmed(); // 2 @@ -521,7 +508,7 @@ void GlobalFunctions::method_parseFloat(const BuiltinFunction *, Scope &scope, C QByteArray ba = trimmed.toLatin1(); bool ok; const char *begin = ba.constData(); - const char *end = 0; + const char *end = nullptr; double d = qstrtod(begin, &end, &ok); if (end - begin == 0) RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); // 3 @@ -530,115 +517,125 @@ void GlobalFunctions::method_parseFloat(const BuiltinFunction *, Scope &scope, C } /// isNaN [15.1.2.4] -void GlobalFunctions::method_isNaN(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_isNaN(const FunctionObject *, const Value *, const Value *argv, int argc) { - if (!callData->argc) + if (!argc) // undefined gets converted to NaN RETURN_RESULT(Encode(true)); - if (callData->args[0].integerCompatible()) + if (argv[0].integerCompatible()) RETURN_RESULT(Encode(false)); - double d = callData->args[0].toNumber(); + double d = argv[0].toNumber(); RETURN_RESULT(Encode((bool)std::isnan(d))); } /// isFinite [15.1.2.5] -void GlobalFunctions::method_isFinite(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_isFinite(const FunctionObject *, const Value *, const Value *argv, int argc) { - if (!callData->argc) + if (!argc) // undefined gets converted to NaN RETURN_RESULT(Encode(false)); - if (callData->args[0].integerCompatible()) + if (argv[0].integerCompatible()) RETURN_RESULT(Encode(true)); - double d = callData->args[0].toNumber(); + double d = argv[0].toNumber(); RETURN_RESULT(Encode((bool)std::isfinite(d))); } /// decodeURI [15.1.3.1] -void GlobalFunctions::method_decodeURI(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_decodeURI(const FunctionObject *b, const Value *, const Value *argv, int argc) { - if (callData->argc == 0) + if (argc == 0) RETURN_UNDEFINED(); - QString uriString = callData->args[0].toQString(); + ExecutionEngine *v4 = b->engine(); + QString uriString = argv[0].toQString(); bool ok; QString out = decode(uriString, DecodeNonReserved, &ok); if (!ok) { + Scope scope(v4); ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence"))); RETURN_RESULT(scope.engine->throwURIError(s)); } - RETURN_RESULT(scope.engine->newString(out)); + RETURN_RESULT(v4->newString(out)); } /// decodeURIComponent [15.1.3.2] -void GlobalFunctions::method_decodeURIComponent(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_decodeURIComponent(const FunctionObject *b, const Value *, const Value *argv, int argc) { - if (callData->argc == 0) + if (argc == 0) RETURN_UNDEFINED(); - QString uriString = callData->args[0].toQString(); + ExecutionEngine *v4 = b->engine(); + QString uriString = argv[0].toQString(); bool ok; QString out = decode(uriString, DecodeAll, &ok); if (!ok) { + Scope scope(v4); ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence"))); RETURN_RESULT(scope.engine->throwURIError(s)); } - RETURN_RESULT(scope.engine->newString(out)); + RETURN_RESULT(v4->newString(out)); } /// encodeURI [15.1.3.3] -void GlobalFunctions::method_encodeURI(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_encodeURI(const FunctionObject *b, const Value *, const Value *argv, int argc) { - if (callData->argc == 0) + if (argc == 0) RETURN_UNDEFINED(); - QString uriString = callData->args[0].toQString(); + ExecutionEngine *v4 = b->engine(); + QString uriString = argv[0].toQString(); bool ok; QString out = encode(uriString, uriUnescapedReserved, &ok); if (!ok) { + Scope scope(v4); ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence"))); RETURN_RESULT(scope.engine->throwURIError(s)); } - RETURN_RESULT(scope.engine->newString(out)); + RETURN_RESULT(v4->newString(out)); } /// encodeURIComponent [15.1.3.4] -void GlobalFunctions::method_encodeURIComponent(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_encodeURIComponent(const FunctionObject *b, const Value *, const Value *argv, int argc) { - if (callData->argc == 0) + if (argc == 0) RETURN_UNDEFINED(); - QString uriString = callData->args[0].toQString(); + ExecutionEngine *v4 = b->engine(); + QString uriString = argv[0].toQString(); bool ok; QString out = encode(uriString, uriUnescaped, &ok); if (!ok) { + Scope scope(v4); ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence"))); RETURN_RESULT(scope.engine->throwURIError(s)); } - RETURN_RESULT(scope.engine->newString(out)); + RETURN_RESULT(v4->newString(out)); } -void GlobalFunctions::method_escape(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_escape(const FunctionObject *b, const Value *, const Value *argv, int argc) { - if (!callData->argc) - RETURN_RESULT(scope.engine->newString(QStringLiteral("undefined"))); + ExecutionEngine *v4 = b->engine(); + if (!argc) + RETURN_RESULT(v4->newString(QStringLiteral("undefined"))); - QString str = callData->args[0].toQString(); - RETURN_RESULT(scope.engine->newString(escape(str))); + QString str = argv[0].toQString(); + RETURN_RESULT(v4->newString(escape(str))); } -void GlobalFunctions::method_unescape(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue GlobalFunctions::method_unescape(const FunctionObject *b, const Value *, const Value *argv, int argc) { - if (!callData->argc) - RETURN_RESULT(scope.engine->newString(QStringLiteral("undefined"))); + ExecutionEngine *v4 = b->engine(); + if (!argc) + RETURN_RESULT(v4->newString(QStringLiteral("undefined"))); - QString str = callData->args[0].toQString(); - RETURN_RESULT(scope.engine->newString(unescape(str))); + QString str = argv[0].toQString(); + RETURN_RESULT(v4->newString(unescape(str))); } diff --git a/src/qml/jsruntime/qv4globalobject_p.h b/src/qml/jsruntime/qv4globalobject_p.h index 273f1ba7ea..021b445955 100644 --- a/src/qml/jsruntime/qv4globalobject_p.h +++ b/src/qml/jsruntime/qv4globalobject_p.h @@ -69,23 +69,23 @@ struct Q_QML_EXPORT EvalFunction : FunctionObject { V4_OBJECT2(EvalFunction, FunctionObject) - void evalCall(Scope &scope, CallData *callData, bool directCall) const; + ReturnedValue evalCall(const Value *thisObject, const Value *argv, int argc, bool directCall) const; - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; struct GlobalFunctions { - static void method_parseInt(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_parseFloat(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isNaN(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isFinite(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_decodeURI(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_decodeURIComponent(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_encodeURI(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_encodeURIComponent(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_escape(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_unescape(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_parseInt(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_parseFloat(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isNaN(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isFinite(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_decodeURI(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_decodeURIComponent(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_encodeURI(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_encodeURIComponent(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_escape(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_unescape(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; } diff --git a/src/qml/jsruntime/qv4identifier.cpp b/src/qml/jsruntime/qv4identifier.cpp index 6260fd0cc8..5db5bd46ec 100644 --- a/src/qml/jsruntime/qv4identifier.cpp +++ b/src/qml/jsruntime/qv4identifier.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qv4identifier_p.h" #include "qv4identifiertable_p.h" +#include "qv4string_p.h" QT_BEGIN_NAMESPACE @@ -54,14 +55,16 @@ static inline int primeForNumBits(int numBits) } -IdentifierHashData::IdentifierHashData(int numBits) +IdentifierHashData::IdentifierHashData(IdentifierTable *table, int numBits) : size(0) , numBits(numBits) + , identifierTable(table) { refCount.store(1); alloc = primeForNumBits(numBits); entries = (IdentifierHashEntry *)malloc(alloc*sizeof(IdentifierHashEntry)); memset(entries, 0, alloc*sizeof(IdentifierHashEntry)); + identifierTable->addIdentifierHash(this); } IdentifierHashData::IdentifierHashData(IdentifierHashData *other) @@ -73,15 +76,21 @@ IdentifierHashData::IdentifierHashData(IdentifierHashData *other) alloc = other->alloc; entries = (IdentifierHashEntry *)malloc(alloc*sizeof(IdentifierHashEntry)); memcpy(entries, other->entries, alloc*sizeof(IdentifierHashEntry)); + identifierTable->addIdentifierHash(this); } -IdentifierHashBase::IdentifierHashBase(ExecutionEngine *engine) +IdentifierHashData::~IdentifierHashData() { + free(entries); + if (identifierTable) + identifierTable->removeIdentifierHash(this); +} + +IdentifierHash::IdentifierHash(ExecutionEngine *engine) { - d = new IdentifierHashData(3); - d->identifierTable = engine->identifierTable; + d = new IdentifierHashData(engine->identifierTable, 3); } -void IdentifierHashBase::detach() +void IdentifierHash::detach() { if (!d || d->refCount == 1) return; @@ -92,8 +101,10 @@ void IdentifierHashBase::detach() } -IdentifierHashEntry *IdentifierHashBase::addEntry(const Identifier *identifier) +IdentifierHashEntry *IdentifierHash::addEntry(PropertyKey identifier) { + Q_ASSERT(identifier.isStringOrSymbol()); + // fill up to max 50% bool grow = (d->alloc <= d->size*2); @@ -104,10 +115,10 @@ IdentifierHashEntry *IdentifierHashBase::addEntry(const Identifier *identifier) memset(newEntries, 0, newAlloc*sizeof(IdentifierHashEntry)); for (int i = 0; i < d->alloc; ++i) { const IdentifierHashEntry &e = d->entries[i]; - if (!e.identifier) + if (!e.identifier.isValid()) continue; - uint idx = e.identifier->hashValue % newAlloc; - while (newEntries[idx].identifier) { + uint idx = e.identifier.id() % newAlloc; + while (newEntries[idx].identifier.isValid()) { ++idx; idx %= newAlloc; } @@ -118,8 +129,8 @@ IdentifierHashEntry *IdentifierHashBase::addEntry(const Identifier *identifier) d->alloc = newAlloc; } - uint idx = identifier->hashValue % d->alloc; - while (d->entries[idx].identifier) { + uint idx = identifier.id() % d->alloc; + while (d->entries[idx].identifier.isValid()) { Q_ASSERT(d->entries[idx].identifier != identifier); ++idx; idx %= d->alloc; @@ -129,16 +140,16 @@ IdentifierHashEntry *IdentifierHashBase::addEntry(const Identifier *identifier) return d->entries + idx; } -const IdentifierHashEntry *IdentifierHashBase::lookup(const Identifier *identifier) const +const IdentifierHashEntry *IdentifierHash::lookup(PropertyKey identifier) const { - if (!d) - return 0; + if (!d || !identifier.isStringOrSymbol()) + return nullptr; Q_ASSERT(d->entries); - uint idx = identifier->hashValue % d->alloc; + uint idx = identifier.id() % d->alloc; while (1) { - if (!d->entries[idx].identifier) - return 0; + if (!d->entries[idx].identifier.isValid()) + return nullptr; if (d->entries[idx].identifier == identifier) return d->entries + idx; ++idx; @@ -146,43 +157,58 @@ const IdentifierHashEntry *IdentifierHashBase::lookup(const Identifier *identifi } } -const IdentifierHashEntry *IdentifierHashBase::lookup(const QString &str) const +const IdentifierHashEntry *IdentifierHash::lookup(const QString &str) const { if (!d) - return 0; - Q_ASSERT(d->entries); + return nullptr; - uint hash = String::createHashValue(str.constData(), str.length(), Q_NULLPTR); - uint idx = hash % d->alloc; - while (1) { - if (!d->entries[idx].identifier) - return 0; - if (d->entries[idx].identifier->string == str) - return d->entries + idx; - ++idx; - idx %= d->alloc; - } + PropertyKey id = d->identifierTable->asPropertyKey(str); + return lookup(id); } -const IdentifierHashEntry *IdentifierHashBase::lookup(String *str) const +const IdentifierHashEntry *IdentifierHash::lookup(String *str) const { if (!d) - return 0; - if (str->d()->identifier) - return lookup(str->d()->identifier); + return nullptr; + PropertyKey id = d->identifierTable->asPropertyKey(str); + if (id.isValid()) + return lookup(id); return lookup(str->toQString()); } -const Identifier *IdentifierHashBase::toIdentifier(const QString &str) const +const PropertyKey IdentifierHash::toIdentifier(const QString &str) const { Q_ASSERT(d); - return d->identifierTable->identifier(str); + return d->identifierTable->asPropertyKey(str); } -const Identifier *IdentifierHashBase::toIdentifier(Heap::String *str) const +const PropertyKey IdentifierHash::toIdentifier(Heap::String *str) const { Q_ASSERT(d); - return d->identifierTable->identifier(str); + return d->identifierTable->asPropertyKey(str); +} + +QString QV4::IdentifierHash::findId(int value) const +{ + IdentifierHashEntry *e = d->entries; + IdentifierHashEntry *end = e + d->alloc; + while (e < end) { + if (e->identifier.isValid() && e->value == value) + return e->identifier.toQString(); + ++e; + } + return QString(); +} + +void IdentifierHashData::markObjects(MarkStack *markStack) const +{ + IdentifierHashEntry *e = entries; + IdentifierHashEntry *end = e + alloc; + while (e < end) { + if (Heap::Base *o = e->identifier.asStringOrSymbol()) + o->mark(markStack); + ++e; + } } diff --git a/src/qml/jsruntime/qv4identifier_p.h b/src/qml/jsruntime/qv4identifier_p.h index 2695bbc875..32de8b7c8d 100644 --- a/src/qml/jsruntime/qv4identifier_p.h +++ b/src/qml/jsruntime/qv4identifier_p.h @@ -51,44 +51,24 @@ // #include <qstring.h> +#include <private/qv4global_p.h> +#include <private/qv4propertykey_p.h> QT_BEGIN_NAMESPACE namespace QV4 { -namespace Heap { - struct String; -} - -struct String; -struct IdentifierTable; -struct ExecutionEngine; - -struct Identifier -{ - QString string; - uint hashValue; -}; - - struct IdentifierHashEntry { - const Identifier *identifier; - union { - int value; - void *pointer; - }; - static int get(const IdentifierHashEntry *This, int *) { return This ? This->value : -1; } - static bool get(const IdentifierHashEntry *This, bool *) { return This != 0; } - static void *get(const IdentifierHashEntry *This, void **) { return This ? This->pointer : 0; } + PropertyKey identifier; + int value; }; struct IdentifierHashData { - IdentifierHashData(int numBits); + IdentifierHashData(IdentifierTable *table, int numBits); explicit IdentifierHashData(IdentifierHashData *other); - ~IdentifierHashData() { - free(entries); - } + ~IdentifierHashData(); + void markObjects(MarkStack *markStack) const; QBasicAtomicInt refCount; int alloc; @@ -98,73 +78,54 @@ struct IdentifierHashData IdentifierHashEntry *entries; }; -struct IdentifierHashBase +struct IdentifierHash { - IdentifierHashData *d; + IdentifierHashData *d = nullptr; - IdentifierHashBase() : d(0) {} - IdentifierHashBase(ExecutionEngine *engine); - inline IdentifierHashBase(const IdentifierHashBase &other); - inline ~IdentifierHashBase(); - inline IdentifierHashBase &operator=(const IdentifierHashBase &other); + IdentifierHash() {} + IdentifierHash(ExecutionEngine *engine); + inline IdentifierHash(const IdentifierHash &other); + inline ~IdentifierHash(); + inline IdentifierHash &operator=(const IdentifierHash &other); bool isEmpty() const { return !d; } inline int count() const; - bool contains(const Identifier *i) const; - bool contains(const QString &str) const; - bool contains(String *str) const; void detach(); + void add(const QString &str, int value); + void add(Heap::String *str, int value); + + inline int value(const QString &str) const; + inline int value(String *str) const; + QString findId(int value) const; + protected: - IdentifierHashEntry *addEntry(const Identifier *i); - const IdentifierHashEntry *lookup(const Identifier *identifier) const; + IdentifierHashEntry *addEntry(PropertyKey i); + const IdentifierHashEntry *lookup(PropertyKey identifier) const; const IdentifierHashEntry *lookup(const QString &str) const; const IdentifierHashEntry *lookup(String *str) const; - const Identifier *toIdentifier(const QString &str) const; - const Identifier *toIdentifier(Heap::String *str) const; + const PropertyKey toIdentifier(const QString &str) const; + const PropertyKey toIdentifier(Heap::String *str) const; }; -template<typename T> -struct IdentifierHash : public IdentifierHashBase -{ - IdentifierHash() - : IdentifierHashBase() {} - IdentifierHash(ExecutionEngine *engine) - : IdentifierHashBase(engine) {} - inline IdentifierHash(const IdentifierHash<T> &other) - : IdentifierHashBase(other) {} - inline ~IdentifierHash() {} - inline IdentifierHash &operator=(const IdentifierHash<T> &other) { - IdentifierHashBase::operator =(other); - return *this; - } - - void add(const QString &str, const T &value); - void add(Heap::String *str, const T &value); - - inline T value(const QString &str) const; - inline T value(String *str) const; - QString findId(T value) const; -}; - -inline IdentifierHashBase::IdentifierHashBase(const IdentifierHashBase &other) +inline IdentifierHash::IdentifierHash(const IdentifierHash &other) { d = other.d; if (d) d->refCount.ref(); } -inline IdentifierHashBase::~IdentifierHashBase() +inline IdentifierHash::~IdentifierHash() { if (d && !d->refCount.deref()) delete d; } -IdentifierHashBase &IdentifierHashBase::operator=(const IdentifierHashBase &other) +IdentifierHash &IdentifierHash::operator=(const IdentifierHash &other) { if (other.d) other.d->refCount.ref(); @@ -174,64 +135,35 @@ IdentifierHashBase &IdentifierHashBase::operator=(const IdentifierHashBase &othe return *this; } -inline int IdentifierHashBase::count() const +inline int IdentifierHash::count() const { return d ? d->size : 0; } -inline bool IdentifierHashBase::contains(const Identifier *i) const -{ - return lookup(i) != 0; -} - -inline bool IdentifierHashBase::contains(const QString &str) const -{ - return lookup(str) != 0; -} - -inline bool IdentifierHashBase::contains(String *str) const -{ - return lookup(str) != 0; -} - -template<typename T> -void IdentifierHash<T>::add(const QString &str, const T &value) +inline +void IdentifierHash::add(const QString &str, int value) { IdentifierHashEntry *e = addEntry(toIdentifier(str)); e->value = value; } -template<typename T> -void IdentifierHash<T>::add(Heap::String *str, const T &value) +inline +void IdentifierHash::add(Heap::String *str, int value) { IdentifierHashEntry *e = addEntry(toIdentifier(str)); e->value = value; } -template<typename T> -inline T IdentifierHash<T>::value(const QString &str) const -{ - return IdentifierHashEntry::get(lookup(str), (T*)0); -} - -template<typename T> -inline T IdentifierHash<T>::value(String *str) const +inline int IdentifierHash::value(const QString &str) const { - return IdentifierHashEntry::get(lookup(str), (T*)0); + const IdentifierHashEntry *e = lookup(str); + return e ? e->value : -1; } - -template<typename T> -QString IdentifierHash<T>::findId(T value) const +inline int IdentifierHash::value(String *str) const { - IdentifierHashEntry *e = d->entries; - IdentifierHashEntry *end = e + d->alloc; - while (e < end) { - if (e->identifier && IdentifierHashEntry::get(e, (T*)0) == value) - return e->identifier->string; - ++e; - } - return QString(); + const IdentifierHashEntry *e = lookup(str); + return e ? e->value : -1; } } diff --git a/src/qml/jsruntime/qv4identifiertable.cpp b/src/qml/jsruntime/qv4identifiertable.cpp index 3def6defbf..e476baa886 100644 --- a/src/qml/jsruntime/qv4identifiertable.cpp +++ b/src/qml/jsruntime/qv4identifiertable.cpp @@ -37,6 +37,7 @@ ** ****************************************************************************/ #include "qv4identifiertable_p.h" +#include "qv4symbol_p.h" QT_BEGIN_NAMESPACE @@ -53,44 +54,44 @@ static inline int primeForNumBits(int numBits) } -IdentifierTable::IdentifierTable(ExecutionEngine *engine) +IdentifierTable::IdentifierTable(ExecutionEngine *engine, int numBits) : engine(engine) , size(0) - , numBits(8) + , numBits(numBits) { alloc = primeForNumBits(numBits); - entries = (Heap::String **)malloc(alloc*sizeof(Heap::String *)); - memset(entries, 0, alloc*sizeof(Heap::String *)); + entriesByHash = (Heap::StringOrSymbol **)malloc(alloc*sizeof(Heap::StringOrSymbol *)); + entriesById = (Heap::StringOrSymbol **)malloc(alloc*sizeof(Heap::StringOrSymbol *)); + memset(entriesByHash, 0, alloc*sizeof(Heap::String *)); + memset(entriesById, 0, alloc*sizeof(Heap::String *)); } IdentifierTable::~IdentifierTable() { - for (int i = 0; i < alloc; ++i) - if (entries[i]) - delete entries[i]->identifier; - free(entries); + free(entriesByHash); + free(entriesById); + for (auto &h : idHashes) + h->identifierTable = nullptr; } -void IdentifierTable::addEntry(Heap::String *str) +void IdentifierTable::addEntry(Heap::StringOrSymbol *str) { uint hash = str->hashValue(); if (str->subtype == Heap::String::StringType_ArrayIndex) return; - str->identifier = new Identifier; - str->identifier->string = str->toQString(); - str->identifier->hashValue = hash; + str->identifier = PropertyKey::fromStringOrSymbol(str); bool grow = (alloc <= size*2); if (grow) { ++numBits; int newAlloc = primeForNumBits(numBits); - Heap::String **newEntries = (Heap::String **)malloc(newAlloc*sizeof(Heap::String *)); - memset(newEntries, 0, newAlloc*sizeof(Heap::String *)); - for (int i = 0; i < alloc; ++i) { - Heap::String *e = entries[i]; + Heap::StringOrSymbol **newEntries = (Heap::StringOrSymbol **)malloc(newAlloc*sizeof(Heap::String *)); + memset(newEntries, 0, newAlloc*sizeof(Heap::StringOrSymbol *)); + for (uint i = 0; i < alloc; ++i) { + Heap::StringOrSymbol *e = entriesByHash[i]; if (!e) continue; uint idx = e->stringHash % newAlloc; @@ -100,17 +101,42 @@ void IdentifierTable::addEntry(Heap::String *str) } newEntries[idx] = e; } - free(entries); - entries = newEntries; + free(entriesByHash); + entriesByHash = newEntries; + + newEntries = (Heap::StringOrSymbol **)malloc(newAlloc*sizeof(Heap::String *)); + memset(newEntries, 0, newAlloc*sizeof(Heap::StringOrSymbol *)); + for (uint i = 0; i < alloc; ++i) { + Heap::StringOrSymbol *e = entriesById[i]; + if (!e) + continue; + uint idx = e->identifier.id() % newAlloc; + while (newEntries[idx]) { + ++idx; + idx %= newAlloc; + } + newEntries[idx] = e; + } + free(entriesById); + entriesById = newEntries; + alloc = newAlloc; } uint idx = hash % alloc; - while (entries[idx]) { + while (entriesByHash[idx]) { + ++idx; + idx %= alloc; + } + entriesByHash[idx] = str; + + idx = str->identifier.id() % alloc; + while (entriesById[idx]) { ++idx; idx %= alloc; } - entries[idx] = str; + entriesById[idx] = str; + ++size; } @@ -120,10 +146,16 @@ Heap::String *IdentifierTable::insertString(const QString &s) { uint subtype; uint hash = String::createHashValue(s.constData(), s.length(), &subtype); + if (subtype == Heap::String::StringType_ArrayIndex) { + Heap::String *str = engine->newString(s); + str->stringHash = hash; + str->subtype = subtype; + return str; + } uint idx = hash % alloc; - while (Heap::String *e = entries[idx]) { + while (Heap::StringOrSymbol *e = entriesByHash[idx]) { if (e->stringHash == hash && e->toQString() == s) - return e; + return static_cast<Heap::String *>(e); ++idx; idx %= alloc; } @@ -135,18 +167,42 @@ Heap::String *IdentifierTable::insertString(const QString &s) return str; } +Heap::Symbol *IdentifierTable::insertSymbol(const QString &s) +{ + Q_ASSERT(s.at(0) == QLatin1Char('@')); + + uint subtype; + uint hash = String::createHashValue(s.constData(), s.length(), &subtype); + uint idx = hash % alloc; + while (Heap::StringOrSymbol *e = entriesByHash[idx]) { + if (e->stringHash == hash && e->toQString() == s) + return static_cast<Heap::Symbol *>(e); + ++idx; + idx %= alloc; + } + + Heap::Symbol *str = Symbol::create(engine, s); + str->stringHash = hash; + str->subtype = subtype; + addEntry(str); + return str; + +} + -Identifier *IdentifierTable::identifierImpl(const Heap::String *str) +PropertyKey IdentifierTable::asPropertyKeyImpl(const Heap::String *str) { - if (str->identifier) + if (str->identifier.isValid()) return str->identifier; uint hash = str->hashValue(); - if (str->subtype == Heap::String::StringType_ArrayIndex) - return 0; + if (str->subtype == Heap::String::StringType_ArrayIndex) { + str->identifier = PropertyKey::fromArrayIndex(hash); + return str->identifier; + } uint idx = hash % alloc; - while (Heap::String *e = entries[idx]) { - if (e->stringHash == hash && e->isEqualTo(str)) { + while (Heap::StringOrSymbol *e = entriesByHash[idx]) { + if (e->stringHash == hash && e->toQString() == str->toQString()) { str->identifier = e->identifier; return e->identifier; } @@ -158,37 +214,96 @@ Identifier *IdentifierTable::identifierImpl(const Heap::String *str) return str->identifier; } -Heap::String *IdentifierTable::stringFromIdentifier(Identifier *i) +Heap::StringOrSymbol *IdentifierTable::resolveId(PropertyKey i) const { - if (!i) - return 0; + uint arrayIdx = i.asArrayIndex(); + if (arrayIdx < UINT_MAX) + return engine->newString(QString::number(arrayIdx)); + if (!i.isValid()) + return nullptr; - uint idx = i->hashValue % alloc; + uint idx = i.id() % alloc; while (1) { - Heap::String *e = entries[idx]; - Q_ASSERT(e); - if (e->identifier == i) + Heap::StringOrSymbol *e = entriesById[idx]; + if (!e || e->identifier == i) return e; ++idx; idx %= alloc; } } -Identifier *IdentifierTable::identifier(const QString &s) +Heap::String *IdentifierTable::stringForId(PropertyKey i) const +{ + Heap::StringOrSymbol *s = resolveId(i); + Q_ASSERT(s && s->internalClass->vtable->isString); + return static_cast<Heap::String *>(s); +} + +Heap::Symbol *IdentifierTable::symbolForId(PropertyKey i) const +{ + Heap::StringOrSymbol *s = resolveId(i); + Q_ASSERT(!s || !s->internalClass->vtable->isString); + return static_cast<Heap::Symbol *>(s); +} + +void IdentifierTable::markObjects(MarkStack *markStack) +{ + for (const auto &h : idHashes) + h->markObjects(markStack); +} + +void IdentifierTable::sweep() +{ + int freed = 0; + + Heap::StringOrSymbol **newTable = (Heap::StringOrSymbol **)malloc(alloc*sizeof(Heap::String *)); + memset(newTable, 0, alloc*sizeof(Heap::StringOrSymbol *)); + memset(entriesById, 0, alloc*sizeof(Heap::StringOrSymbol *)); + for (uint i = 0; i < alloc; ++i) { + Heap::StringOrSymbol *e = entriesByHash[i]; + if (!e) + continue; + if (!e->isMarked()) { + ++freed; + continue; + } + uint idx = e->hashValue() % alloc; + while (newTable[idx]) { + ++idx; + if (idx == alloc) + idx = 0; + } + newTable[idx] = e; + + idx = e->identifier.id() % alloc; + while (entriesById[idx]) { + ++idx; + if (idx == alloc) + idx = 0; + } + entriesById[idx] = e; + } + free(entriesByHash); + entriesByHash = newTable; + + size -= freed; +} + +PropertyKey IdentifierTable::asPropertyKey(const QString &s) { return insertString(s)->identifier; } -Identifier *IdentifierTable::identifier(const char *s, int len) +PropertyKey IdentifierTable::asPropertyKey(const char *s, int len) { uint subtype; uint hash = String::createHashValue(s, len, &subtype); if (hash == UINT_MAX) - return identifier(QString::fromUtf8(s, len)); + return asPropertyKey(QString::fromUtf8(s, len)); QLatin1String latin(s, len); uint idx = hash % alloc; - while (Heap::String *e = entries[idx]) { + while (Heap::StringOrSymbol *e = entriesByHash[idx]) { if (e->stringHash == hash && e->toQString() == latin) return e->identifier; ++idx; diff --git a/src/qml/jsruntime/qv4identifiertable_p.h b/src/qml/jsruntime/qv4identifiertable_p.h index b0b08f1e54..78e2b6620e 100644 --- a/src/qml/jsruntime/qv4identifiertable_p.h +++ b/src/qml/jsruntime/qv4identifiertable_p.h @@ -53,55 +53,61 @@ #include "qv4identifier_p.h" #include "qv4string_p.h" #include "qv4engine_p.h" +#include <qset.h> #include <limits.h> QT_BEGIN_NAMESPACE namespace QV4 { -struct IdentifierTable +struct Q_QML_PRIVATE_EXPORT IdentifierTable { ExecutionEngine *engine; - int alloc; - int size; + uint alloc; + uint size; int numBits; - Heap::String **entries; + Heap::StringOrSymbol **entriesByHash; + Heap::StringOrSymbol **entriesById; - void addEntry(Heap::String *str); + QSet<IdentifierHashData *> idHashes; + + void addEntry(Heap::StringOrSymbol *str); public: - IdentifierTable(ExecutionEngine *engine); + IdentifierTable(ExecutionEngine *engine, int numBits = 8); ~IdentifierTable(); Heap::String *insertString(const QString &s); + Heap::Symbol *insertSymbol(const QString &s); - Identifier *identifier(const Heap::String *str) { - if (str->identifier) + PropertyKey asPropertyKey(const Heap::String *str) { + if (str->identifier.isValid()) return str->identifier; - return identifierImpl(str); + return asPropertyKeyImpl(str); } - Identifier *identifier(const QV4::String *str) { - return identifier(str->d()); + PropertyKey asPropertyKey(const QV4::String *str) { + return asPropertyKey(str->d()); } - Identifier *identifier(const QString &s); - Identifier *identifier(const char *s, int len); + PropertyKey asPropertyKey(const QString &s); + PropertyKey asPropertyKey(const char *s, int len); + + PropertyKey asPropertyKeyImpl(const Heap::String *str); - Identifier *identifierImpl(const Heap::String *str); + Heap::StringOrSymbol *resolveId(PropertyKey i) const; + Heap::String *stringForId(PropertyKey i) const; + Heap::Symbol *symbolForId(PropertyKey i) const; - Heap::String *stringFromIdentifier(Identifier *i); + void markObjects(MarkStack *markStack); + void sweep(); - void mark(MarkStack *markStack) { - for (int i = 0; i < alloc; ++i) { - Heap::String *entry = entries[i]; - if (!entry || entry->isMarked()) - continue; - entry->setMarkBit(); - Q_ASSERT(entry->vtable()->markObjects); - entry->vtable()->markObjects(entry, markStack); - } + void addIdentifierHash(IdentifierHashData *h) { + idHashes.insert(h); + } + void removeIdentifierHash(IdentifierHashData *h) { + idHashes.remove(h); } }; diff --git a/src/qml/jsruntime/qv4include.cpp b/src/qml/jsruntime/qv4include.cpp index 1edb7d3914..36569b0a60 100644 --- a/src/qml/jsruntime/qv4include.cpp +++ b/src/qml/jsruntime/qv4include.cpp @@ -39,6 +39,7 @@ #include "qv4include_p.h" #include "qv4scopedvalue_p.h" +#include "qv4jscall_p.h" #include <QtQml/qjsengine.h> #if QT_CONFIG(qml_network) @@ -60,7 +61,7 @@ QV4Include::QV4Include(const QUrl &url, QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &callback) : v4(engine), m_url(url) #if QT_CONFIG(qml_network) - , m_redirectCount(0), m_network(0) , m_reply(0) + , m_redirectCount(0), m_network(nullptr) , m_reply(nullptr) #endif { if (qmlContext) @@ -87,11 +88,12 @@ QV4Include::~QV4Include() { #if QT_CONFIG(qml_network) delete m_reply; - m_reply = 0; + m_reply = nullptr; #endif } -QV4::ReturnedValue QV4Include::resultValue(QV4::ExecutionEngine *v4, Status status) +QV4::ReturnedValue QV4Include::resultValue(QV4::ExecutionEngine *v4, Status status, + const QString &statusText) { QV4::Scope scope(v4); @@ -99,11 +101,13 @@ QV4::ReturnedValue QV4Include::resultValue(QV4::ExecutionEngine *v4, Status stat QV4::ScopedObject o(scope, v4->newObject()); QV4::ScopedString s(scope); QV4::ScopedValue v(scope); - o->put((s = v4->newString(QStringLiteral("OK"))), (v = QV4::Primitive::fromInt32(Ok))); - o->put((s = v4->newString(QStringLiteral("LOADING"))), (v = QV4::Primitive::fromInt32(Loading))); - o->put((s = v4->newString(QStringLiteral("NETWORK_ERROR"))), (v = QV4::Primitive::fromInt32(NetworkError))); - o->put((s = v4->newString(QStringLiteral("EXCEPTION"))), (v = QV4::Primitive::fromInt32(Exception))); - o->put((s = v4->newString(QStringLiteral("status"))), (v = QV4::Primitive::fromInt32(status))); + o->put((s = v4->newString(QStringLiteral("OK"))), (v = QV4::Value::fromInt32(Ok))); + o->put((s = v4->newString(QStringLiteral("LOADING"))), (v = QV4::Value::fromInt32(Loading))); + o->put((s = v4->newString(QStringLiteral("NETWORK_ERROR"))), (v = QV4::Value::fromInt32(NetworkError))); + o->put((s = v4->newString(QStringLiteral("EXCEPTION"))), (v = QV4::Value::fromInt32(Exception))); + o->put((s = v4->newString(QStringLiteral("status"))), (v = QV4::Value::fromInt32(status))); + if (!statusText.isEmpty()) + o->put((s = v4->newString(QStringLiteral("statusText"))), (v = v4->newString(statusText))); return o.asReturnedValue(); } @@ -118,10 +122,10 @@ void QV4Include::callback(const QV4::Value &callback, const QV4::Value &status) if (!f) return; - QV4::ScopedCallData callData(scope, 1); - callData->thisObject = v4->globalObject->asReturnedValue(); - callData->args[0] = status; - f->call(scope, callData); + QV4::JSCallData jsCallData(scope, 1); + *jsCallData->thisObject = v4->globalObject->asReturnedValue(); + jsCallData->args[0] = status; + f->call(jsCallData); if (scope.hasException()) scope.engine->catchException(); } @@ -162,27 +166,27 @@ void QV4Include::finished() QmlIR::Document::removeScriptPragmas(code); QV4::Scoped<QV4::QmlContext> qml(scope, m_qmlContext.value()); - QV4::Script script(v4, qml, code, m_url.toString()); + QV4::Script script(v4, qml, /*parse as QML binding*/false, code, m_url.toString()); script.parse(); if (!scope.engine->hasException) script.run(); if (scope.engine->hasException) { QV4::ScopedValue ex(scope, scope.engine->catchException()); - resultObj->put(status, QV4::ScopedValue(scope, QV4::Primitive::fromInt32(Exception))); + resultObj->put(status, QV4::ScopedValue(scope, QV4::Value::fromInt32(Exception))); QV4::ScopedString exception(scope, v4->newString(QStringLiteral("exception"))); resultObj->put(exception, ex); } else { - resultObj->put(status, QV4::ScopedValue(scope, QV4::Primitive::fromInt32(Ok))); + resultObj->put(status, QV4::ScopedValue(scope, QV4::Value::fromInt32(Ok))); } } else { - resultObj->put(status, QV4::ScopedValue(scope, QV4::Primitive::fromInt32(NetworkError))); + resultObj->put(status, QV4::ScopedValue(scope, QV4::Value::fromInt32(NetworkError))); } #else QV4::Scope scope(v4); QV4::ScopedObject resultObj(scope, m_resultObject.value()); QV4::ScopedString status(scope, v4->newString(QStringLiteral("status"))); - resultObj->put(status, QV4::ScopedValue(scope, QV4::Primitive::fromInt32(NetworkError))); + resultObj->put(status, QV4::ScopedValue(scope, QV4::Value::fromInt32(NetworkError))); #endif // qml_network QV4::ScopedValue cb(scope, m_callbackFunction.value()); @@ -195,22 +199,23 @@ void QV4Include::finished() /* Documented in qv8engine.cpp */ -void QV4Include::method_include(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData) +QV4::ReturnedValue QV4Include::method_include(const QV4::FunctionObject *b, const QV4::Value *, const QV4::Value *argv, int argc) { - if (!callData->argc) + QV4::Scope scope(b); + if (!argc) RETURN_UNDEFINED(); QQmlContextData *context = scope.engine->callingQmlContext(); - if (!context || !context->isJSContext) + if ((!context || !context->isJSContext) && scope.engine->qmlEngine()) RETURN_RESULT(scope.engine->throwError(QString::fromUtf8("Qt.include(): Can only be called from JavaScript files"))); - QV4::ScopedValue callbackFunction(scope, QV4::Primitive::undefinedValue()); - if (callData->argc >= 2 && callData->args[1].as<QV4::FunctionObject>()) - callbackFunction = callData->args[1]; + QV4::ScopedValue callbackFunction(scope, QV4::Value::undefinedValue()); + if (argc >= 2 && argv[1].as<QV4::FunctionObject>()) + callbackFunction = argv[1]; #if QT_CONFIG(qml_network) - QUrl url(scope.engine->resolvedUrl(callData->args[0].toQStringNoThrow())); + QUrl url(scope.engine->resolvedUrl(argv[0].toQStringNoThrow())); if (scope.engine->qmlEngine() && scope.engine->qmlEngine()->urlInterceptor()) url = scope.engine->qmlEngine()->urlInterceptor()->intercept(url, QQmlAbstractUrlInterceptor::JavaScriptFile); @@ -225,21 +230,8 @@ void QV4Include::method_include(const QV4::BuiltinFunction *, QV4::Scope &scope, } else { QScopedPointer<QV4::Script> script; - - if (const QQmlPrivate::CachedQmlUnit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(url)) { - QV4::CompiledData::CompilationUnit *jsUnit = cachedUnit->createCompilationUnit(); - script.reset(new QV4::Script(scope.engine, qmlcontext, jsUnit)); - } else { - QFile f(localFile); - - if (f.open(QIODevice::ReadOnly)) { - QByteArray data = f.readAll(); - QString code = QString::fromUtf8(data); - QmlIR::Document::removeScriptPragmas(code); - - script.reset(new QV4::Script(scope.engine, qmlcontext, code, url.toString())); - } - } + QString error; + script.reset(QV4::Script::createFromFileOrCache(scope.engine, qmlcontext, localFile, url, &error)); if (!script.isNull()) { script->parse(); @@ -254,19 +246,19 @@ void QV4Include::method_include(const QV4::BuiltinFunction *, QV4::Scope &scope, result = resultValue(scope.engine, Ok); } } else { - result = resultValue(scope.engine, NetworkError); + result = resultValue(scope.engine, NetworkError, error); } callback(callbackFunction, result); } - scope.result = result; #else QV4::ScopedValue result(scope); result = resultValue(scope.engine, NetworkError); callback(callbackFunction, result); - scope.result = result; #endif + + return result->asReturnedValue(); } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4include_p.h b/src/qml/jsruntime/qv4include_p.h index 5908d6bfde..70ccfbf223 100644 --- a/src/qml/jsruntime/qv4include_p.h +++ b/src/qml/jsruntime/qv4include_p.h @@ -77,7 +77,7 @@ public: Exception = 3 }; - static void method_include(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData); + static QV4::ReturnedValue method_include(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); private Q_SLOTS: void finished(); @@ -88,7 +88,8 @@ private: QV4::ReturnedValue result(); - static QV4::ReturnedValue resultValue(QV4::ExecutionEngine *v4, Status status = Loading); + static QV4::ReturnedValue resultValue(QV4::ExecutionEngine *v4, Status status = Loading, + const QString &statusText = QString()); static void callback(const QV4::Value &callback, const QV4::Value &status); QV4::ExecutionEngine *v4; diff --git a/src/qml/jsruntime/qv4internalclass.cpp b/src/qml/jsruntime/qv4internalclass.cpp index 603da1df7b..ddb8542e07 100644 --- a/src/qml/jsruntime/qv4internalclass.cpp +++ b/src/qml/jsruntime/qv4internalclass.cpp @@ -47,7 +47,7 @@ QT_BEGIN_NAMESPACE -using namespace QV4; +namespace QV4 { static const uchar prime_deltas[] = { 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, @@ -74,27 +74,11 @@ void PropertyHash::addEntry(const PropertyHash::Entry &entry, int classSize) // fill up to max 50% bool grow = (d->alloc <= d->size*2); - if (classSize < d->size || grow) { - PropertyHashData *dd = new PropertyHashData(grow ? d->numBits + 1 : d->numBits); - for (int i = 0; i < d->alloc; ++i) { - const Entry &e = d->entries[i]; - if (!e.identifier || e.index >= static_cast<unsigned>(classSize)) - continue; - uint idx = e.identifier->hashValue % dd->alloc; - while (dd->entries[idx].identifier) { - ++idx; - idx %= dd->alloc; - } - dd->entries[idx] = e; - } - dd->size = classSize; - Q_ASSERT(d->refCount > 1); - --d->refCount; - d = dd; - } + if (classSize < d->size || grow) + detach(grow, classSize); - uint idx = entry.identifier->hashValue % d->alloc; - while (d->entries[idx].identifier) { + uint idx = entry.identifier.id() % d->alloc; + while (d->entries[idx].identifier.isValid()) { ++idx; idx %= d->alloc; } @@ -102,69 +86,205 @@ void PropertyHash::addEntry(const PropertyHash::Entry &entry, int classSize) ++d->size; } +int PropertyHash::removeIdentifier(PropertyKey identifier, int classSize) +{ + int val = -1; + PropertyHashData *dd = new PropertyHashData(d->numBits); + for (int i = 0; i < d->alloc; ++i) { + const Entry &e = d->entries[i]; + if (!e.identifier.isValid() || e.index >= static_cast<unsigned>(classSize)) + continue; + if (e.identifier == identifier) { + val = e.index; + continue; + } + uint idx = e.identifier.id() % dd->alloc; + while (dd->entries[idx].identifier.isValid()) { + ++idx; + idx %= dd->alloc; + } + dd->entries[idx] = e; + } + dd->size = classSize; + if (!--d->refCount) + delete d; + d = dd; -InternalClass::InternalClass(ExecutionEngine *engine) - : engine(engine) - , vtable(0) - , prototype(0) - , m_sealed(0) - , m_frozen(0) - , size(0) - , extensible(true) + Q_ASSERT(val != -1); + return val; +} + +void PropertyHash::detach(bool grow, int classSize) { + if (d->refCount == 1 && !grow) + return; + + PropertyHashData *dd = new PropertyHashData(grow ? d->numBits + 1 : d->numBits); + for (int i = 0; i < d->alloc; ++i) { + const Entry &e = d->entries[i]; + if (!e.identifier.isValid() || e.index >= static_cast<unsigned>(classSize)) + continue; + uint idx = e.identifier.id() % dd->alloc; + while (dd->entries[idx].identifier.isValid()) { + ++idx; + idx %= dd->alloc; + } + dd->entries[idx] = e; + } + dd->size = classSize; + if (!--d->refCount) + delete d; + d = dd; } -InternalClass::InternalClass(const QV4::InternalClass &other) - : QQmlJS::Managed() - , engine(other.engine) - , vtable(other.vtable) - , prototype(other.prototype) - , propertyTable(other.propertyTable) - , nameMap(other.nameMap) - , propertyData(other.propertyData) - , m_sealed(0) - , m_frozen(0) - , size(other.size) - , extensible(other.extensible) +SharedInternalClassDataPrivate<PropertyKey>::SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate<PropertyKey> &other) + : refcount(1), + engine(other.engine), + data(nullptr) { - Q_ASSERT(extensible); + if (other.alloc()) { + int s = other.size(); + data = MemberData::allocate(engine, other.alloc(), other.data); + setSize(s); + } } -static void insertHoleIntoPropertyData(Object *object, int idx) +SharedInternalClassDataPrivate<PropertyKey>::SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate<PropertyKey> &other, + uint pos, PropertyKey value) + : refcount(1), + engine(other.engine) { - Heap::Object *o = object->d(); - ExecutionEngine *v4 = o->internalClass->engine; - int size = o->internalClass->size; - for (int i = size - 1; i > idx; --i) - o->setProperty(v4, i, *o->propertyData(i - 1)); + data = MemberData::allocate(engine, other.alloc(), nullptr); + memcpy(data, other.data, sizeof(Heap::MemberData) - sizeof(Value) + pos*sizeof(Value)); + data->values.size = pos + 1; + data->values.set(engine, pos, Value::fromReturnedValue(value.id())); } -static void removeFromPropertyData(Object *object, int idx, bool accessor = false) +void SharedInternalClassDataPrivate<PropertyKey>::grow() { - Heap::Object *o = object->d(); - ExecutionEngine *v4 = o->internalClass->engine; - int size = o->internalClass->size; - for (int i = idx; i < size; ++i) - o->setProperty(v4, i, *o->propertyData(i + (accessor ? 2 : 1))); + uint a = alloc() * 2; + int s = size(); + data = MemberData::allocate(engine, a, data); + setSize(s); + Q_ASSERT(alloc() >= a); } -void InternalClass::changeMember(Object *object, String *string, PropertyAttributes data, uint *index) +uint SharedInternalClassDataPrivate<PropertyKey>::alloc() const { - uint idx; - InternalClass *oldClass = object->internalClass(); - InternalClass *newClass = oldClass->changeMember(string->identifier(), data, &idx); - if (index) - *index = idx; + return data ? data->values.alloc : 0; +} - object->setInternalClass(newClass); - if (newClass->size > oldClass->size) { - Q_ASSERT(newClass->size == oldClass->size + 1); - insertHoleIntoPropertyData(object, idx); - } else if (newClass->size < oldClass->size) { - Q_ASSERT(newClass->size == oldClass->size - 1); - removeFromPropertyData(object, idx + 1); +uint SharedInternalClassDataPrivate<PropertyKey>::size() const +{ + return data ? data->values.size : 0; +} + +void SharedInternalClassDataPrivate<PropertyKey>::setSize(uint s) +{ + Q_ASSERT(data && s <= alloc()); + data->values.size = s; +} + +PropertyKey SharedInternalClassDataPrivate<PropertyKey>::at(uint i) +{ + Q_ASSERT(data && i < size()); + return PropertyKey::fromId(data->values.values[i].rawValue()); +} + +void SharedInternalClassDataPrivate<PropertyKey>::set(uint i, PropertyKey t) +{ + Q_ASSERT(data && i < size()); + data->values.values[i].rawValueRef() = t.id(); +} + +void SharedInternalClassDataPrivate<PropertyKey>::mark(MarkStack *s) +{ + if (data) + data->mark(s); +} + + + +namespace Heap { + +void InternalClass::init(ExecutionEngine *engine) +{ + Base::init(); + new (&propertyTable) PropertyHash(); + new (&nameMap) SharedInternalClassData<PropertyKey>(engine); + new (&propertyData) SharedInternalClassData<PropertyAttributes>(engine); + new (&transitions) std::vector<Transition>(); + + this->engine = engine; + vtable = QV4::InternalClass::staticVTable(); +// prototype = nullptr; +// parent = nullptr; +// size = 0; + extensible = true; + isFrozen = false; + isSealed = false; + isUsedAsProto = false; + protoId = engine->newProtoId(); + + // Also internal classes need an internal class pointer. Simply make it point to itself + internalClass.set(engine, this); +} + + +void InternalClass::init(Heap::InternalClass *other) +{ + Base::init(); + Q_ASSERT(!other->isFrozen); + new (&propertyTable) PropertyHash(other->propertyTable); + new (&nameMap) SharedInternalClassData<PropertyKey>(other->nameMap); + new (&propertyData) SharedInternalClassData<PropertyAttributes>(other->propertyData); + new (&transitions) std::vector<Transition>(); + + engine = other->engine; + vtable = other->vtable; + prototype = other->prototype; + parent = other; + size = other->size; + extensible = other->extensible; + isSealed = other->isSealed; + isFrozen = other->isFrozen; + isUsedAsProto = other->isUsedAsProto; + protoId = engine->newProtoId(); + + internalClass.set(engine, other->internalClass); +} + +void InternalClass::destroy() +{ +#ifndef QT_NO_DEBUG + for (const auto &t : transitions) { + Q_ASSERT(!t.lookup || !t.lookup->isMarked()); } +#endif + if (parent && parent->engine && parent->isMarked()) + parent->removeChildEntry(this); + + propertyTable.~PropertyHash(); + nameMap.~SharedInternalClassData<PropertyKey>(); + propertyData.~SharedInternalClassData<PropertyAttributes>(); + transitions.~vector<Transition>(); + engine = nullptr; + Base::destroy(); +} + +QString InternalClass::keyAt(uint index) const +{ + return nameMap.at(index).toQString(); +} + +void InternalClass::changeMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry) +{ + Q_ASSERT(id.isStringOrSymbol()); + + Heap::InternalClass *oldClass = object->internalClass(); + Heap::InternalClass *newClass = oldClass->changeMember(id, data, entry); + object->setInternalClass(newClass); } InternalClassTransition &InternalClass::lookupOrInsertTransition(const InternalClassTransition &t) @@ -178,44 +298,66 @@ InternalClassTransition &InternalClass::lookupOrInsertTransition(const InternalC } } -InternalClass *InternalClass::changeMember(Identifier *identifier, PropertyAttributes data, uint *index) +static void addDummyEntry(InternalClass *newClass, PropertyHash::Entry e) +{ + // add a dummy entry, since we need two entries for accessors + newClass->propertyTable.addEntry(e, newClass->size); + newClass->nameMap.add(newClass->size, PropertyKey::invalid()); + newClass->propertyData.add(newClass->size, PropertyAttributes()); + ++newClass->size; +} + +Heap::InternalClass *InternalClass::changeMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry) { - data.resolve(); - uint idx = find(identifier); + if (!data.isEmpty()) + data.resolve(); + PropertyHash::Entry *e = findEntry(identifier); + Q_ASSERT(e && e->index != UINT_MAX); + uint idx = e->index; Q_ASSERT(idx != UINT_MAX); - if (index) - *index = idx; + if (entry) { + entry->index = idx; + entry->setterIndex = e->setterIndex; + entry->attributes = data; + } if (data == propertyData.at(idx)) - return this; + return static_cast<Heap::InternalClass *>(this); - Transition temp = { { identifier }, nullptr, (int)data.flags() }; + Transition temp = { { identifier }, nullptr, int(data.all()) }; Transition &t = lookupOrInsertTransition(temp); if (t.lookup) return t.lookup; // create a new class and add it to the tree - InternalClass *newClass = engine->internalClasses[EngineBase::Class_Empty]->changeVTable(vtable); - newClass = newClass->changePrototype(prototype); - for (uint i = 0; i < size; ++i) { - if (i == idx) { - newClass = newClass->addMember(nameMap.at(i), data); - } else if (!propertyData.at(i).isEmpty()) { - newClass = newClass->addMember(nameMap.at(i), propertyData.at(i)); - } + Heap::InternalClass *newClass = engine->newClass(this); + if (data.isAccessor() && e->setterIndex == UINT_MAX) { + Q_ASSERT(!propertyData.at(idx).isAccessor()); + + // add a dummy entry for the accessor + entry->setterIndex = newClass->size; + e->setterIndex = newClass->size; + addDummyEntry(newClass, *e); } + newClass->propertyData.set(idx, data); + t.lookup = newClass; Q_ASSERT(t.lookup); return newClass; } -InternalClass *InternalClass::changePrototypeImpl(Heap::Object *proto) +Heap::InternalClass *InternalClass::changePrototypeImpl(Heap::Object *proto) { + Scope scope(engine); + ScopedValue protectThis(scope, this); + if (proto) + proto->setUsedAsProto(); Q_ASSERT(prototype != proto); + Q_ASSERT(!proto || proto->internalClass->isUsedAsProto); - Transition temp = { { nullptr }, 0, Transition::PrototypeChange }; + Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::PrototypeChange }; temp.prototype = proto; Transition &t = lookupOrInsertTransition(temp); @@ -223,28 +365,19 @@ InternalClass *InternalClass::changePrototypeImpl(Heap::Object *proto) return t.lookup; // create a new class and add it to the tree - InternalClass *newClass; - if (!size && !prototype) { - newClass = engine->newClass(*this); - newClass->prototype = proto; - } else { - newClass = engine->internalClasses[EngineBase::Class_Empty]->changeVTable(vtable); - newClass = newClass->changePrototype(proto); - for (uint i = 0; i < size; ++i) { - if (!propertyData.at(i).isEmpty()) - newClass = newClass->addMember(nameMap.at(i), propertyData.at(i)); - } - } + Heap::InternalClass *newClass = engine->newClass(this); + newClass->prototype = proto; t.lookup = newClass; + return newClass; } -InternalClass *InternalClass::changeVTableImpl(const VTable *vt) +Heap::InternalClass *InternalClass::changeVTableImpl(const VTable *vt) { Q_ASSERT(vtable != vt); - Transition temp = { { nullptr }, nullptr, Transition::VTableChange }; + Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::VTableChange }; temp.vtable = vt; Transition &t = lookupOrInsertTransition(temp); @@ -252,18 +385,8 @@ InternalClass *InternalClass::changeVTableImpl(const VTable *vt) return t.lookup; // create a new class and add it to the tree - InternalClass *newClass; - if (this == engine->internalClasses[EngineBase::Class_Empty]) { - newClass = engine->newClass(*this); - newClass->vtable = vt; - } else { - newClass = engine->internalClasses[EngineBase::Class_Empty]->changeVTable(vt); - newClass = newClass->changePrototype(prototype); - for (uint i = 0; i < size; ++i) { - if (!propertyData.at(i).isEmpty()) - newClass = newClass->addMember(nameMap.at(i), propertyData.at(i)); - } - } + Heap::InternalClass *newClass = engine->newClass(this); + newClass->vtable = vt; t.lookup = newClass; Q_ASSERT(t.lookup); @@ -271,17 +394,17 @@ InternalClass *InternalClass::changeVTableImpl(const VTable *vt) return newClass; } -InternalClass *InternalClass::nonExtensible() +Heap::InternalClass *InternalClass::nonExtensible() { if (!extensible) return this; - Transition temp = { { nullptr }, nullptr, Transition::NotExtensible}; + Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::NotExtensible}; Transition &t = lookupOrInsertTransition(temp); if (t.lookup) return t.lookup; - InternalClass *newClass = engine->newClass(*this); + Heap::InternalClass *newClass = engine->newClass(this); newClass->extensible = false; t.lookup = newClass; @@ -289,214 +412,261 @@ InternalClass *InternalClass::nonExtensible() return newClass; } -void InternalClass::addMember(Object *object, String *string, PropertyAttributes data, uint *index) +void InternalClass::addMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry) { - data.resolve(); - object->internalClass()->engine->identifierTable->identifier(string); - if (object->internalClass()->propertyTable.lookup(string->d()->identifier) < object->internalClass()->size) { - changeMember(object, string, data, index); + Q_ASSERT(id.isStringOrSymbol()); + if (!data.isEmpty()) + data.resolve(); + PropertyHash::Entry *e = object->internalClass()->findEntry(id); + if (e) { + changeMember(object, id, data, entry); return; } - uint idx; - InternalClass *newClass = object->internalClass()->addMemberImpl(string->identifier(), data, &idx); - if (index) - *index = idx; - + Heap::InternalClass *newClass = object->internalClass()->addMemberImpl(id, data, entry); object->setInternalClass(newClass); } -InternalClass *InternalClass::addMember(String *string, PropertyAttributes data, uint *index) +Heap::InternalClass *InternalClass::addMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry) { - engine->identifierTable->identifier(string); - return addMember(string->identifier(), data, index); -} + Q_ASSERT(identifier.isStringOrSymbol()); + if (!data.isEmpty()) + data.resolve(); -InternalClass *InternalClass::addMember(Identifier *identifier, PropertyAttributes data, uint *index) -{ - data.resolve(); + PropertyHash::Entry *e = findEntry(identifier); + if (e) + return changeMember(identifier, data, entry); - if (propertyTable.lookup(identifier) < size) - return changeMember(identifier, data, index); - - return addMemberImpl(identifier, data, index); + return addMemberImpl(identifier, data, entry); } -InternalClass *InternalClass::addMemberImpl(Identifier *identifier, PropertyAttributes data, uint *index) +Heap::InternalClass *InternalClass::addMemberImpl(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry) { Transition temp = { { identifier }, nullptr, (int)data.flags() }; Transition &t = lookupOrInsertTransition(temp); - if (index) - *index = size; + if (entry) { + entry->index = size; + entry->setterIndex = data.isAccessor() ? size + 1 : UINT_MAX; + entry->attributes = data; + } if (t.lookup) return t.lookup; // create a new class and add it to the tree - InternalClass *newClass = engine->newClass(*this); - PropertyHash::Entry e = { identifier, newClass->size }; + Scope scope(engine); + Scoped<QV4::InternalClass> ic(scope, engine->newClass(this)); + InternalClass *newClass = ic->d(); + PropertyHash::Entry e = { identifier, newClass->size, data.isAccessor() ? newClass->size + 1 : UINT_MAX }; newClass->propertyTable.addEntry(e, newClass->size); newClass->nameMap.add(newClass->size, identifier); newClass->propertyData.add(newClass->size, data); ++newClass->size; - if (data.isAccessor()) { - // add a dummy entry, since we need two entries for accessors - newClass->propertyTable.addEntry(e, newClass->size); - newClass->nameMap.add(newClass->size, 0); - newClass->propertyData.add(newClass->size, PropertyAttributes()); - ++newClass->size; - } + if (data.isAccessor()) + addDummyEntry(newClass, e); t.lookup = newClass; Q_ASSERT(t.lookup); return newClass; } -void InternalClass::removeMember(Object *object, Identifier *id) +void InternalClass::removeChildEntry(InternalClass *child) { - InternalClass *oldClass = object->internalClass(); - uint propIdx = oldClass->propertyTable.lookup(id); - Q_ASSERT(propIdx < oldClass->size); - - Transition temp = { { id }, nullptr, -1 }; - Transition &t = object->internalClass()->lookupOrInsertTransition(temp); - - bool accessor = oldClass->propertyData.at(propIdx).isAccessor(); - - if (t.lookup) { - object->setInternalClass(t.lookup); - } else { - // create a new class and add it to the tree - InternalClass *newClass = oldClass->engine->internalClasses[EngineBase::Class_Empty]->changeVTable(oldClass->vtable); - newClass = newClass->changePrototype(oldClass->prototype); - for (uint i = 0; i < oldClass->size; ++i) { - if (i == propIdx) - continue; - if (!oldClass->propertyData.at(i).isEmpty()) - newClass = newClass->addMember(oldClass->nameMap.at(i), oldClass->propertyData.at(i)); + Q_ASSERT(engine); + for (auto &t : transitions) { + if (t.lookup == child) { + t.lookup = nullptr; + return; } - object->setInternalClass(newClass); } + Q_UNREACHABLE(); - Q_ASSERT(object->internalClass()->size == oldClass->size - (accessor ? 2 : 1)); - - // remove the entry in the property data - removeFromPropertyData(object, propIdx, accessor); - - t.lookup = object->internalClass(); - Q_ASSERT(t.lookup); } -uint QV4::InternalClass::find(const String *string) +void InternalClass::removeMember(QV4::Object *object, PropertyKey identifier) { - engine->identifierTable->identifier(string); - const Identifier *id = string->d()->identifier; +#ifndef QT_NO_DEBUG + Heap::InternalClass *oldClass = object->internalClass(); + Q_ASSERT(oldClass->findEntry(identifier) != nullptr); +#endif - uint index = propertyTable.lookup(id); - if (index < size) - return index; + changeMember(object, identifier, Attr_Invalid); - return UINT_MAX; +#ifndef QT_NO_DEBUG + // we didn't remove the data slot, just made it inaccessible + Q_ASSERT(object->internalClass()->size == oldClass->size); +#endif } -InternalClass *InternalClass::sealed() +Heap::InternalClass *InternalClass::sealed() { - if (m_sealed) - return m_sealed; + if (isSealed) + return this; + + bool alreadySealed = !extensible; + for (uint i = 0; i < size; ++i) { + PropertyAttributes attrs = propertyData.at(i); + if (attrs.isEmpty()) + continue; + if (attrs.isConfigurable()) { + alreadySealed = false; + break; + } + } + + if (alreadySealed) { + isSealed = true; + return this; + } + + Transition temp = { { PropertyKey::invalid() }, nullptr, InternalClassTransition::Sealed }; + Transition &t = lookupOrInsertTransition(temp); + + if (t.lookup) { + Q_ASSERT(t.lookup && t.lookup->isSealed); + return t.lookup; + } + + Scope scope(engine); + Scoped<QV4::InternalClass> ic(scope, engine->newClass(this)); + Heap::InternalClass *s = ic->d(); - m_sealed = engine->internalClasses[EngineBase::Class_Empty]->changeVTable(vtable); - m_sealed = m_sealed->changePrototype(prototype); for (uint i = 0; i < size; ++i) { PropertyAttributes attrs = propertyData.at(i); if (attrs.isEmpty()) continue; attrs.setConfigurable(false); - m_sealed = m_sealed->addMember(nameMap.at(i), attrs); + s->propertyData.set(i, attrs); } - m_sealed = m_sealed->nonExtensible(); + s->extensible = false; + s->isSealed = true; - m_sealed->m_sealed = m_sealed; - return m_sealed; + t.lookup = s; + return s; } -InternalClass *InternalClass::frozen() +Heap::InternalClass *InternalClass::frozen() { - if (m_frozen) - return m_frozen; + if (isFrozen) + return this; + + bool alreadyFrozen = !extensible; + for (uint i = 0; i < size; ++i) { + PropertyAttributes attrs = propertyData.at(i); + if (attrs.isEmpty()) + continue; + if ((attrs.isData() && attrs.isWritable()) || attrs.isConfigurable()) { + alreadyFrozen = false; + break; + } + } + + if (alreadyFrozen) { + isSealed = true; + isFrozen = true; + return this; + } - m_frozen = propertiesFrozen(); - m_frozen = m_frozen->nonExtensible(); + Transition temp = { { PropertyKey::invalid() }, nullptr, InternalClassTransition::Frozen }; + Transition &t = lookupOrInsertTransition(temp); - m_frozen->m_frozen = m_frozen; - m_frozen->m_sealed = m_frozen; - return m_frozen; + if (t.lookup) { + Q_ASSERT(t.lookup && t.lookup->isSealed && t.lookup->isFrozen); + return t.lookup; + } + + Scope scope(engine); + Scoped<QV4::InternalClass> ic(scope, engine->newClass(this)); + Heap::InternalClass *f = ic->d(); + + for (uint i = 0; i < size; ++i) { + PropertyAttributes attrs = propertyData.at(i); + if (attrs.isEmpty()) + continue; + if (attrs.isData()) + attrs.setWritable(false); + attrs.setConfigurable(false); + f->propertyData.set(i, attrs); + } + f->extensible = false; + f->isSealed = true; + f->isFrozen = true; + + t.lookup = f; + return f; } -InternalClass *InternalClass::propertiesFrozen() const +Heap::InternalClass *InternalClass::propertiesFrozen() const { - InternalClass *frozen = engine->internalClasses[EngineBase::Class_Empty]->changeVTable(vtable); + Scope scope(engine); + Scoped<QV4::InternalClass> frozen(scope, engine->internalClasses(EngineBase::Class_Empty)->changeVTable(vtable)); frozen = frozen->changePrototype(prototype); for (uint i = 0; i < size; ++i) { PropertyAttributes attrs = propertyData.at(i); - if (attrs.isEmpty()) + if (!nameMap.at(i).isValid()) continue; - attrs.setWritable(false); - attrs.setConfigurable(false); + if (!attrs.isEmpty()) { + attrs.setWritable(false); + attrs.setConfigurable(false); + } frozen = frozen->addMember(nameMap.at(i), attrs); } - return frozen; + return frozen->d(); } -void InternalClass::destroy() +Heap::InternalClass *InternalClass::asProtoClass() { - std::vector<InternalClass *> destroyStack; - destroyStack.reserve(64); - destroyStack.push_back(this); + if (isUsedAsProto) + return this; - while (!destroyStack.empty()) { - InternalClass *next = destroyStack.back(); - destroyStack.pop_back(); - if (!next->engine) - continue; - next->engine = 0; - next->propertyTable.~PropertyHash(); - next->nameMap.~SharedInternalClassData<Identifier *>(); - next->propertyData.~SharedInternalClassData<PropertyAttributes>(); - if (next->m_sealed) - destroyStack.push_back(next->m_sealed); - if (next->m_frozen) - destroyStack.push_back(next->m_frozen); - - for (size_t i = 0; i < next->transitions.size(); ++i) { - Q_ASSERT(next->transitions.at(i).lookup); - destroyStack.push_back(next->transitions.at(i).lookup); - } + Transition temp = { { PropertyKey::invalid() }, nullptr, Transition::ProtoClass }; + Transition &t = lookupOrInsertTransition(temp); + if (t.lookup) + return t.lookup; + + Heap::InternalClass *newClass = engine->newClass(this); + newClass->isUsedAsProto = true; - next->transitions.~vector<Transition>(); + t.lookup = newClass; + Q_ASSERT(t.lookup); + return newClass; +} + +static void updateProtoUsage(Heap::Object *o, Heap::InternalClass *ic) +{ + if (ic->prototype == o) + ic->protoId = ic->engine->newProtoId(); + for (auto &t : ic->transitions) { + if (t.lookup) + updateProtoUsage(o, t.lookup); } } -void InternalClassPool::markObjects(MarkStack *markStack) + +void InternalClass::updateProtoUsage(Heap::Object *o) { - InternalClass *ic = markStack->engine->internalClasses[EngineBase::Class_Empty]; + Q_ASSERT(isUsedAsProto); + Heap::InternalClass *ic = engine->internalClasses(EngineBase::Class_Empty); Q_ASSERT(!ic->prototype); - // only need to go two levels into the IC hierarchy, as prototype changes - // can only happen there - for (auto &t : ic->transitions) { - Q_ASSERT(t.lookup); - if (t.flags == InternalClassTransition::VTableChange) { - InternalClass *ic2 = t.lookup; - for (auto &t2 : ic2->transitions) { - if (t2.flags == InternalClassTransition::PrototypeChange) - t2.lookup->prototype->mark(markStack); - } - } else if (t.flags == InternalClassTransition::PrototypeChange) { - t.lookup->prototype->mark(markStack); - } - } + Heap::updateProtoUsage(o, ic); +} + +void InternalClass::markObjects(Heap::Base *b, MarkStack *stack) +{ + Heap::InternalClass *ic = static_cast<Heap::InternalClass *>(b); + if (ic->prototype) + ic->prototype->mark(stack); + if (ic->parent) + ic->parent->mark(stack); + + ic->nameMap.mark(stack); +} + +} + } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4internalclass_p.h b/src/qml/jsruntime/qv4internalclass_p.h index df17074e72..681cbda5f9 100644 --- a/src/qml/jsruntime/qv4internalclass_p.h +++ b/src/qml/jsruntime/qv4internalclass_p.h @@ -53,25 +53,30 @@ #include "qv4global_p.h" #include <QHash> -#include <private/qqmljsmemorypool_p.h> -#include <private/qv4identifier_p.h> +#include <private/qv4propertykey_p.h> +#include <private/qv4heap_p.h> QT_BEGIN_NAMESPACE namespace QV4 { -struct String; -struct Object; -struct Identifier; struct VTable; struct MarkStack; +struct InternalClassEntry { + uint index; + uint setterIndex; + PropertyAttributes attributes; + bool isValid() const { return !attributes.isEmpty(); } +}; + struct PropertyHashData; struct PropertyHash { struct Entry { - const Identifier *identifier; + PropertyKey identifier; uint index; + uint setterIndex; }; PropertyHashData *d; @@ -79,12 +84,12 @@ struct PropertyHash inline PropertyHash(); inline PropertyHash(const PropertyHash &other); inline ~PropertyHash(); + PropertyHash &operator=(const PropertyHash &other); void addEntry(const Entry &entry, int classSize); - uint lookup(const Identifier *identifier) const; - -private: - PropertyHash &operator=(const PropertyHash &other); + Entry *lookup(PropertyKey identifier) const; + int removeIdentifier(PropertyKey identifier, int classSize); + void detach(bool grow, int classSize); }; struct PropertyHashData @@ -118,40 +123,121 @@ inline PropertyHash::~PropertyHash() delete d; } -inline uint PropertyHash::lookup(const Identifier *identifier) const +inline PropertyHash &PropertyHash::operator=(const PropertyHash &other) +{ + ++other.d->refCount; + if (!--d->refCount) + delete d; + d = other.d; + return *this; +} + + + +inline PropertyHash::Entry *PropertyHash::lookup(PropertyKey identifier) const { Q_ASSERT(d->entries); - uint idx = identifier->hashValue % d->alloc; + uint idx = identifier.id() % d->alloc; while (1) { if (d->entries[idx].identifier == identifier) - return d->entries[idx].index; - if (!d->entries[idx].identifier) - return UINT_MAX; + return d->entries + idx; + if (!d->entries[idx].identifier.isValid()) + return nullptr; ++idx; idx %= d->alloc; } } +template<typename T> +struct SharedInternalClassDataPrivate { + SharedInternalClassDataPrivate(ExecutionEngine *) + : refcount(1), + m_alloc(0), + m_size(0), + data(nullptr) + { } + SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate &other) + : refcount(1), + m_alloc(other.m_alloc), + m_size(other.m_size) + { + if (m_alloc) { + data = new T[m_alloc]; + memcpy(data, other.data, m_size*sizeof(T)); + } + } + SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate &other, uint pos, T value) + : refcount(1), + m_alloc(pos + 8), + m_size(pos + 1) + { + data = new T[m_alloc]; + if (other.data) + memcpy(data, other.data, (m_size - 1)*sizeof(T)); + data[pos] = value; + } + ~SharedInternalClassDataPrivate() { delete [] data; } + + + void grow() { + if (!m_alloc) + m_alloc = 4; + T *n = new T[m_alloc * 2]; + if (data) { + memcpy(n, data, m_alloc*sizeof(T)); + delete [] data; + } + data = n; + m_alloc *= 2; + } + + uint alloc() const { return m_alloc; } + uint size() const { return m_size; } + void setSize(uint s) { m_size = s; } + + T at(uint i) { Q_ASSERT(data && i < m_alloc); return data[i]; } + void set(uint i, T t) { Q_ASSERT(data && i < m_alloc); data[i] = t; } + + void mark(MarkStack *) {} + + int refcount = 1; +private: + uint m_alloc; + uint m_size; + T *data; +}; + +template<> +struct SharedInternalClassDataPrivate<PropertyKey> { + SharedInternalClassDataPrivate(ExecutionEngine *e) : refcount(1), engine(e), data(nullptr) {} + SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate &other); + SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate &other, uint pos, PropertyKey value); + ~SharedInternalClassDataPrivate() {} + + void grow(); + uint alloc() const; + uint size() const; + void setSize(uint s); + + PropertyKey at(uint i); + void set(uint i, PropertyKey t); + + void mark(MarkStack *s); + + int refcount = 1; +private: + ExecutionEngine *engine; + Heap::MemberData *data; +}; + template <typename T> struct SharedInternalClassData { - struct Private { - Private(int alloc) - : refcount(1), - alloc(alloc), - size(0) - { data = new T [alloc]; } - ~Private() { delete [] data; } - - int refcount; - uint alloc; - uint size; - T *data; - }; + using Private = SharedInternalClassDataPrivate<T>; Private *d; - inline SharedInternalClassData() { - d = new Private(8); + inline SharedInternalClassData(ExecutionEngine *e) { + d = new Private(e); } inline SharedInternalClassData(const SharedInternalClassData &other) @@ -163,76 +249,72 @@ struct SharedInternalClassData { if (!--d->refcount) delete d; } + SharedInternalClassData &operator=(const SharedInternalClassData &other) { + ++other.d->refcount; + if (!--d->refcount) + delete d; + d = other.d; + return *this; + } void add(uint pos, T value) { - if (pos < d->size) { + if (pos < d->size()) { Q_ASSERT(d->refcount > 1); // need to detach - Private *dd = new Private(pos + 8); - memcpy(dd->data, d->data, pos*sizeof(T)); - dd->size = pos + 1; - dd->data[pos] = value; + Private *dd = new Private(*d, pos, value); if (!--d->refcount) delete d; d = dd; return; } - Q_ASSERT(pos == d->size); - if (pos == d->alloc) { - T *n = new T[d->alloc * 2]; - memcpy(n, d->data, d->alloc*sizeof(T)); - delete [] d->data; - d->data = n; - d->alloc *= 2; - } - d->data[pos] = value; - ++d->size; + Q_ASSERT(pos == d->size()); + if (pos == d->alloc()) + d->grow(); + d->setSize(d->size() + 1); + d->set(pos, value); } void set(uint pos, T value) { - Q_ASSERT(pos < d->size); + Q_ASSERT(pos < d->size()); if (d->refcount > 1) { // need to detach - Private *dd = new Private(d->alloc); - memcpy(dd->data, d->data, d->size*sizeof(T)); - dd->size = d->size; + Private *dd = new Private(*d); if (!--d->refcount) delete d; d = dd; } - d->data[pos] = value; + d->set(pos, value); } - T *constData() const { - return d->data; - } T at(uint i) const { - Q_ASSERT(i < d->size); - return d->data[i]; + Q_ASSERT(i < d->size()); + return d->at(i); } T operator[] (uint i) { - Q_ASSERT(i < d->size); - return d->data[i]; + Q_ASSERT(i < d->size()); + return d->at(i); } -private: - SharedInternalClassData &operator=(const SharedInternalClassData &other); + void mark(MarkStack *s) { d->mark(s); } }; struct InternalClassTransition { union { - Identifier *id; + PropertyKey id; const VTable *vtable; Heap::Object *prototype; }; - InternalClass *lookup; + Heap::InternalClass *lookup; int flags; enum { // range 0-0xff is reserved for attribute changes NotExtensible = 0x100, VTableChange = 0x200, - PrototypeChange = 0x201 + PrototypeChange = 0x201, + ProtoClass = 0x202, + Sealed = 0x203, + Frozen = 0x204 }; bool operator==(const InternalClassTransition &other) const @@ -242,72 +324,168 @@ struct InternalClassTransition { return id < other.id || (id == other.id && flags < other.flags); } }; -struct InternalClass : public QQmlJS::Managed { +namespace Heap { + +struct InternalClass : Base { ExecutionEngine *engine; const VTable *vtable; + quintptr protoId; // unique across the engine, gets changed whenever the proto chain changes Heap::Object *prototype; + InternalClass *parent; PropertyHash propertyTable; // id to valueIndex - SharedInternalClassData<Identifier *> nameMap; + SharedInternalClassData<PropertyKey> nameMap; SharedInternalClassData<PropertyAttributes> propertyData; typedef InternalClassTransition Transition; std::vector<Transition> transitions; InternalClassTransition &lookupOrInsertTransition(const InternalClassTransition &t); - InternalClass *m_sealed; - InternalClass *m_frozen; - uint size; bool extensible; + bool isSealed; + bool isFrozen; + bool isUsedAsProto; + void init(ExecutionEngine *engine); + void init(InternalClass *other); + void destroy(); + + Q_QML_PRIVATE_EXPORT QString keyAt(uint index) const; Q_REQUIRED_RESULT InternalClass *nonExtensible(); - Q_REQUIRED_RESULT InternalClass *changeVTable(const VTable *vt) { - if (vtable == vt) - return this; - return changeVTableImpl(vt); + + static void addMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry); + Q_REQUIRED_RESULT InternalClass *addMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry = nullptr); + Q_REQUIRED_RESULT InternalClass *changeMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry = nullptr); + static void changeMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry = nullptr); + static void removeMember(QV4::Object *object, PropertyKey identifier); + PropertyHash::Entry *findEntry(const PropertyKey id) + { + Q_ASSERT(id.isStringOrSymbol()); + + PropertyHash::Entry *e = propertyTable.lookup(id); + if (e && e->index < size) + return e; + + return nullptr; } - Q_REQUIRED_RESULT InternalClass *changePrototype(Heap::Object *proto) { - if (prototype == proto) - return this; - return changePrototypeImpl(proto); + + InternalClassEntry find(const PropertyKey id) + { + Q_ASSERT(id.isStringOrSymbol()); + + PropertyHash::Entry *e = propertyTable.lookup(id); + if (e && e->index < size) { + PropertyAttributes a = propertyData.at(e->index); + if (!a.isEmpty()) + return { e->index, e->setterIndex, a }; + } + + return { UINT_MAX, UINT_MAX, Attr_Invalid }; } - static void addMember(Object *object, String *string, PropertyAttributes data, uint *index); - Q_REQUIRED_RESULT InternalClass *addMember(String *string, PropertyAttributes data, uint *index = 0); - Q_REQUIRED_RESULT InternalClass *addMember(Identifier *identifier, PropertyAttributes data, uint *index = 0); - Q_REQUIRED_RESULT InternalClass *changeMember(Identifier *identifier, PropertyAttributes data, uint *index = 0); - static void changeMember(Object *object, String *string, PropertyAttributes data, uint *index = 0); - static void removeMember(Object *object, Identifier *id); - uint find(const String *string); - uint find(const Identifier *id) + struct IndexAndAttribute { + uint index; + PropertyAttributes attrs; + bool isValid() const { return index != UINT_MAX; } + }; + + IndexAndAttribute findValueOrGetter(const PropertyKey id) { - uint index = propertyTable.lookup(id); - if (index < size) - return index; + Q_ASSERT(id.isStringOrSymbol()); + + PropertyHash::Entry *e = propertyTable.lookup(id); + if (e && e->index < size) { + PropertyAttributes a = propertyData.at(e->index); + if (!a.isEmpty()) + return { e->index, a }; + } + + return { UINT_MAX, Attr_Invalid }; + } + + IndexAndAttribute findValueOrSetter(const PropertyKey id) + { + Q_ASSERT(id.isStringOrSymbol()); + + PropertyHash::Entry *e = propertyTable.lookup(id); + if (e && e->index < size) { + PropertyAttributes a = propertyData.at(e->index); + if (!a.isEmpty()) { + if (a.isAccessor()) { + Q_ASSERT(e->setterIndex != UINT_MAX); + return { e->setterIndex, a }; + } + return { e->index, a }; + } + } + + return { UINT_MAX, Attr_Invalid }; + } + + uint indexOfValueOrGetter(const PropertyKey id) + { + Q_ASSERT(id.isStringOrSymbol()); + + PropertyHash::Entry *e = propertyTable.lookup(id); + if (e && e->index < size) { + Q_ASSERT(!propertyData.at(e->index).isEmpty()); + return e->index; + } return UINT_MAX; } + bool verifyIndex(const PropertyKey id, uint index) + { + Q_ASSERT(id.isStringOrSymbol()); + + PropertyHash::Entry *e = propertyTable.lookup(id); + if (e && e->index < size) { + Q_ASSERT(!propertyData.at(e->index).isEmpty()); + return e->index == index; + } + + return false; + } + Q_REQUIRED_RESULT InternalClass *sealed(); Q_REQUIRED_RESULT InternalClass *frozen(); Q_REQUIRED_RESULT InternalClass *propertiesFrozen() const; - void destroy(); + Q_REQUIRED_RESULT InternalClass *asProtoClass(); + + Q_REQUIRED_RESULT InternalClass *changeVTable(const VTable *vt) { + if (vtable == vt) + return this; + return changeVTableImpl(vt); + } + Q_REQUIRED_RESULT InternalClass *changePrototype(Heap::Object *proto) { + if (prototype == proto) + return this; + return changePrototypeImpl(proto); + } + + void updateProtoUsage(Heap::Object *o); + + static void markObjects(Heap::Base *ic, MarkStack *stack); private: Q_QML_EXPORT InternalClass *changeVTableImpl(const VTable *vt); Q_QML_EXPORT InternalClass *changePrototypeImpl(Heap::Object *proto); - InternalClass *addMemberImpl(Identifier *identifier, PropertyAttributes data, uint *index); + InternalClass *addMemberImpl(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry); + + void removeChildEntry(InternalClass *child); friend struct ExecutionEngine; - InternalClass(ExecutionEngine *engine); - InternalClass(const InternalClass &other); }; -struct InternalClassPool : public QQmlJS::MemoryPool +inline +void Base::markObjects(Base *b, MarkStack *stack) { - void markObjects(MarkStack *markStack); -}; + b->internalClass->mark(stack); +} + +} } diff --git a/src/qml/jsruntime/qv4iterator.cpp b/src/qml/jsruntime/qv4iterator.cpp new file mode 100644 index 0000000000..a543565b37 --- /dev/null +++ b/src/qml/jsruntime/qv4iterator.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qv4iterator_p.h> +#include <qv4symbol_p.h> +#include <qv4engine_p.h> + +using namespace QV4; + +void IteratorPrototype::init(ExecutionEngine *engine) +{ + defineDefaultProperty(engine->symbol_iterator(), method_iterator, 0); +} + +ReturnedValue IteratorPrototype::method_iterator(const FunctionObject *, const Value *thisObject, const Value *, int) +{ + return thisObject->asReturnedValue(); +} + + +ReturnedValue IteratorPrototype::createIterResultObject(ExecutionEngine *engine, const Value &value, bool done) +{ + Scope scope(engine); + ScopedObject obj(scope, engine->newObject()); + obj->set(ScopedString(scope, engine->newString(QStringLiteral("value"))), value, Object::DoNotThrow); + obj->set(ScopedString(scope, engine->newString(QStringLiteral("done"))), Value::fromBoolean(done), Object::DoNotThrow); + return obj->asReturnedValue(); +} + diff --git a/src/qml/jsruntime/qv4iterator_p.h b/src/qml/jsruntime/qv4iterator_p.h new file mode 100644 index 0000000000..28e337d21b --- /dev/null +++ b/src/qml/jsruntime/qv4iterator_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4ITERATOR_P_H +#define QV4ITERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4arraydata_p.h" + +QT_BEGIN_NAMESPACE + + +namespace QV4 { + +enum IteratorKind { + KeyIteratorKind, + ValueIteratorKind, + KeyValueIteratorKind +}; + +struct IteratorPrototype : Object +{ + void init(ExecutionEngine *engine); + + static ReturnedValue method_iterator(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue createIterResultObject(ExecutionEngine *engine, const Value &value, bool done); +}; + +} + +QT_END_NAMESPACE + +#endif // QV4ARRAYITERATOR_P_H + diff --git a/src/qml/jsruntime/qv4jscall_p.h b/src/qml/jsruntime/qv4jscall_p.h new file mode 100644 index 0000000000..31689b1ba1 --- /dev/null +++ b/src/qml/jsruntime/qv4jscall_p.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4JSCALL_H +#define QV4JSCALL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4function_p.h" +#include "qv4functionobject_p.h" +#include "qv4context_p.h" +#include "qv4scopedvalue_p.h" +#include "qv4stackframe_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +struct JSCallData { + JSCallData(const Scope &scope, int argc = 0, const Value *argv = nullptr, const Value *thisObject = nullptr) + : scope(scope), argc(argc) + { + if (thisObject) + this->thisObject = const_cast<Value *>(thisObject); + else + this->thisObject = scope.alloc(); + if (argv) + this->args = const_cast<Value *>(argv); + else + this->args = scope.alloc(argc); + } + + JSCallData *operator->() { + return this; + } + + CallData *callData(const FunctionObject *f = nullptr) const { + int size = int(offsetof(QV4::CallData, args)/sizeof(QV4::Value)) + argc; + CallData *ptr = reinterpret_cast<CallData *>(scope.alloc<Scope::Uninitialized>(size)); + ptr->function = Encode::undefined(); + ptr->context = Encode::undefined(); + ptr->accumulator = Encode::undefined(); + ptr->thisObject = thisObject->asReturnedValue(); + ptr->newTarget = Encode::undefined(); + ptr->setArgc(argc); + if (argc) + memcpy(ptr->args, args, argc*sizeof(Value)); + if (f) + ptr->function = f->asReturnedValue(); + return ptr; + } + const Scope &scope; + int argc; + Value *args; + Value *thisObject; +}; + +inline +ReturnedValue FunctionObject::callAsConstructor(const JSCallData &data) const +{ + return callAsConstructor(data.args, data.argc, this); +} + +inline +ReturnedValue FunctionObject::call(const JSCallData &data) const +{ + return call(data.thisObject, data.args, data.argc); +} + + +struct ScopedStackFrame { + Scope &scope; + CppStackFrame frame; + + ScopedStackFrame(Scope &scope, Heap::ExecutionContext *context) + : scope(scope) + { + frame.parent = scope.engine->currentStackFrame; + if (!context) + return; + frame.jsFrame = reinterpret_cast<CallData *>(scope.alloc(sizeof(CallData)/sizeof(Value))); + frame.jsFrame->context = context; + frame.v4Function = frame.parent ? frame.parent->v4Function : nullptr; + scope.engine->currentStackFrame = &frame; + } + ~ScopedStackFrame() { + scope.engine->currentStackFrame = frame.parent; + } +}; + +} + +QT_END_NAMESPACE + +#endif // QV4JSCALL_H diff --git a/src/qml/jsruntime/qv4jsonobject.cpp b/src/qml/jsruntime/qv4jsonobject.cpp index 0f021c8bd0..936c032fad 100644 --- a/src/qml/jsruntime/qv4jsonobject.cpp +++ b/src/qml/jsruntime/qv4jsonobject.cpp @@ -46,6 +46,8 @@ #include <qv4runtime_p.h> #include <qv4variantobject_p.h> #include "qv4string_p.h" +#include "qv4jscall_p.h" +#include <qv4symbol_p.h> #include <qstack.h> #include <qstringlist.h> @@ -259,11 +261,12 @@ bool JsonParser::parseMember(Object *o) if (!parseValue(val)) return false; - ScopedString s(scope, engine->newIdentifier(key)); - uint idx = s->asArrayIndex(); - if (idx < UINT_MAX) { - o->putIndexed(idx, val); + ScopedString s(scope, engine->newString(key)); + PropertyKey skey = s->toPropertyKey(); + if (skey.isArrayIndex()) { + o->put(skey.asArrayIndex(), val); } else { + // avoid trouble with properties named __proto__ o->insertMember(s, val); } @@ -337,7 +340,7 @@ bool JsonParser::parseValue(Value *val) if (*json++ == 'u' && *json++ == 'l' && *json++ == 'l') { - *val = Primitive::nullValue(); + *val = Value::nullValue(); DEBUG << "value: null"; END; return true; @@ -352,7 +355,7 @@ bool JsonParser::parseValue(Value *val) if (*json++ == 'r' && *json++ == 'u' && *json++ == 'e') { - *val = Primitive::fromBoolean(true); + *val = Value::fromBoolean(true); DEBUG << "value: true"; END; return true; @@ -368,7 +371,7 @@ bool JsonParser::parseValue(Value *val) *json++ == 'l' && *json++ == 's' && *json++ == 'e') { - *val = Primitive::fromBoolean(false); + *val = Value::fromBoolean(false); DEBUG << "value: false"; END; return true; @@ -476,7 +479,7 @@ bool JsonParser::parseNumber(Value *val) bool ok; int n = number.toInt(&ok); if (ok && n < (1<<25) && n > -(1<<25)) { - *val = Primitive::fromInt32(n); + *val = Value::fromInt32(n); END; return true; } @@ -491,7 +494,7 @@ bool JsonParser::parseNumber(Value *val) return false; } - * val = Primitive::fromDouble(d); + * val = Value::fromDouble(d); END; return true; @@ -633,10 +636,10 @@ struct Stringify return false; } - Stringify(ExecutionEngine *e) : v4(e), replacerFunction(0), propertyList(0), propertyListSize(0) {} + Stringify(ExecutionEngine *e) : v4(e), replacerFunction(nullptr), propertyList(nullptr), propertyListSize(0) {} QString Str(const QString &key, const Value &v); - QString JA(ArrayObject *a); + QString JA(Object *a); QString JO(Object *o); QString makeMember(const QString &key, const Value &v); @@ -689,61 +692,61 @@ static QString quote(const QString &str) QString Stringify::Str(const QString &key, const Value &v) { Scope scope(v4); - scope.result = v; - ScopedObject o(scope, scope.result); + ScopedValue value(scope, v); + ScopedObject o(scope, value); if (o) { ScopedString s(scope, v4->newString(QStringLiteral("toJSON"))); ScopedFunctionObject toJSON(scope, o->get(s)); if (!!toJSON) { - ScopedCallData callData(scope, 1); - callData->thisObject = scope.result; - callData->args[0] = v4->newString(key); - toJSON->call(scope, callData); + JSCallData jsCallData(scope, 1); + *jsCallData->thisObject = value; + jsCallData->args[0] = v4->newString(key); + value = toJSON->call(jsCallData); } } if (replacerFunction) { ScopedObject holder(scope, v4->newObject()); - holder->put(scope.engine->id_empty(), scope.result); - ScopedCallData callData(scope, 2); - callData->args[0] = v4->newString(key); - callData->args[1] = scope.result; - callData->thisObject = holder; - replacerFunction->call(scope, callData); + holder->put(scope.engine->id_empty(), value); + JSCallData jsCallData(scope, 2); + jsCallData->args[0] = v4->newString(key); + jsCallData->args[1] = value; + *jsCallData->thisObject = holder; + value = replacerFunction->call(jsCallData); } - o = scope.result.asReturnedValue(); + o = value->asReturnedValue(); if (o) { if (NumberObject *n = o->as<NumberObject>()) - scope.result = Encode(n->value()); + value = Encode(n->value()); else if (StringObject *so = o->as<StringObject>()) - scope.result = so->d()->string; + value = so->d()->string; else if (BooleanObject *b = o->as<BooleanObject>()) - scope.result = Encode(b->value()); + value = Encode(b->value()); } - if (scope.result.isNull()) + if (value->isNull()) return QStringLiteral("null"); - if (scope.result.isBoolean()) - return scope.result.booleanValue() ? QStringLiteral("true") : QStringLiteral("false"); - if (String *s = scope.result.stringValue()) - return quote(s->toQString()); - - if (scope.result.isNumber()) { - double d = scope.result.toNumber(); - return std::isfinite(d) ? scope.result.toQString() : QStringLiteral("null"); + if (value->isBoolean()) + return value->booleanValue() ? QStringLiteral("true") : QStringLiteral("false"); + if (value->isString()) + return quote(value->stringValue()->toQString()); + + if (value->isNumber()) { + double d = value->toNumber(); + return std::isfinite(d) ? value->toQString() : QStringLiteral("null"); } - if (const QV4::VariantObject *v = scope.result.as<QV4::VariantObject>()) { - return v->d()->data().toString(); + if (const QV4::VariantObject *v = value->as<QV4::VariantObject>()) { + return quote(v->d()->data().toString()); } - o = scope.result.asReturnedValue(); + o = value->asReturnedValue(); if (o) { if (!o->as<FunctionObject>()) { - if (o->as<ArrayObject>()) { - return JA(static_cast<ArrayObject *>(o.getPointer())); + if (o->isArrayLike()) { + return JA(o.getPointer()); } else { return JO(o); } @@ -826,7 +829,7 @@ QString Stringify::JO(Object *o) return result; } -QString Stringify::JA(ArrayObject *a) +QString Stringify::JA(Object *a) { if (stackContains(a)) { v4->throwTypeError(); @@ -845,7 +848,7 @@ QString Stringify::JA(ArrayObject *a) ScopedValue v(scope); for (uint i = 0; i < len; ++i) { bool exists; - v = a->getIndexed(i, &exists); + v = a->get(i, &exists); if (!exists) { partial += QStringLiteral("null"); continue; @@ -880,31 +883,36 @@ void Heap::JsonObject::init() o->defineDefaultProperty(QStringLiteral("parse"), QV4::JsonObject::method_parse, 2); o->defineDefaultProperty(QStringLiteral("stringify"), QV4::JsonObject::method_stringify, 3); + ScopedString json(scope, scope.engine->newString(QStringLiteral("JSON"))); + o->defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), json); } -void JsonObject::method_parse(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue JsonObject::method_parse(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedValue v(scope, callData->argument(0)); - QString jtext = v->toQString(); + ExecutionEngine *v4 = b->engine(); + QString jtext; + if (argc > 0) + jtext = argv[0].toQString(); DEBUG << "parsing source = " << jtext; - JsonParser parser(scope.engine, jtext.constData(), jtext.length()); + JsonParser parser(v4, jtext.constData(), jtext.length()); QJsonParseError error; - ScopedValue result(scope, parser.parse(&error)); + ReturnedValue result = parser.parse(&error); if (error.error != QJsonParseError::NoError) { DEBUG << "parse error" << error.errorString(); - RETURN_RESULT(scope.engine->throwSyntaxError(QStringLiteral("JSON.parse: Parse error"))); + RETURN_RESULT(v4->throwSyntaxError(QStringLiteral("JSON.parse: Parse error"))); } - scope.result = result; + return result; } -void JsonObject::method_stringify(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue JsonObject::method_stringify(const FunctionObject *b, const Value *, const Value *argv, int argc) { + Scope scope(b); Stringify stringify(scope.engine); - ScopedObject o(scope, callData->argument(1)); + ScopedObject o(scope, argc > 1 ? argv[1] : Value::undefinedValue()); if (o) { stringify.replacerFunction = o->as<FunctionObject>(); if (o->isArrayObject()) { @@ -912,15 +920,15 @@ void JsonObject::method_stringify(const BuiltinFunction *, Scope &scope, CallDat stringify.propertyList = static_cast<QV4::String *>(scope.alloc(arrayLen)); for (uint i = 0; i < arrayLen; ++i) { Value *v = stringify.propertyList + i; - *v = o->getIndexed(i); + *v = o->get(i); if (v->as<NumberObject>() || v->as<StringObject>() || v->isNumber()) *v = v->toString(scope.engine); if (!v->isString()) { - v->setM(0); + v->setM(nullptr); } else { for (uint j = 0; j <i; ++j) { if (stringify.propertyList[j].m() == v->m()) { - v->setM(0); + v->setM(nullptr); break; } } @@ -929,7 +937,7 @@ void JsonObject::method_stringify(const BuiltinFunction *, Scope &scope, CallDat } } - ScopedValue s(scope, callData->argument(2)); + ScopedValue s(scope, argc > 2 ? argv[2] : Value::undefinedValue()); if (NumberObject *n = s->as<NumberObject>()) s = Encode(n->value()); else if (StringObject *so = s->as<StringObject>()) @@ -942,11 +950,11 @@ void JsonObject::method_stringify(const BuiltinFunction *, Scope &scope, CallDat } - ScopedValue arg0(scope, callData->argument(0)); + ScopedValue arg0(scope, argc ? argv[0] : Value::undefinedValue()); QString result = stringify.Str(QString(), arg0); if (result.isEmpty() || scope.engine->hasException) RETURN_UNDEFINED(); - scope.result = scope.engine->newString(result); + return Encode(scope.engine->newString(result)); } @@ -1074,7 +1082,7 @@ QJsonArray JsonObject::toJsonArray(const ArrayObject *a, V4ObjectSet &visitedObj ScopedValue v(scope); quint32 length = a->getLength(); for (quint32 i = 0; i < length; ++i) { - v = a->getIndexed(i); + v = a->get(i); if (v->as<FunctionObject>()) v = Encode::null(); result.append(toJsonValue(v, visitedObjects)); diff --git a/src/qml/jsruntime/qv4jsonobject_p.h b/src/qml/jsruntime/qv4jsonobject_p.h index a73ce1c74e..7d9f204910 100644 --- a/src/qml/jsruntime/qv4jsonobject_p.h +++ b/src/qml/jsruntime/qv4jsonobject_p.h @@ -88,8 +88,8 @@ private: typedef QSet<ObjectItem> V4ObjectSet; public: - static void method_parse(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_stringify(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_parse(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_stringify(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); static ReturnedValue fromJsonValue(ExecutionEngine *engine, const QJsonValue &value); static ReturnedValue fromJsonObject(ExecutionEngine *engine, const QJsonObject &object); diff --git a/src/qml/jsruntime/qv4lookup.cpp b/src/qml/jsruntime/qv4lookup.cpp index d943ae1340..1b6cdcbd14 100644 --- a/src/qml/jsruntime/qv4lookup.cpp +++ b/src/qml/jsruntime/qv4lookup.cpp @@ -38,324 +38,178 @@ ****************************************************************************/ #include "qv4lookup_p.h" #include "qv4functionobject_p.h" -#include "qv4scopedvalue_p.h" +#include "qv4jscall_p.h" #include "qv4string_p.h" +#include <private/qv4identifiertable_p.h> QT_BEGIN_NAMESPACE using namespace QV4; -ReturnedValue Lookup::lookup(const Value &thisObject, Object *o, PropertyAttributes *attrs) +void Lookup::resolveProtoGetter(PropertyKey name, const Heap::Object *proto) { - ExecutionEngine *engine = o->engine(); - Identifier *name = engine->current->compilationUnit->runtimeStrings[nameIndex]->identifier; - int i = 0; - Heap::Object *obj = o->d(); - while (i < Size && obj) { - classList[i] = obj->internalClass; - - index = obj->internalClass->find(name); - if (index != UINT_MAX) { - level = i; - *attrs = obj->internalClass->propertyData.at(index); - const Value *v = obj->propertyData(index); - return !attrs->isAccessor() ? v->asReturnedValue() : Object::getValue(thisObject, *v, *attrs); - } - - obj = obj->prototype(); - ++i; - } - level = Size; - - while (obj) { - index = obj->internalClass->find(name); - if (index != UINT_MAX) { - *attrs = obj->internalClass->propertyData.at(index); - const Value *v = obj->propertyData(index); - return !attrs->isAccessor() ? v->asReturnedValue() : Object::getValue(thisObject, *v, *attrs); - } - - obj = obj->prototype(); - } - return Primitive::emptyValue().asReturnedValue(); -} - -ReturnedValue Lookup::lookup(const Object *thisObject, PropertyAttributes *attrs) -{ - Heap::Object *obj = thisObject->d(); - ExecutionEngine *engine = thisObject->engine(); - Identifier *name = engine->current->compilationUnit->runtimeStrings[nameIndex]->identifier; - int i = 0; - while (i < Size && obj) { - classList[i] = obj->internalClass; - - index = obj->internalClass->find(name); - if (index != UINT_MAX) { - level = i; - *attrs = obj->internalClass->propertyData.at(index); - const Value *v = obj->propertyData(index); - return !attrs->isAccessor() ? v->asReturnedValue() : thisObject->getValue(*v, *attrs); - } - - obj = obj->prototype(); - ++i; - } - level = Size; - - while (obj) { - index = obj->internalClass->find(name); - if (index != UINT_MAX) { - *attrs = obj->internalClass->propertyData.at(index); - const Value *v = obj->propertyData(index); - return !attrs->isAccessor() ? v->asReturnedValue() : thisObject->getValue(*v, *attrs); - } - - obj = obj->prototype(); - } - return Primitive::emptyValue().asReturnedValue(); -} - -ReturnedValue Lookup::indexedGetterGeneric(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index) -{ - uint idx; - if (object.isObject() && index.asArrayIndex(idx)) { - l->indexedGetter = indexedGetterObjectInt; - return indexedGetterObjectInt(l, engine, object, index); - } - return indexedGetterFallback(l, engine, object, index); -} - -ReturnedValue Lookup::indexedGetterFallback(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index) -{ - Q_UNUSED(l); - Scope scope(engine); - uint idx = 0; - bool isInt = index.asArrayIndex(idx); - - ScopedObject o(scope, object); - if (!o) { - if (isInt) { - if (const String *str = object.as<String>()) { - if (idx >= (uint)str->toQString().length()) { - return Encode::undefined(); - } - const QString s = str->toQString().mid(idx, 1); - return scope.engine->newString(s)->asReturnedValue(); + while (proto) { + auto index = proto->internalClass->findValueOrGetter(name); + if (index.isValid()) { + PropertyAttributes attrs = index.attrs; + protoLookup.data = proto->propertyData(index.index); + if (attrs.isData()) { + getter = getterProto; + } else { + getter = getterProtoAccessor; } - } - - if (object.isNullOrUndefined()) { - QString message = QStringLiteral("Cannot read property '%1' of %2").arg(index.toQStringNoThrow()).arg(object.toQStringNoThrow()); - return engine->throwTypeError(message); - } - - o = RuntimeHelpers::convertToObject(scope.engine, object); - if (!o) // type error - return Encode::undefined(); - } - - if (isInt) { - if (o->d()->arrayData && !o->d()->arrayData->attrs) { - ScopedValue v(scope, Scoped<ArrayData>(scope, o->arrayData())->get(idx)); - if (!v->isEmpty()) - return v->asReturnedValue(); - } - - return o->getIndexed(idx); - } - - ScopedString name(scope, index.toString(scope.engine)); - if (scope.hasException()) - return Encode::undefined(); - return o->get(name); - -} - - -ReturnedValue Lookup::indexedGetterObjectInt(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index) -{ - uint idx; - if (index.asArrayIndex(idx)) { - if (Heap::Base *b = object.heapObject()) { - if (b->vtable()->isObject) { - Heap::Object *o = static_cast<Heap::Object *>(b); - if (o->arrayData && o->arrayData->type == Heap::ArrayData::Simple) { - Heap::SimpleArrayData *s = o->arrayData.cast<Heap::SimpleArrayData>(); - if (idx < s->values.size) - if (!s->data(idx).isEmpty()) - return s->data(idx).asReturnedValue(); - } - } - } - } - - return indexedGetterFallback(l, engine, object, index); -} - -void Lookup::indexedSetterGeneric(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index, const Value &v) -{ - if (Object *o = object.objectValue()) { - uint idx; - if (o->d()->arrayData && o->d()->arrayData->type == Heap::ArrayData::Simple && index.asArrayIndex(idx)) { - l->indexedSetter = indexedSetterObjectInt; - indexedSetterObjectInt(l, engine, object, index, v); return; } + proto = proto->prototype(); } - indexedSetterFallback(l, engine, object, index, v); + // ### put in a getterNotFound! + getter = getterFallback; } -void Lookup::indexedSetterFallback(Lookup *, ExecutionEngine *engine, const Value &object, const Value &index, const Value &value) +ReturnedValue Lookup::resolveGetter(ExecutionEngine *engine, const Object *object) { - Scope scope(engine); - ScopedObject o(scope, object.toObject(scope.engine)); - if (scope.engine->hasException) - return; - - uint idx; - if (index.asArrayIndex(idx)) { - if (o->d()->arrayData && o->d()->arrayData->type == Heap::ArrayData::Simple) { - Heap::SimpleArrayData *s = o->d()->arrayData.cast<Heap::SimpleArrayData>(); - if (idx < s->values.size) { - s->setData(engine, idx, value); - return; - } - } - o->putIndexed(idx, value); - return; + Heap::Object *obj = object->d(); + PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + if (name.isArrayIndex()) { + indexedLookup.index = name.asArrayIndex(); + getter = getterIndexed; + return getter(this, engine, *object); } - ScopedString name(scope, index.toString(scope.engine)); - o->put(name, value); -} - -void Lookup::indexedSetterObjectInt(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index, const Value &v) -{ - uint idx; - if (index.asArrayIndex(idx)) { - if (Heap::Base *b = object.heapObject()) { - if (b->vtable()->isObject) { - Heap::Object *o = static_cast<Heap::Object *>(b); - if (o->arrayData && o->arrayData->type == Heap::ArrayData::Simple) { - Heap::SimpleArrayData *s = o->arrayData.cast<Heap::SimpleArrayData>(); - if (idx < s->values.size) { - s->setData(engine, idx, v); - return; - } - } + auto index = obj->internalClass->findValueOrGetter(name); + if (index.isValid()) { + PropertyAttributes attrs = index.attrs; + uint nInline = obj->vtable()->nInlineProperties; + if (attrs.isData()) { + if (index.index < obj->vtable()->nInlineProperties) { + index.index += obj->vtable()->inlinePropertyOffset; + getter = getter0Inline; + } else { + index.index -= nInline; + getter = getter0MemberData; } + } else { + getter = getterAccessor; } + objectLookup.ic = obj->internalClass; + objectLookup.offset = index.index; + return getter(this, engine, *object); } - indexedSetterFallback(l, engine, object, index, v); + + protoLookup.protoId = obj->internalClass->protoId; + resolveProtoGetter(name, obj->prototype()); + return getter(this, engine, *object); } -ReturnedValue Lookup::getterGeneric(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::resolvePrimitiveGetter(ExecutionEngine *engine, const Value &object) { - if (const Object *o = object.as<Object>()) - return o->getLookup(l); - - Object *proto; - switch (object.type()) { + primitiveLookup.type = object.type(); + switch (primitiveLookup.type) { case Value::Undefined_Type: case Value::Null_Type: return engine->throwTypeError(); case Value::Boolean_Type: - proto = engine->booleanPrototype(); + primitiveLookup.proto = engine->booleanPrototype()->d(); break; case Value::Managed_Type: { - Q_ASSERT(object.isString()); - proto = engine->stringPrototype(); + // ### Should move this over to the Object path, as strings also have an internalClass + Q_ASSERT(object.isStringOrSymbol()); + primitiveLookup.proto = static_cast<const Managed &>(object).internalClass()->prototype; + Q_ASSERT(primitiveLookup.proto); Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[l->nameIndex]); - if (name->equals(engine->id_length())) { + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + if (object.isString() && name->equals(engine->id_length())) { // special case, as the property is on the object itself - l->getter = stringLengthGetter; - return stringLengthGetter(l, engine, object); + getter = stringLengthGetter; + return stringLengthGetter(this, engine, object); } break; } case Value::Integer_Type: default: // Number - proto = engine->numberPrototype(); + primitiveLookup.proto = engine->numberPrototype()->d(); } - PropertyAttributes attrs; - ReturnedValue v = l->lookup(object, proto, &attrs); - if (v != Primitive::emptyValue().asReturnedValue()) { - l->type = object.type(); - l->proto = proto->d(); - if (attrs.isData()) { - if (l->level == 0) { - uint nInline = l->proto->vtable()->nInlineProperties; - if (l->index < nInline) - l->getter = Lookup::primitiveGetter0Inline; - else { - l->index -= nInline; - l->getter = Lookup::primitiveGetter0MemberData; - } - } else if (l->level == 1) - l->getter = Lookup::primitiveGetter1; - return v; - } else { - if (l->level == 0) - l->getter = Lookup::primitiveGetterAccessor0; - else if (l->level == 1) - l->getter = Lookup::primitiveGetterAccessor1; - return v; - } + PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + protoLookup.protoId = primitiveLookup.proto->internalClass->protoId; + resolveProtoGetter(name, primitiveLookup.proto); + + if (getter == getterProto) + getter = primitiveGetterProto; + else if (getter == getterProtoAccessor) + getter = primitiveGetterAccessor; + return getter(this, engine, object); +} + +ReturnedValue Lookup::resolveGlobalGetter(ExecutionEngine *engine) +{ + Object *o = engine->globalObject; + PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + protoLookup.protoId = o->internalClass()->protoId; + resolveProtoGetter(name, o->d()); + + if (getter == getterProto) + globalGetter = globalGetterProto; + else if (getter == getterProtoAccessor) + globalGetter = globalGetterProtoAccessor; + else { + globalGetter = globalGetterGeneric; + Scope scope(engine); + ScopedString n(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + return engine->throwReferenceError(n); } + return globalGetter(this, engine); +} - return Encode::undefined(); +ReturnedValue Lookup::getterGeneric(Lookup *l, ExecutionEngine *engine, const Value &object) +{ + if (const Object *o = object.as<Object>()) + return l->resolveGetter(engine, o); + return l->resolvePrimitiveGetter(engine, object); } ReturnedValue Lookup::getterTwoClasses(Lookup *l, ExecutionEngine *engine, const Value &object) { - Lookup l1 = *l; - - if (l1.getter == Lookup::getter0MemberData || l1.getter == Lookup::getter0Inline || l1.getter == Lookup::getter1) { - if (const Object *o = object.as<Object>()) { - ReturnedValue v = o->getLookup(l); - Lookup l2 = *l; - - if (l2.index != UINT_MAX) { - if (l1.getter != Lookup::getter0Inline) { - if (l2.getter == Lookup::getter0Inline || - (l1.getter != Lookup::getter0MemberData && l2.getter == Lookup::getter0MemberData)) - // sort the better getter first - qSwap(l1, l2); - } - - l->classList[0] = l1.classList[0]; - l->classList[1] = l1.classList[1]; - l->classList[2] = l2.classList[0]; - l->classList[3] = l2.classList[1]; - l->index = l1.index; - l->index2 = l2.index; - - if (l1.getter == Lookup::getter0Inline) { - if (l2.getter == Lookup::getter0Inline) - l->getter = Lookup::getter0Inlinegetter0Inline; - else if (l2.getter == Lookup::getter0MemberData) - l->getter = Lookup::getter0Inlinegetter0MemberData; - else if (l2.getter == Lookup::getter1) - l->getter = Lookup::getter0Inlinegetter1; - else - Q_UNREACHABLE(); - } else if (l1.getter == Lookup::getter0MemberData) { - if (l2.getter == Lookup::getter0MemberData) - l->getter = Lookup::getter0MemberDatagetter0MemberData; - else if (l2.getter == Lookup::getter1) - l->getter = Lookup::getter0MemberDatagetter1; - else - Q_UNREACHABLE(); - } else { - Q_ASSERT(l1.getter == Lookup::getter1 && l2.getter == Lookup::getter1); - l->getter = Lookup::getter1getter1; - } - return v; - } + if (const Object *o = object.as<Object>()) { + Lookup first = *l; + Lookup second = *l; + + ReturnedValue result = second.resolveGetter(engine, o); + + if (first.getter == getter0Inline && (second.getter == getter0Inline || second.getter == getter0MemberData)) { + l->objectLookupTwoClasses.ic = first.objectLookup.ic; + l->objectLookupTwoClasses.ic2 = second.objectLookup.ic; + l->objectLookupTwoClasses.offset = first.objectLookup.offset; + l->objectLookupTwoClasses.offset2 = second.objectLookup.offset; + l->getter = second.getter == getter0Inline ? getter0Inlinegetter0Inline : getter0Inlinegetter0MemberData; + return result; } + if (first.getter == getter0MemberData && (second.getter == getter0Inline || second.getter == getter0MemberData)) { + l->objectLookupTwoClasses.ic = second.objectLookup.ic; + l->objectLookupTwoClasses.ic2 = first.objectLookup.ic; + l->objectLookupTwoClasses.offset = second.objectLookup.offset; + l->objectLookupTwoClasses.offset2 = first.objectLookup.offset; + l->getter = second.getter == getter0Inline ? getter0Inlinegetter0MemberData : getter0MemberDatagetter0MemberData; + return result; + } + if (first.getter == getterProto && second.getter == getterProto) { + l->protoLookupTwoClasses.protoId = first.protoLookup.protoId; + l->protoLookupTwoClasses.protoId2 = second.protoLookup.protoId; + l->protoLookupTwoClasses.data = first.protoLookup.data; + l->protoLookupTwoClasses.data2 = second.protoLookup.data; + l->getter = getterProtoTwoClasses; + return result; + } + if (first.getter == getterProtoAccessor && second.getter == getterProtoAccessor) { + l->protoLookupTwoClasses.protoId = first.protoLookup.protoId; + l->protoLookupTwoClasses.protoId2 = second.protoLookup.protoId; + l->protoLookupTwoClasses.data = first.protoLookup.data; + l->protoLookupTwoClasses.data2 = second.protoLookup.data; + l->getter = getterProtoAccessorTwoClasses; + return result; + } + } l->getter = getterFallback; @@ -368,7 +222,7 @@ ReturnedValue Lookup::getterFallback(Lookup *l, ExecutionEngine *engine, const V QV4::ScopedObject o(scope, object.toObject(scope.engine)); if (!o) return Encode::undefined(); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[l->nameIndex]); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); return o->get(name); } @@ -378,8 +232,8 @@ ReturnedValue Lookup::getter0MemberData(Lookup *l, ExecutionEngine *engine, cons // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass) - return o->memberData->values.data()[l->index].asReturnedValue(); + if (l->objectLookup.ic == o->internalClass) + return o->memberData->values.data()[l->objectLookup.offset].asReturnedValue(); } return getterTwoClasses(l, engine, object); } @@ -390,53 +244,37 @@ ReturnedValue Lookup::getter0Inline(Lookup *l, ExecutionEngine *engine, const Va // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass) - return o->inlinePropertyData(l->index)->asReturnedValue(); + if (l->objectLookup.ic == o->internalClass) + return o->inlinePropertyDataWithOffset(l->objectLookup.offset)->asReturnedValue(); } return getterTwoClasses(l, engine, object); } -ReturnedValue Lookup::getter1(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::getterProto(Lookup *l, ExecutionEngine *engine, const Value &object) { // we can safely cast to a QV4::Object here. If object is actually a string, // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass && l->classList[1] == l->proto->internalClass) - return l->proto->propertyData(l->index)->asReturnedValue(); + if (l->protoLookup.protoId == o->internalClass->protoId) + return l->protoLookup.data->asReturnedValue(); } return getterTwoClasses(l, engine, object); } -ReturnedValue Lookup::getter2(Lookup *l, ExecutionEngine *engine, const Value &object) -{ - // we can safely cast to a QV4::Object here. If object is actually a string, - // the internal class won't match - Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); - if (o) { - if (l->classList[0] == o->internalClass) { - Q_ASSERT(l->proto == o->prototype()); - if (l->classList[1] == l->proto->internalClass) { - Heap::Object *p = l->proto->prototype(); - if (l->classList[2] == p->internalClass) - return p->propertyData(l->index)->asReturnedValue(); - } - } - } - l->getter = getterFallback; - return getterFallback(l, engine, object); -} - ReturnedValue Lookup::getter0Inlinegetter0Inline(Lookup *l, ExecutionEngine *engine, const Value &object) { // we can safely cast to a QV4::Object here. If object is actually a string, // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass) - return o->inlinePropertyData(l->index)->asReturnedValue(); - if (l->classList[2] == o->internalClass) - return o->inlinePropertyData(l->index2)->asReturnedValue(); + if (l->objectLookupTwoClasses.ic == o->internalClass) + return o->inlinePropertyDataWithOffset(l->objectLookupTwoClasses.offset)->asReturnedValue(); + if (l->objectLookupTwoClasses.ic2 == o->internalClass) + return o->inlinePropertyDataWithOffset(l->objectLookupTwoClasses.offset2)->asReturnedValue(); + Value obj = Value::fromHeapObject(o); + Value str = Value::fromHeapObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); + return static_cast<Object &>(obj).get(&static_cast<String &>(str)); } l->getter = getterFallback; return getterFallback(l, engine, object); @@ -448,10 +286,13 @@ ReturnedValue Lookup::getter0Inlinegetter0MemberData(Lookup *l, ExecutionEngine // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass) - return o->inlinePropertyData(l->index)->asReturnedValue(); - if (l->classList[2] == o->internalClass) - return o->memberData->values.data()[l->index2].asReturnedValue(); + if (l->objectLookupTwoClasses.ic == o->internalClass) + return o->inlinePropertyDataWithOffset(l->objectLookupTwoClasses.offset)->asReturnedValue(); + if (l->objectLookupTwoClasses.ic2 == o->internalClass) + return o->memberData->values.data()[l->objectLookupTwoClasses.offset2].asReturnedValue(); + Value obj = Value::fromHeapObject(o); + Value str = Value::fromHeapObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); + return static_cast<Object &>(obj).get(&static_cast<String &>(str)); } l->getter = getterFallback; return getterFallback(l, engine, object); @@ -463,206 +304,130 @@ ReturnedValue Lookup::getter0MemberDatagetter0MemberData(Lookup *l, ExecutionEng // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass) - return o->memberData->values.data()[l->index].asReturnedValue(); - if (l->classList[2] == o->internalClass) - return o->memberData->values.data()[l->index2].asReturnedValue(); + if (l->objectLookupTwoClasses.ic == o->internalClass) + return o->memberData->values.data()[l->objectLookupTwoClasses.offset].asReturnedValue(); + if (l->objectLookupTwoClasses.ic2 == o->internalClass) + return o->memberData->values.data()[l->objectLookupTwoClasses.offset2].asReturnedValue(); + Value obj = Value::fromHeapObject(o); + Value str = Value::fromHeapObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); + return static_cast<Object &>(obj).get(&static_cast<String &>(str)); } l->getter = getterFallback; return getterFallback(l, engine, object); } -ReturnedValue Lookup::getter0Inlinegetter1(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::getterProtoTwoClasses(Lookup *l, ExecutionEngine *engine, const Value &object) { // we can safely cast to a QV4::Object here. If object is actually a string, // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass) - return o->inlinePropertyData(l->index)->asReturnedValue(); - if (l->classList[2] == o->internalClass && l->classList[3] == o->prototype()->internalClass) - return o->prototype()->propertyData(l->index2)->asReturnedValue(); + if (l->protoLookupTwoClasses.protoId == o->internalClass->protoId) + return l->protoLookupTwoClasses.data->asReturnedValue(); + if (l->protoLookupTwoClasses.protoId2 == o->internalClass->protoId) + return l->protoLookupTwoClasses.data2->asReturnedValue(); + Value obj = Value::fromHeapObject(o); + Value str = Value::fromHeapObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); + return static_cast<Object &>(obj).get(&static_cast<String &>(str)); } l->getter = getterFallback; return getterFallback(l, engine, object); } -ReturnedValue Lookup::getter0MemberDatagetter1(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::getterAccessor(Lookup *l, ExecutionEngine *engine, const Value &object) { // we can safely cast to a QV4::Object here. If object is actually a string, // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass) - return o->memberData->values.data()[l->index].asReturnedValue(); - if (l->classList[2] == o->internalClass && l->classList[3] == o->prototype()->internalClass) - return o->prototype()->propertyData(l->index2)->asReturnedValue(); - } - l->getter = getterFallback; - return getterFallback(l, engine, object); -} + if (l->objectLookup.ic == o->internalClass) { + const Value *getter = o->propertyData(l->objectLookup.offset); + if (!getter->isFunctionObject()) // ### catch at resolve time + return Encode::undefined(); -ReturnedValue Lookup::getter1getter1(Lookup *l, ExecutionEngine *engine, const Value &object) -{ - // we can safely cast to a QV4::Object here. If object is actually a string, - // the internal class won't match - Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); - if (o) { - if (l->classList[0] == o->internalClass && - l->classList[1] == o->prototype()->internalClass) - return o->prototype()->propertyData(l->index)->asReturnedValue(); - if (l->classList[2] == o->internalClass && - l->classList[3] == o->prototype()->internalClass) - return o->prototype()->propertyData(l->index2)->asReturnedValue(); - return getterFallback(l, engine, object); + return static_cast<const FunctionObject *>(getter)->call(&object, nullptr, 0); + } } l->getter = getterFallback; return getterFallback(l, engine, object); } - -ReturnedValue Lookup::getterAccessor0(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::getterProtoAccessor(Lookup *l, ExecutionEngine *engine, const Value &object) { // we can safely cast to a QV4::Object here. If object is actually a string, // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); - if (o) { - if (l->classList[0] == o->internalClass) { - Scope scope(o->internalClass->engine); - ScopedFunctionObject getter(scope, o->propertyData(l->index + Object::GetterOffset)); - if (!getter) - return Encode::undefined(); + if (o && l->protoLookup.protoId == o->internalClass->protoId) { + const Value *getter = l->protoLookup.data; + if (!getter->isFunctionObject()) // ### catch at resolve time + return Encode::undefined(); - ScopedCallData callData(scope, 0); - callData->thisObject = object; - getter->call(scope, callData); - return scope.result.asReturnedValue(); - } + return static_cast<const FunctionObject *>(getter)->call(&object, nullptr, 0); } - l->getter = getterFallback; - return getterFallback(l, engine, object); + return getterTwoClasses(l, engine, object); } -ReturnedValue Lookup::getterAccessor1(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::getterProtoAccessorTwoClasses(Lookup *l, ExecutionEngine *engine, const Value &object) { // we can safely cast to a QV4::Object here. If object is actually a string, // the internal class won't match Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o) { - if (l->classList[0] == o->internalClass && - l->classList[1] == l->proto->internalClass) { - Scope scope(o->internalClass->engine); - ScopedFunctionObject getter(scope, o->prototype()->propertyData(l->index + Object::GetterOffset)); - if (!getter) + const Value *getter = nullptr; + if (l->protoLookupTwoClasses.protoId == o->internalClass->protoId) + getter = l->protoLookupTwoClasses.data; + else if (l->protoLookupTwoClasses.protoId2 == o->internalClass->protoId) + getter = l->protoLookupTwoClasses.data2; + if (getter) { + if (!getter->isFunctionObject()) // ### catch at resolve time return Encode::undefined(); - ScopedCallData callData(scope, 0); - callData->thisObject = object; - getter->call(scope, callData); - return scope.result.asReturnedValue(); + return static_cast<const FunctionObject *>(getter)->call(&object, nullptr, 0); } } l->getter = getterFallback; return getterFallback(l, engine, object); } -ReturnedValue Lookup::getterAccessor2(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::getterIndexed(Lookup *l, ExecutionEngine *engine, const Value &object) { - // we can safely cast to a QV4::Object here. If object is actually a string, - // the internal class won't match - Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); + Object *o = object.objectValue(); if (o) { - if (l->classList[0] == o->internalClass) { - Q_ASSERT(o->prototype() == l->proto); - if (l->classList[1] == l->proto->internalClass) { - o = l->proto->prototype(); - if (l->classList[2] == o->internalClass) { - Scope scope(o->internalClass->engine); - ScopedFunctionObject getter(scope, o->propertyData(l->index + Object::GetterOffset)); - if (!getter) - return Encode::undefined(); - - ScopedCallData callData(scope, 0); - callData->thisObject = object; - getter->call(scope, callData); - return scope.result.asReturnedValue(); - } - } + Heap::Object *ho = o->d(); + if (ho->arrayData && ho->arrayData->type == Heap::ArrayData::Simple) { + Heap::SimpleArrayData *s = ho->arrayData.cast<Heap::SimpleArrayData>(); + if (l->indexedLookup.index < s->values.size) + if (!s->data(l->indexedLookup.index).isEmpty()) + return s->data(l->indexedLookup.index).asReturnedValue(); } + return o->get(l->indexedLookup.index); } l->getter = getterFallback; return getterFallback(l, engine, object); -} -ReturnedValue Lookup::primitiveGetter0Inline(Lookup *l, ExecutionEngine *engine, const Value &object) -{ - if (object.type() == l->type) { - Heap::Object *o = l->proto; - if (l->classList[0] == o->internalClass) - return o->inlinePropertyData(l->index)->asReturnedValue(); - } - l->getter = getterGeneric; - return getterGeneric(l, engine, object); -} - -ReturnedValue Lookup::primitiveGetter0MemberData(Lookup *l, ExecutionEngine *engine, const Value &object) -{ - if (object.type() == l->type) { - Heap::Object *o = l->proto; - if (l->classList[0] == o->internalClass) - return o->memberData->values.data()[l->index].asReturnedValue(); - } - l->getter = getterGeneric; - return getterGeneric(l, engine, object); } -ReturnedValue Lookup::primitiveGetter1(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::primitiveGetterProto(Lookup *l, ExecutionEngine *engine, const Value &object) { - if (object.type() == l->type) { - Heap::Object *o = l->proto; - if (l->classList[0] == o->internalClass && - l->classList[1] == o->prototype()->internalClass) - return o->prototype()->propertyData(l->index)->asReturnedValue(); + if (object.type() == l->primitiveLookup.type) { + Heap::Object *o = l->primitiveLookup.proto; + if (l->primitiveLookup.protoId == o->internalClass->protoId) + return l->primitiveLookup.data->asReturnedValue(); } l->getter = getterGeneric; return getterGeneric(l, engine, object); } -ReturnedValue Lookup::primitiveGetterAccessor0(Lookup *l, ExecutionEngine *engine, const Value &object) +ReturnedValue Lookup::primitiveGetterAccessor(Lookup *l, ExecutionEngine *engine, const Value &object) { - if (object.type() == l->type) { - Heap::Object *o = l->proto; - if (l->classList[0] == o->internalClass) { - Scope scope(o->internalClass->engine); - ScopedFunctionObject getter(scope, o->propertyData(l->index + Object::GetterOffset)); - if (!getter) + if (object.type() == l->primitiveLookup.type) { + Heap::Object *o = l->primitiveLookup.proto; + if (l->primitiveLookup.protoId == o->internalClass->protoId) { + const Value *getter = l->primitiveLookup.data; + if (!getter->isFunctionObject()) // ### catch at resolve time return Encode::undefined(); - ScopedCallData callData(scope, 0); - callData->thisObject = object; - getter->call(scope, callData); - return scope.result.asReturnedValue(); - } - } - l->getter = getterGeneric; - return getterGeneric(l, engine, object); -} - -ReturnedValue Lookup::primitiveGetterAccessor1(Lookup *l, ExecutionEngine *engine, const Value &object) -{ - if (object.type() == l->type) { - Heap::Object *o = l->proto; - if (l->classList[0] == o->internalClass && - l->classList[1] == o->prototype()->internalClass) { - Scope scope(o->internalClass->engine); - ScopedFunctionObject getter(scope, o->prototype()->propertyData(l->index + Object::GetterOffset)); - if (!getter) - return Encode::undefined(); - - ScopedCallData callData(scope, 0); - callData->thisObject = object; - getter->call(scope, callData); - return scope.result.asReturnedValue(); + return static_cast<const FunctionObject *>(getter)->call(&object, nullptr, 0); } } l->getter = getterGeneric; @@ -678,296 +443,206 @@ ReturnedValue Lookup::stringLengthGetter(Lookup *l, ExecutionEngine *engine, con return getterGeneric(l, engine, object); } -ReturnedValue Lookup::arrayLengthGetter(Lookup *l, ExecutionEngine *engine, const Value &object) -{ - if (const ArrayObject *a = object.as<ArrayObject>()) - return a->propertyData(Heap::ArrayObject::LengthPropertyIndex)->asReturnedValue(); - - l->getter = getterGeneric; - return getterGeneric(l, engine, object); -} - - ReturnedValue Lookup::globalGetterGeneric(Lookup *l, ExecutionEngine *engine) { - Object *o = engine->globalObject; - PropertyAttributes attrs; - ReturnedValue v = l->lookup(o, &attrs); - if (v != Primitive::emptyValue().asReturnedValue()) { - if (attrs.isData()) { - if (l->level == 0) { - uint nInline = o->d()->vtable()->nInlineProperties; - if (l->index < nInline) - l->globalGetter = globalGetter0Inline; - else { - l->index -= nInline; - l->globalGetter = globalGetter0MemberData; - } - } else if (l->level == 1) - l->globalGetter = globalGetter1; - else if (l->level == 2) - l->globalGetter = globalGetter2; - return v; - } else { - if (l->level == 0) - l->globalGetter = globalGetterAccessor0; - else if (l->level == 1) - l->globalGetter = globalGetterAccessor1; - else if (l->level == 2) - l->globalGetter = globalGetterAccessor2; - return v; - } - } - Scope scope(engine); - ScopedString n(scope, engine->current->compilationUnit->runtimeStrings[l->nameIndex]); - return engine->throwReferenceError(n); -} - -ReturnedValue Lookup::globalGetter0Inline(Lookup *l, ExecutionEngine *engine) -{ - Object *o = engine->globalObject; - if (l->classList[0] == o->internalClass()) - return o->d()->inlinePropertyData(l->index)->asReturnedValue(); - - l->globalGetter = globalGetterGeneric; - return globalGetterGeneric(l, engine); + return l->resolveGlobalGetter(engine); } -ReturnedValue Lookup::globalGetter0MemberData(Lookup *l, ExecutionEngine *engine) +ReturnedValue Lookup::globalGetterProto(Lookup *l, ExecutionEngine *engine) { - Object *o = engine->globalObject; - if (l->classList[0] == o->internalClass()) - return o->d()->memberData->values.data()[l->index].asReturnedValue(); - + Heap::Object *o = engine->globalObject->d(); + if (l->protoLookup.protoId == o->internalClass->protoId) + return l->protoLookup.data->asReturnedValue(); l->globalGetter = globalGetterGeneric; return globalGetterGeneric(l, engine); } -ReturnedValue Lookup::globalGetter1(Lookup *l, ExecutionEngine *engine) +ReturnedValue Lookup::globalGetterProtoAccessor(Lookup *l, ExecutionEngine *engine) { - Object *o = engine->globalObject; - if (l->classList[0] == o->internalClass() && - l->classList[1] == o->prototype()->internalClass) - return o->prototype()->propertyData(l->index)->asReturnedValue(); + Heap::Object *o = engine->globalObject->d(); + if (l->protoLookup.protoId == o->internalClass->protoId) { + const Value *getter = l->protoLookup.data; + if (!getter->isFunctionObject()) // ### catch at resolve time + return Encode::undefined(); + return static_cast<const FunctionObject *>(getter)->call(engine->globalObject, nullptr, 0); + } l->globalGetter = globalGetterGeneric; return globalGetterGeneric(l, engine); } -ReturnedValue Lookup::globalGetter2(Lookup *l, ExecutionEngine *engine) +bool Lookup::resolveSetter(ExecutionEngine *engine, Object *object, const Value &value) { - Heap::Object *o = engine->globalObject->d(); - if (l->classList[0] == o->internalClass) { - o = o->prototype(); - if (l->classList[1] == o->internalClass) { - o = o->prototype(); - if (l->classList[2] == o->internalClass) { - return o->prototype()->propertyData(l->index)->asReturnedValue(); + Scope scope(engine); + ScopedString name(scope, scope.engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + + Heap::InternalClass *c = object->internalClass(); + PropertyKey key = name->toPropertyKey(); + auto idx = c->findValueOrSetter(key); + if (idx.isValid()) { + if (object->isArrayObject() && idx.index == Heap::ArrayObject::LengthPropertyIndex) { + Q_ASSERT(!idx.attrs.isAccessor()); + setter = arrayLengthSetter; + return setter(this, engine, *object, value); + } else if (idx.attrs.isData() && idx.attrs.isWritable()) { + objectLookup.ic = object->internalClass(); + objectLookup.index = idx.index; + const auto nInline = object->d()->vtable()->nInlineProperties; + if (idx.index < nInline) { + setter = Lookup::setter0Inline; + objectLookup.offset = idx.index + object->d()->vtable()->inlinePropertyOffset; + } else { + setter = Lookup::setter0MemberData; + objectLookup.offset = idx.index - nInline; } + return setter(this, engine, *object, value); + } else { + // ### handle setter + setter = setterFallback; } + return setter(this, engine, *object, value); } - l->globalGetter = globalGetterGeneric; - return globalGetterGeneric(l, engine); -} -ReturnedValue Lookup::globalGetterAccessor0(Lookup *l, ExecutionEngine *engine) -{ - Object *o = engine->globalObject; - if (l->classList[0] == o->internalClass()) { - Scope scope(o->engine()); - ScopedFunctionObject getter(scope, o->propertyData(l->index + Object::GetterOffset)); - if (!getter) - return Encode::undefined(); - - ScopedCallData callData(scope, 0); - callData->thisObject = Primitive::undefinedValue(); - getter->call(scope, callData); - return scope.result.asReturnedValue(); + insertionLookup.protoId = c->protoId; + if (!object->put(key, value)) { + setter = Lookup::setterFallback; + return false; } - l->globalGetter = globalGetterGeneric; - return globalGetterGeneric(l, engine); -} -ReturnedValue Lookup::globalGetterAccessor1(Lookup *l, ExecutionEngine *engine) -{ - Object *o = engine->globalObject; - if (l->classList[0] == o->internalClass() && - l->classList[1] == o->prototype()->internalClass) { - Scope scope(o->engine()); - ScopedFunctionObject getter(scope, o->prototype()->propertyData(l->index + Object::GetterOffset)); - if (!getter) - return Encode::undefined(); - - ScopedCallData callData(scope, 0); - callData->thisObject = Primitive::undefinedValue(); - getter->call(scope, callData); - return scope.result.asReturnedValue(); + if (object->internalClass() == c) { + // ### setter in the prototype, should handle this + setter = setterFallback; + return true; } - l->globalGetter = globalGetterGeneric; - return globalGetterGeneric(l, engine); -} - -ReturnedValue Lookup::globalGetterAccessor2(Lookup *l, ExecutionEngine *engine) -{ - Heap::Object *o = engine->globalObject->d(); - if (l->classList[0] == o->internalClass) { - o = o->prototype(); - if (l->classList[1] == o->internalClass) { - o = o->prototype(); - if (l->classList[2] == o->internalClass) { - Scope scope(o->internalClass->engine); - ScopedFunctionObject getter(scope, o->propertyData(l->index + Object::GetterOffset)); - if (!getter) - return Encode::undefined(); - - ScopedCallData callData(scope, 0); - callData->thisObject = Primitive::undefinedValue(); - getter->call(scope, callData); - return scope.result.asReturnedValue(); - } - } + idx = object->internalClass()->findValueOrSetter(key); + if (!idx.isValid() || idx.attrs.isAccessor()) { // ### can this even happen? + setter = setterFallback; + return false; } - l->globalGetter = globalGetterGeneric; - return globalGetterGeneric(l, engine); + insertionLookup.newClass = object->internalClass(); + insertionLookup.offset = idx.index; + setter = setterInsert; + return true; } -void Lookup::setterGeneric(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::setterGeneric(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { + if (object.isObject()) + return l->resolveSetter(engine, static_cast<Object *>(&object), value); + + if (engine->currentStackFrame->v4Function->isStrict()) + return false; + Scope scope(engine); - ScopedObject o(scope, object); - if (!o) { - o = RuntimeHelpers::convertToObject(scope.engine, object); - if (!o) // type error - return; - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[l->nameIndex]); - o->put(name, value); - return; - } - o->setLookup(l, value); + ScopedObject o(scope, RuntimeHelpers::convertToObject(scope.engine, object)); + if (!o) // type error + return false; + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); + return o->put(name, value); } -void Lookup::setterTwoClasses(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::setterTwoClasses(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { - Lookup l1 = *l; + Lookup first = *l; + Lookup second = *l; - if (Object *o = object.as<Object>()) { - o->setLookup(l, value); + if (object.isObject()) { + if (!l->resolveSetter(engine, static_cast<Object *>(&object), value)) { + l->setter = setterFallback; + return false; + } - if (l->setter == Lookup::setter0 || l->setter == Lookup::setter0Inline) { + if (l->setter == Lookup::setter0MemberData || l->setter == Lookup::setter0Inline) { + l->objectLookupTwoClasses.ic = first.objectLookup.ic; + l->objectLookupTwoClasses.ic2 = second.objectLookup.ic; + l->objectLookupTwoClasses.offset = first.objectLookup.index; + l->objectLookupTwoClasses.offset2 = second.objectLookup.index; l->setter = setter0setter0; - l->classList[1] = l1.classList[0]; - l->index2 = l1.index; - return; + return true; } } l->setter = setterFallback; - setterFallback(l, engine, object, value); + return setterFallback(l, engine, object, value); } -void Lookup::setterFallback(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::setterFallback(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { QV4::Scope scope(engine); QV4::ScopedObject o(scope, object.toObject(scope.engine)); - if (o) { - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[l->nameIndex]); - o->put(name, value); - } -} - -void Lookup::setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) -{ - Object *o = static_cast<Object *>(object.managed()); - if (o && o->internalClass() == l->classList[0]) { - o->setProperty(engine, l->index, value); - return; - } + if (!o) + return false; - setterTwoClasses(l, engine, object, value); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]); + return o->put(name, value); } -void Lookup::setter0Inline(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::setter0MemberData(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { - Object *o = static_cast<Object *>(object.managed()); - if (o && o->internalClass() == l->classList[0]) { - o->d()->setInlineProperty(engine, l->index, value); - return; + Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); + if (o && o->internalClass == l->objectLookup.ic) { + o->memberData->values.set(engine, l->objectLookup.offset, value); + return true; } - setterTwoClasses(l, engine, object, value); + return setterTwoClasses(l, engine, object, value); } -void Lookup::setterInsert0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::setter0Inline(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { - Object *o = static_cast<Object *>(object.managed()); - if (o && o->internalClass() == l->classList[0]) { - Q_ASSERT(!o->prototype()); - o->setInternalClass(l->classList[3]); - o->setProperty(l->index, value); - return; + Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); + if (o && o->internalClass == l->objectLookup.ic) { + o->setInlinePropertyWithOffset(engine, l->objectLookup.offset, value); + return true; } - l->setter = setterFallback; - setterFallback(l, engine, object, value); + return setterTwoClasses(l, engine, object, value); } -void Lookup::setterInsert1(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::setter0setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { - Object *o = static_cast<Object *>(object.managed()); - if (o && o->internalClass() == l->classList[0]) { - Heap::Object *p = o->prototype(); - Q_ASSERT(p); - if (p->internalClass == l->classList[1]) { - Q_ASSERT(!p->prototype()); - o->setInternalClass(l->classList[3]); - o->setProperty(l->index, value); - return; + Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); + if (o) { + if (o->internalClass == l->objectLookupTwoClasses.ic) { + o->setProperty(engine, l->objectLookupTwoClasses.offset, value); + return true; + } + if (o->internalClass == l->objectLookupTwoClasses.ic2) { + o->setProperty(engine, l->objectLookupTwoClasses.offset2, value); + return true; } } l->setter = setterFallback; - setterFallback(l, engine, object, value); + return setterFallback(l, engine, object, value); } -void Lookup::setterInsert2(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::setterInsert(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { Object *o = static_cast<Object *>(object.managed()); - if (o && o->internalClass() == l->classList[0]) { - Heap::Object *p = o->prototype(); - Q_ASSERT(p); - if (p->internalClass == l->classList[1]) { - p = p->prototype(); - Q_ASSERT(p); - if (p->internalClass == l->classList[2]) { - Q_ASSERT(!p->prototype()); - o->setInternalClass(l->classList[3]); - o->setProperty(l->index, value); - return; - } - } + if (o && o->internalClass()->protoId == l->insertionLookup.protoId) { + o->setInternalClass(l->insertionLookup.newClass); + o->d()->setProperty(engine, l->insertionLookup.offset, value); + return true; } l->setter = setterFallback; - setterFallback(l, engine, object, value); + return setterFallback(l, engine, object, value); } -void Lookup::setter0setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::arrayLengthSetter(Lookup *, ExecutionEngine *engine, Value &object, const Value &value) { - Object *o = static_cast<Object *>(object.managed()); - if (o) { - if (o->internalClass() == l->classList[0]) { - o->setProperty(l->index, value); - return; - } - if (o->internalClass() == l->classList[1]) { - o->setProperty(l->index2, value); - return; - } + Q_ASSERT(object.isObject() && static_cast<Object &>(object).isArrayObject()); + bool ok; + uint len = value.asArrayLength(&ok); + if (!ok) { + engine->throwRangeError(value); + return false; } - - l->setter = setterFallback; - setterFallback(l, engine, object, value); - + ok = static_cast<Object &>(object).setArrayLength(len); + if (!ok) + return false; + return true; } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index ce5189a780..bfe2354427 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -65,38 +65,67 @@ QT_BEGIN_NAMESPACE namespace QV4 { struct Lookup { - enum { Size = 4 }; union { - ReturnedValue (*indexedGetter)(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index); - void (*indexedSetter)(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index, const Value &v); ReturnedValue (*getter)(Lookup *l, ExecutionEngine *engine, const Value &object); ReturnedValue (*globalGetter)(Lookup *l, ExecutionEngine *engine); - void (*setter)(Lookup *l, ExecutionEngine *engine, Value &object, const Value &v); + bool (*setter)(Lookup *l, ExecutionEngine *engine, Value &object, const Value &v); }; union { - InternalClass *classList[Size]; struct { - void *dummy0; - void *dummy1; - void *dummy2; + Heap::Base *h1; + Heap::Base *h2; + quintptr unused; + quintptr unused2; + } markDef; + struct { + Heap::InternalClass *ic; + quintptr unused; + uint index; + uint offset; + } objectLookup; + struct { + quintptr protoId; + quintptr _unused; + const Value *data; + } protoLookup; + struct { + Heap::InternalClass *ic; + Heap::InternalClass *ic2; + uint offset; + uint offset2; + } objectLookupTwoClasses; + struct { + quintptr protoId; + quintptr protoId2; + const Value *data; + const Value *data2; + } protoLookupTwoClasses; + struct { + // Make sure the next two values are in sync with protoLookup + quintptr protoId; Heap::Object *proto; - }; - }; - union { - int level; - uint index2; - unsigned type; + const Value *data; + quintptr type; + } primitiveLookup; + struct { + Heap::InternalClass *newClass; + quintptr protoId; + uint offset; + uint unused; + } insertionLookup; + struct { + quintptr _unused; + quintptr _unused2; + uint index; + uint unused; + } indexedLookup; }; - uint index; uint nameIndex; - static ReturnedValue indexedGetterGeneric(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index); - static ReturnedValue indexedGetterFallback(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index); - static ReturnedValue indexedGetterObjectInt(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index); - - static void indexedSetterGeneric(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index, const Value &v); - static void indexedSetterFallback(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index, const Value &value); - static void indexedSetterObjectInt(Lookup *l, ExecutionEngine *engine, const Value &object, const Value &index, const Value &v); + ReturnedValue resolveGetter(ExecutionEngine *engine, const Object *object); + ReturnedValue resolvePrimitiveGetter(ExecutionEngine *engine, const Value &object); + ReturnedValue resolveGlobalGetter(ExecutionEngine *engine); + void resolveProtoGetter(PropertyKey name, const Heap::Object *proto); static ReturnedValue getterGeneric(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getterTwoClasses(Lookup *l, ExecutionEngine *engine, const Value &object); @@ -104,54 +133,49 @@ struct Lookup { static ReturnedValue getter0MemberData(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getter0Inline(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue getter1(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue getter2(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue getterProto(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getter0Inlinegetter0Inline(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getter0Inlinegetter0MemberData(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue getter0MemberDatagetter0MemberData(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue getter0Inlinegetter1(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue getter0MemberDatagetter1(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue getter1getter1(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue getterAccessor0(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue getterAccessor1(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue getterAccessor2(Lookup *l, ExecutionEngine *engine, const Value &object); - - static ReturnedValue primitiveGetter0Inline(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue primitiveGetter0MemberData(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue primitiveGetter1(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue primitiveGetterAccessor0(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue primitiveGetterAccessor1(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue getterProtoTwoClasses(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue getterAccessor(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue getterProtoAccessor(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue getterProtoAccessorTwoClasses(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue getterIndexed(Lookup *l, ExecutionEngine *engine, const Value &object); + + static ReturnedValue primitiveGetterProto(Lookup *l, ExecutionEngine *engine, const Value &object); + static ReturnedValue primitiveGetterAccessor(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue stringLengthGetter(Lookup *l, ExecutionEngine *engine, const Value &object); - static ReturnedValue arrayLengthGetter(Lookup *l, ExecutionEngine *engine, const Value &object); static ReturnedValue globalGetterGeneric(Lookup *l, ExecutionEngine *engine); - static ReturnedValue globalGetter0Inline(Lookup *l, ExecutionEngine *engine); - static ReturnedValue globalGetter0MemberData(Lookup *l, ExecutionEngine *engine); - static ReturnedValue globalGetter1(Lookup *l, ExecutionEngine *engine); - static ReturnedValue globalGetter2(Lookup *l, ExecutionEngine *engine); - static ReturnedValue globalGetterAccessor0(Lookup *l, ExecutionEngine *engine); - static ReturnedValue globalGetterAccessor1(Lookup *l, ExecutionEngine *engine); - static ReturnedValue globalGetterAccessor2(Lookup *l, ExecutionEngine *engine); - - static void setterGeneric(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static void setterTwoClasses(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static void setterFallback(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static void setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static void setter0Inline(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static void setterInsert0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static void setterInsert1(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static void setterInsert2(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static void setter0setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - - ReturnedValue lookup(const Value &thisObject, Object *obj, PropertyAttributes *attrs); - ReturnedValue lookup(const Object *obj, PropertyAttributes *attrs); - + static ReturnedValue globalGetterProto(Lookup *l, ExecutionEngine *engine); + static ReturnedValue globalGetterProtoAccessor(Lookup *l, ExecutionEngine *engine); + + bool resolveSetter(ExecutionEngine *engine, Object *object, const Value &value); + static bool setterGeneric(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + Q_NEVER_INLINE static bool setterTwoClasses(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool setterFallback(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool setter0MemberData(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool setter0Inline(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool setter0setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool setterInsert(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool arrayLengthSetter(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + + void markObjects(MarkStack *stack) { + if (markDef.h1 && !(reinterpret_cast<quintptr>(markDef.h1) & 1)) + markDef.h1->mark(stack); + if (markDef.h2 && !(reinterpret_cast<quintptr>(markDef.h2) & 1)) + markDef.h2->mark(stack); + } + + void clear() { + memset(&markDef, 0, sizeof(markDef)); + } }; Q_STATIC_ASSERT(std::is_standard_layout<Lookup>::value); // Ensure that these offsets are always at this point to keep generated code compatible // across 32-bit and 64-bit (matters when cross-compiling). -Q_STATIC_ASSERT(offsetof(Lookup, indexedGetter) == 0); Q_STATIC_ASSERT(offsetof(Lookup, getter) == 0); } diff --git a/src/qml/jsruntime/qv4managed.cpp b/src/qml/jsruntime/qv4managed.cpp index e00eaa0d9b..d51b03d90b 100644 --- a/src/qml/jsruntime/qv4managed.cpp +++ b/src/qml/jsruntime/qv4managed.cpp @@ -43,35 +43,23 @@ using namespace QV4; +DEFINE_MANAGED_VTABLE(Managed); -const VTable Managed::static_vtbl = -{ - 0, - 0, - 0, - 0, - Managed::IsExecutionContext, - Managed::IsString, - Managed::IsObject, - Managed::IsFunctionObject, - Managed::IsErrorObject, - Managed::IsArrayData, - 0, - Managed::MyType, - "Managed", - 0, - 0 /*markObjects*/, - isEqualTo -}; +DEFINE_MANAGED_VTABLE(InternalClass); QString Managed::className() const { - const char *s = 0; - switch (Type(d()->vtable()->type)) { + const char *s = nullptr; + switch (Type(vtable()->type)) { case Type_Invalid: - case Type_String: return QString(); + case Type_String: + s = "String"; + break; + case Type_Symbol: + s = "Symbol"; + break; case Type_Object: s = "Object"; break; @@ -81,6 +69,9 @@ QString Managed::className() const case Type_FunctionObject: s = "Function"; break; + case Type_GeneratorObject: + s = "Generator"; + break; case Type_BooleanObject: s = "Boolean"; break; @@ -90,6 +81,9 @@ QString Managed::className() const case Type_StringObject: s = "String"; break; + case Type_SymbolObject: + s = "Symbol"; + break; case Type_DateObject: s = "Date"; break; @@ -97,7 +91,7 @@ QString Managed::className() const s = "RegExp"; break; case Type_ErrorObject: - s = ErrorObject::className(static_cast<Heap::ErrorObject *>(d())->errorType); + s = "Error"; break; case Type_ArgumentsObject: s = "Arguments"; @@ -105,6 +99,9 @@ QString Managed::className() const case Type_JsonObject: s = "JSON"; break; + case Type_ProxyObject: + s = "ProxyObject"; + break; case Type_MathObject: s = "Math"; break; @@ -112,8 +109,23 @@ QString Managed::className() const case Type_ExecutionContext: s = "__ExecutionContext"; break; - case Type_ForeachIteratorObject: - s = "__ForeachIterator"; + case Type_MapIteratorObject: + s = "Map Iterator"; + break; + case Type_SetIteratorObject: + s = "Set Iterator"; + break; + case Type_ArrayIteratorObject: + s = "Array Iterator"; + break; + case Type_StringIteratorObject: + s = "String Iterator"; + break; + case Type_ForInIterator: + s = "__ForIn Iterator"; + break; + case Type_InternalClass: + s = "__InternalClass"; break; case Type_RegExp: s = "__RegExp"; @@ -126,7 +138,12 @@ QString Managed::className() const return QString::fromLatin1(s); } -bool Managed::isEqualTo(Managed *, Managed *) +bool Managed::virtualIsEqualTo(Managed *, Managed *) { return false; } + + +OwnPropertyKeyIterator::~OwnPropertyKeyIterator() +{ +} diff --git a/src/qml/jsruntime/qv4managed_p.h b/src/qml/jsruntime/qv4managed_p.h index 40dfc244ea..d85b30a056 100644 --- a/src/qml/jsruntime/qv4managed_p.h +++ b/src/qml/jsruntime/qv4managed_p.h @@ -55,6 +55,7 @@ #include "qv4enginebase_p.h" #include <private/qv4heap_p.h> #include <private/qv4writebarrier_p.h> +#include <private/qv4vtable_p.h> QT_BEGIN_NAMESPACE @@ -70,13 +71,9 @@ inline int qYouForgotTheQ_MANAGED_Macro(T, T) { return 0; } template <typename T1, typename T2> inline void qYouForgotTheQ_MANAGED_Macro(T1, T2) {} -#ifdef Q_COMPILER_STATIC_ASSERT -#define V4_MANAGED_SIZE_TEST void __dataTest() { Q_STATIC_ASSERT(sizeof(*this) == sizeof(Managed)); } -#else -#define V4_MANAGED_SIZE_TEST -#endif +#define V4_MANAGED_SIZE_TEST void __dataTest() { static_assert (sizeof(*this) == sizeof(Managed), "Classes derived from Managed can't have own data members."); } -#define V4_NEEDS_DESTROY static void destroy(QV4::Heap::Base *b) { static_cast<Data *>(b)->destroy(); } +#define V4_NEEDS_DESTROY static void virtualDestroy(QV4::Heap::Base *b) { static_cast<Data *>(b)->destroy(); } #define V4_MANAGED_ITSELF(DataClass, superClass) \ @@ -92,79 +89,30 @@ inline void qYouForgotTheQ_MANAGED_Macro(T1, T2) {} QV4::Heap::DataClass *dptr = d_unchecked(); \ dptr->_checkIsInitialized(); \ return dptr; \ - } \ - static Q_CONSTEXPR quint64 markTable = QV4::Heap::DataClass::markTable; \ - V4_ASSERT_IS_TRIVIAL(QV4::Heap::DataClass) + } #define V4_MANAGED(DataClass, superClass) \ private: \ DataClass() Q_DECL_EQ_DELETE; \ Q_DISABLE_COPY(DataClass) \ - V4_MANAGED_ITSELF(DataClass, superClass) + V4_MANAGED_ITSELF(DataClass, superClass) \ + Q_STATIC_ASSERT(std::is_trivial< QV4::Heap::DataClass >::value); #define Q_MANAGED_TYPE(type) \ public: \ enum { MyType = Type_##type }; -#define Q_VTABLE_FUNCTION(classname, func) \ - (classname::func == QV4::Managed::func ? 0 : classname::func) - -// Q_VTABLE_FUNCTION triggers a bogus tautological-compare warning in GCC6+ -#if (defined(Q_CC_GNU) && Q_CC_GNU >= 600) -#define QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_ON \ - QT_WARNING_PUSH; \ - QT_WARNING_DISABLE_GCC("-Wtautological-compare") - -#define QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_OFF \ - ;QT_WARNING_POP -#elif defined(Q_CC_CLANG) && Q_CC_CLANG >= 306 -#define QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_ON \ - QT_WARNING_PUSH; \ - QT_WARNING_DISABLE_CLANG("-Wtautological-compare") - -#define QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_OFF \ - ;QT_WARNING_POP -#else -#define QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_ON -#define QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_OFF -#endif - -#define DEFINE_MANAGED_VTABLE_INT(classname, parentVTable) \ -{ \ - parentVTable, \ - markTable, \ - (sizeof(classname::Data) + sizeof(QV4::Value) - 1)/sizeof(QV4::Value), \ - (sizeof(classname::Data) + (std::is_same<classname, Object>::value ? 2*sizeof(QV4::Value) : 0) + QV4::Chunk::SlotSize - 1)/QV4::Chunk::SlotSize*QV4::Chunk::SlotSize/sizeof(QV4::Value) \ - - (sizeof(classname::Data) + sizeof(QV4::Value) - 1)/sizeof(QV4::Value), \ - classname::IsExecutionContext, \ - classname::IsString, \ - classname::IsObject, \ - classname::IsFunctionObject, \ - classname::IsErrorObject, \ - classname::IsArrayData, \ - 0, \ - classname::MyType, \ - #classname, \ - Q_VTABLE_FUNCTION(classname, destroy), \ - Q_VTABLE_FUNCTION(classname, markObjects), \ - isEqualTo \ -} - -#define DEFINE_MANAGED_VTABLE(classname) \ -QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_ON \ -const QV4::VTable classname::static_vtbl = DEFINE_MANAGED_VTABLE_INT(classname, 0) \ -QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_OFF - #define V4_INTERNALCLASS(c) \ - static QV4::InternalClass *defaultInternalClass(QV4::EngineBase *e) \ - { return e->internalClasses[QV4::EngineBase::Class_##c]; } + static Heap::InternalClass *defaultInternalClass(QV4::EngineBase *e) \ + { return e->internalClasses(QV4::EngineBase::Class_##c); } -struct Q_QML_PRIVATE_EXPORT Managed : Value +struct Q_QML_PRIVATE_EXPORT Managed : Value, VTableBase { V4_MANAGED_ITSELF(Base, Managed) enum { IsExecutionContext = false, IsString = false, + IsStringOrSymbol = false, IsObject = false, IsFunctionObject = false, IsErrorObject = false, @@ -176,53 +124,61 @@ private: Q_DISABLE_COPY(Managed) public: + enum { NInlineProperties = 0 }; enum Type { Type_Invalid, Type_String, Type_Object, + Type_Symbol, Type_ArrayObject, Type_FunctionObject, + Type_GeneratorObject, Type_BooleanObject, Type_NumberObject, Type_StringObject, + Type_SymbolObject, Type_DateObject, Type_RegExpObject, Type_ErrorObject, Type_ArgumentsObject, Type_JsonObject, Type_MathObject, + Type_ProxyObject, Type_ExecutionContext, - Type_ForeachIteratorObject, + Type_InternalClass, + Type_SetIteratorObject, + Type_MapIteratorObject, + Type_ArrayIteratorObject, + Type_StringIteratorObject, + Type_ForInIterator, Type_RegExp, Type_QmlSequence }; Q_MANAGED_TYPE(Invalid) - InternalClass *internalClass() const { return d()->internalClass; } + Heap::InternalClass *internalClass() const { return d()->internalClass; } + const VTable *vtable() const { return d()->internalClass->vtable; } inline ExecutionEngine *engine() const { return internalClass()->engine; } - bool isListType() const { return d()->vtable()->type == Type_QmlSequence; } + bool isListType() const { return d()->internalClass->vtable->type == Type_QmlSequence; } + bool isArrayLike() const { return isArrayObject() || isListType(); } - bool isArrayObject() const { return d()->vtable()->type == Type_ArrayObject; } - bool isStringObject() const { return d()->vtable()->type == Type_StringObject; } + bool isArrayObject() const { return d()->internalClass->vtable->type == Type_ArrayObject; } + bool isStringObject() const { return d()->internalClass->vtable->type == Type_StringObject; } + bool isSymbolObject() const { return d()->internalClass->vtable->type == Type_SymbolObject; } QString className() const; bool isEqualTo(const Managed *other) const - { return d()->vtable()->isEqualTo(const_cast<Managed *>(this), const_cast<Managed *>(other)); } - - static bool isEqualTo(Managed *m, Managed *other); + { return d()->internalClass->vtable->isEqualTo(const_cast<Managed *>(this), const_cast<Managed *>(other)); } bool inUse() const { return d()->inUse(); } bool markBit() const { return d()->isMarked(); } inline void mark(MarkStack *markStack); - static void destroy(Heap::Base *) {} - static void markObjects(Heap::Base *, MarkStack *) {} - Q_ALWAYS_INLINE Heap::Base *heapObject() const { return m(); } @@ -234,12 +190,20 @@ public: return static_cast<const T *>(this); } +protected: + static bool virtualIsEqualTo(Managed *m, Managed *other); + private: friend class MemoryManager; friend struct Identifiers; friend struct ObjectIterator; }; +inline void Managed::mark(MarkStack *markStack) +{ + Q_ASSERT(m()); + m()->mark(markStack); +} template<> inline const Managed *Value::as() const { @@ -251,6 +215,33 @@ inline const Object *Value::as() const { return objectValue(); } + +struct InternalClass : Managed +{ + V4_MANAGED_ITSELF(InternalClass, Managed) + Q_MANAGED_TYPE(InternalClass) + V4_INTERNALCLASS(Empty) + V4_NEEDS_DESTROY + + Q_REQUIRED_RESULT Heap::InternalClass *changeVTable(const VTable *vt) { + return d()->changeVTable(vt); + } + Q_REQUIRED_RESULT Heap::InternalClass *changePrototype(Heap::Object *proto) { + return d()->changePrototype(proto); + } + Q_REQUIRED_RESULT Heap::InternalClass *addMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry = nullptr) { + return d()->addMember(identifier, data, entry); + } + + Q_REQUIRED_RESULT Heap::InternalClass *changeMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry = nullptr) { + return d()->changeMember(identifier, data, entry); + } + + void operator =(Heap::InternalClass *ic) { + Value::operator=(ic); + } +}; + } diff --git a/src/qml/jsruntime/qv4mapiterator.cpp b/src/qml/jsruntime/qv4mapiterator.cpp new file mode 100644 index 0000000000..cd5fbb8e63 --- /dev/null +++ b/src/qml/jsruntime/qv4mapiterator.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qv4iterator_p.h> +#include <private/qv4estable_p.h> +#include <private/qv4mapiterator_p.h> +#include <private/qv4mapobject_p.h> +#include <private/qv4symbol_p.h> + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(MapIteratorObject); + +void MapIteratorPrototype::init(ExecutionEngine *e) +{ + defineDefaultProperty(QStringLiteral("next"), method_next, 0); + + Scope scope(e); + ScopedString val(scope, e->newString(QLatin1String("Map Iterator"))); + defineReadonlyConfigurableProperty(e->symbol_toStringTag(), val); +} + +ReturnedValue MapIteratorPrototype::method_next(const FunctionObject *b, const Value *that, const Value *, int) +{ + Scope scope(b); + const MapIteratorObject *thisObject = that->as<MapIteratorObject>(); + if (!thisObject) + return scope.engine->throwTypeError(QLatin1String("Not a Map Iterator instance")); + + Scoped<MapObject> s(scope, thisObject->d()->iteratedMap); + uint index = thisObject->d()->mapNextIndex; + IteratorKind itemKind = thisObject->d()->iterationKind; + + if (!s) { + QV4::Value undefined = Value::undefinedValue(); + return IteratorPrototype::createIterResultObject(scope.engine, undefined, true); + } + + Value *arguments = scope.alloc(2); + + while (index < s->d()->esTable->size()) { + s->d()->esTable->iterate(index, &arguments[0], &arguments[1]); + thisObject->d()->mapNextIndex = index + 1; + + ScopedValue result(scope); + + if (itemKind == KeyIteratorKind) { + result = arguments[0]; + } else if (itemKind == ValueIteratorKind) { + result = arguments[1]; + } else { + Q_ASSERT(itemKind == KeyValueIteratorKind); + + result = scope.engine->newArrayObject(); + + Scoped<ArrayObject> resultArray(scope, result); + resultArray->arrayReserve(2); + resultArray->arrayPut(0, arguments[0]); + resultArray->arrayPut(1, arguments[1]); + resultArray->setArrayLengthUnchecked(2); + } + + return IteratorPrototype::createIterResultObject(scope.engine, result, false); + } + + thisObject->d()->iteratedMap.set(scope.engine, nullptr); + QV4::Value undefined = Value::undefinedValue(); + return IteratorPrototype::createIterResultObject(scope.engine, undefined, true); +} + + diff --git a/src/qml/jsruntime/qv4mapiterator_p.h b/src/qml/jsruntime/qv4mapiterator_p.h new file mode 100644 index 0000000000..836ba14663 --- /dev/null +++ b/src/qml/jsruntime/qv4mapiterator_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4MAPITERATOR_P_H +#define QV4MAPITERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4iterator_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Heap { + +#define MapIteratorObjectMembers(class, Member) \ + Member(class, Pointer, Object *, iteratedMap) \ + Member(class, NoMark, IteratorKind, iterationKind) \ + Member(class, NoMark, quint32, mapNextIndex) + +DECLARE_HEAP_OBJECT(MapIteratorObject, Object) { + DECLARE_MARKOBJECTS(MapIteratorObject); + void init(Object *obj, QV4::ExecutionEngine *engine) + { + Object::init(); + this->iteratedMap.set(engine, obj); + this->mapNextIndex = 0; + } +}; + +} + +struct MapIteratorPrototype : Object +{ + V4_PROTOTYPE(iteratorPrototype) + void init(ExecutionEngine *engine); + + static ReturnedValue method_next(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); +}; + +struct MapIteratorObject : Object +{ + V4_OBJECT2(MapIteratorObject, Object) + Q_MANAGED_TYPE(MapIteratorObject) + V4_PROTOTYPE(mapIteratorPrototype) + + void init(ExecutionEngine *engine); +}; + + +} + +QT_END_NAMESPACE + +#endif // QV4MAPITERATOR_P_H + + diff --git a/src/qml/jsruntime/qv4mapobject.cpp b/src/qml/jsruntime/qv4mapobject.cpp new file mode 100644 index 0000000000..90e1908a84 --- /dev/null +++ b/src/qml/jsruntime/qv4mapobject.cpp @@ -0,0 +1,382 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4setobject_p.h" // ### temporary +#include "qv4mapobject_p.h" +#include "qv4mapiterator_p.h" +#include "qv4estable_p.h" +#include "qv4symbol_p.h" + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(WeakMapCtor); +DEFINE_OBJECT_VTABLE(MapCtor); +DEFINE_OBJECT_VTABLE(MapObject); + +void Heap::WeakMapCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("WeakMap")); +} + +void Heap::MapCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("Map")); +} + +ReturnedValue WeakMapCtor::construct(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget, bool weakMap) +{ + Scope scope(f); + Scoped<MapObject> a(scope, scope.engine->memoryManager->allocate<MapObject>()); + bool protoSet = false; + if (newTarget) + protoSet = a->setProtoFromNewTarget(newTarget); + if (!protoSet && weakMap) { + a->setPrototypeOf(scope.engine->weakMapPrototype()); + scope.engine->memoryManager->registerWeakMap(a->d()); + } + a->d()->isWeakMap = weakMap; + + if (argc > 0) { + ScopedValue iterable(scope, argv[0]); + + if (!iterable->isNullOrUndefined()) { + ScopedFunctionObject adder(scope, a->get(ScopedString(scope, scope.engine->newString(QString::fromLatin1("set"))))); + if (!adder) + return scope.engine->throwTypeError(); + + ScopedObject iter(scope, Runtime::GetIterator::call(scope.engine, iterable, true)); + if (scope.hasException()) + return Encode::undefined(); + Q_ASSERT(iter); + + ScopedValue obj(scope); + Value *arguments = scope.alloc(2); + ScopedValue done(scope); + forever { + done = Runtime::IteratorNext::call(scope.engine, iter, obj); + if (scope.hasException()) + break; + if (done->toBoolean()) + return a->asReturnedValue(); + const Object *o = obj->objectValue(); + if (!o) { + scope.engine->throwTypeError(); + break; + } + + arguments[0] = o->get(PropertyKey::fromArrayIndex(0)); + if (scope.hasException()) + break; + arguments[1] = o->get(PropertyKey::fromArrayIndex(1)); + if (scope.hasException()) + break; + + adder->call(a, arguments, 2); + if (scope.hasException()) + break; + } + ScopedValue falsey(scope, Encode(false)); + return Runtime::IteratorClose::call(scope.engine, iter, falsey); + } + } + return a->asReturnedValue(); + +} + +ReturnedValue WeakMapCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + return construct(f, argv, argc, newTarget, true); +} + +ReturnedValue WeakMapCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) +{ + Scope scope(f); + return scope.engine->throwTypeError(QString::fromLatin1("(Weak)Map requires new")); +} + + +ReturnedValue MapCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + return construct(f, argv, argc, newTarget, false); +} + +void WeakMapPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedObject o(scope); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0)); + ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); + defineDefaultProperty(engine->id_constructor(), (o = ctor)); + + defineDefaultProperty(QStringLiteral("delete"), method_delete, 1); + defineDefaultProperty(QStringLiteral("get"), method_get, 1); + defineDefaultProperty(QStringLiteral("has"), method_has, 1); + defineDefaultProperty(QStringLiteral("set"), method_set, 2); + + ScopedString val(scope, engine->newString(QLatin1String("WeakMap"))); + defineReadonlyConfigurableProperty(engine->symbol_toStringTag(), val); +} + + +void MapPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedObject o(scope); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0)); + ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); + ctor->addSymbolSpecies(); + defineDefaultProperty(engine->id_constructor(), (o = ctor)); + + defineDefaultProperty(QStringLiteral("clear"), method_clear, 0); + defineDefaultProperty(QStringLiteral("delete"), method_delete, 1); + defineDefaultProperty(QStringLiteral("forEach"), method_forEach, 1); + defineDefaultProperty(QStringLiteral("get"), method_get, 1); + defineDefaultProperty(QStringLiteral("has"), method_has, 1); + defineDefaultProperty(QStringLiteral("keys"), method_keys, 0); + defineDefaultProperty(QStringLiteral("set"), method_set, 2); + defineAccessorProperty(QStringLiteral("size"), method_get_size, nullptr); + defineDefaultProperty(QStringLiteral("values"), method_values, 0); + + // Per the spec, the value for entries/@@iterator is the same + ScopedString valString(scope, scope.engine->newIdentifier(QStringLiteral("entries"))); + ScopedFunctionObject entriesFn(scope, FunctionObject::createBuiltinFunction(engine, valString, MapPrototype::method_entries, 0)); + defineDefaultProperty(QStringLiteral("entries"), entriesFn); + defineDefaultProperty(engine->symbol_iterator(), entriesFn); + + ScopedString val(scope, engine->newString(QLatin1String("Map"))); + defineReadonlyConfigurableProperty(engine->symbol_toStringTag(), val); +} + +void Heap::MapObject::init() +{ + Object::init(); + esTable = new ESTable(); +} + +void Heap::MapObject::destroy() +{ + delete esTable; + esTable = nullptr; +} + +void Heap::MapObject::removeUnmarkedKeys() +{ + esTable->removeUnmarkedKeys(); +} + +void Heap::MapObject::markObjects(Heap::Base *that, MarkStack *markStack) +{ + MapObject *m = static_cast<MapObject *>(that); + m->esTable->markObjects(markStack, m->isWeakMap); + Object::markObjects(that, markStack); +} + +ReturnedValue WeakMapPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || !that->d()->isWeakMap) + return scope.engine->throwTypeError(); + if (!argc || !argv[0].isObject()) + return Encode(false); + + return Encode(that->d()->esTable->remove(argv[0])); + +} + +ReturnedValue WeakMapPrototype::method_get(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || !that->d()->isWeakMap) + return scope.engine->throwTypeError(); + if (!argc || !argv[0].isObject()) + return Encode::undefined(); + + return that->d()->esTable->get(argv[0]); +} + +ReturnedValue WeakMapPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || !that->d()->isWeakMap) + return scope.engine->throwTypeError(); + if (!argc || !argv[0].isObject()) + return Encode(false); + + return Encode(that->d()->esTable->has(argv[0])); +} + +ReturnedValue WeakMapPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if ((!that || !that->d()->isWeakMap) || + (!argc || !argv[0].isObject())) + return scope.engine->throwTypeError(); + + that->d()->esTable->set(argv[0], argc > 1 ? argv[1] : Value::undefinedValue()); + return that.asReturnedValue(); +} + + +ReturnedValue MapPrototype::method_clear(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + that->d()->esTable->clear(); + return Encode::undefined(); +} + +ReturnedValue MapPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + return Encode(that->d()->esTable->remove(argc ? argv[0] : Value::undefinedValue())); +} + +ReturnedValue MapPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + Scoped<MapIteratorObject> ao(scope, scope.engine->newMapIteratorObject(that)); + ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; + return ao->asReturnedValue(); +} + +ReturnedValue MapPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + ScopedFunctionObject callbackfn(scope, argv[0]); + if (!callbackfn) + return scope.engine->throwTypeError(); + + ScopedValue thisArg(scope, Value::undefinedValue()); + if (argc > 1) + thisArg = ScopedValue(scope, argv[1]); + + Value *arguments = scope.alloc(3); + arguments[2] = that; + for (uint i = 0; i < that->d()->esTable->size(); ++i) { + that->d()->esTable->iterate(i, &arguments[1], &arguments[0]); // fill in key (0), value (1) + + callbackfn->call(thisArg, arguments, 3); + CHECK_EXCEPTION(); + } + return Encode::undefined(); +} + +ReturnedValue MapPrototype::method_get(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + return that->d()->esTable->get(argc ? argv[0] : Value::undefinedValue()); +} + +ReturnedValue MapPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + return Encode(that->d()->esTable->has(argc ? argv[0] : Value::undefinedValue())); +} + +ReturnedValue MapPrototype::method_keys(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + Scoped<MapIteratorObject> ao(scope, scope.engine->newMapIteratorObject(that)); + ao->d()->iterationKind = IteratorKind::KeyIteratorKind; + return ao->asReturnedValue(); +} + +ReturnedValue MapPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + that->d()->esTable->set(argc ? argv[0] : Value::undefinedValue(), argc > 1 ? argv[1] : Value::undefinedValue()); + return that.asReturnedValue(); +} + +ReturnedValue MapPrototype::method_get_size(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + return Encode(that->d()->esTable->size()); +} + +ReturnedValue MapPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<MapObject> that(scope, thisObject); + if (!that || that->d()->isWeakMap) + return scope.engine->throwTypeError(); + + Scoped<MapIteratorObject> ao(scope, scope.engine->newMapIteratorObject(that)); + ao->d()->iterationKind = IteratorKind::ValueIteratorKind; + return ao->asReturnedValue(); +} diff --git a/src/qml/jsruntime/qv4mapobject_p.h b/src/qml/jsruntime/qv4mapobject_p.h new file mode 100644 index 0000000000..a0fae2a14a --- /dev/null +++ b/src/qml/jsruntime/qv4mapobject_p.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4MAPOBJECT_P_H +#define QV4MAPOBJECT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4objectproto_p.h" +#include "qv4functionobject_p.h" +#include "qv4string_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +class ESTable; + +namespace Heap { + +struct WeakMapCtor : FunctionObject { + void init(QV4::ExecutionContext *scope); +}; + +struct MapCtor : WeakMapCtor { + void init(QV4::ExecutionContext *scope); +}; + +struct MapObject : Object { + static void markObjects(Heap::Base *that, MarkStack *markStack); + void init(); + void destroy(); + void removeUnmarkedKeys(); + + MapObject *nextWeakMap; + ESTable *esTable; + bool isWeakMap; +}; + +} + +struct WeakMapCtor: FunctionObject +{ + V4_OBJECT2(WeakMapCtor, FunctionObject) + + static ReturnedValue construct(const FunctionObject *f, const Value *argv, int argc, const Value *, bool weakMap); + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct MapCtor : WeakMapCtor +{ + V4_OBJECT2(MapCtor, WeakMapCtor) + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); +}; + +struct MapObject : Object +{ + V4_OBJECT2(MapObject, Object) + V4_PROTOTYPE(mapPrototype) + V4_NEEDS_DESTROY +}; + +struct WeakMapPrototype : Object +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_delete(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_has(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_set(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + +struct MapPrototype : WeakMapPrototype +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_clear(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_delete(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_entries(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_forEach(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_has(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_keys(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_set(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_size(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_values(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + + +} // namespace QV4 + + +QT_END_NAMESPACE + +#endif // QV4MAPOBJECT_P_H + diff --git a/src/qml/jsruntime/qv4math_p.h b/src/qml/jsruntime/qv4math_p.h index 67c963622f..a60a49a811 100644 --- a/src/qml/jsruntime/qv4math_p.h +++ b/src/qml/jsruntime/qv4math_p.h @@ -66,28 +66,43 @@ QT_BEGIN_NAMESPACE namespace QV4 { -static inline QMLJS_READONLY ReturnedValue add_int32(int a, int b) +static inline QMLJS_READONLY ReturnedValue add_int32(int a, int b, quint8 *traceInfo = nullptr) { int result; - if (Q_UNLIKELY(add_overflow(a, b, &result))) - return Primitive::fromDouble(static_cast<double>(a) + b).asReturnedValue(); - return Primitive::fromInt32(result).asReturnedValue(); + if (Q_UNLIKELY(add_overflow(a, b, &result))) { + if (traceInfo) + *traceInfo |= quint8(QV4::ObservedTraceValues::Double); + return Value::fromDouble(static_cast<double>(a) + b).asReturnedValue(); + } + if (traceInfo) + *traceInfo |= quint8(QV4::ObservedTraceValues::Integer); + return Value::fromInt32(result).asReturnedValue(); } -static inline QMLJS_READONLY ReturnedValue sub_int32(int a, int b) +static inline QMLJS_READONLY ReturnedValue sub_int32(int a, int b, quint8 *traceInfo = nullptr) { int result; - if (Q_UNLIKELY(sub_overflow(a, b, &result))) - return Primitive::fromDouble(static_cast<double>(a) - b).asReturnedValue(); - return Primitive::fromInt32(result).asReturnedValue(); + if (Q_UNLIKELY(sub_overflow(a, b, &result))) { + if (traceInfo) + *traceInfo |= quint8(QV4::ObservedTraceValues::Double); + return Value::fromDouble(static_cast<double>(a) - b).asReturnedValue(); + } + if (traceInfo) + *traceInfo |= quint8(QV4::ObservedTraceValues::Integer); + return Value::fromInt32(result).asReturnedValue(); } -static inline QMLJS_READONLY ReturnedValue mul_int32(int a, int b) +static inline QMLJS_READONLY ReturnedValue mul_int32(int a, int b, quint8 *traceInfo = nullptr) { int result; - if (Q_UNLIKELY(mul_overflow(a, b, &result))) - return Primitive::fromDouble(static_cast<double>(a) * b).asReturnedValue(); - return Primitive::fromInt32(result).asReturnedValue(); + if (Q_UNLIKELY(mul_overflow(a, b, &result))) { + if (traceInfo) + *traceInfo |= quint8(QV4::ObservedTraceValues::Double); + return Value::fromDouble(static_cast<double>(a) * b).asReturnedValue(); + } + if (traceInfo) + *traceInfo |= quint8(QV4::ObservedTraceValues::Integer); + return Value::fromInt32(result).asReturnedValue(); } } diff --git a/src/qml/jsruntime/qv4mathobject.cpp b/src/qml/jsruntime/qv4mathobject.cpp index 2d9d81c64b..07440047d4 100644 --- a/src/qml/jsruntime/qv4mathobject.cpp +++ b/src/qml/jsruntime/qv4mathobject.cpp @@ -39,12 +39,15 @@ #include "qv4mathobject_p.h" #include "qv4objectproto_p.h" +#include "qv4symbol_p.h" #include <QtCore/qdatetime.h> #include <QtCore/qmath.h> +#include <QtCore/qrandom.h> #include <QtCore/private/qnumeric_p.h> #include <QtCore/qthreadstorage.h> +#include <math.h> #include <cmath> using namespace QV4; @@ -57,25 +60,38 @@ void Heap::MathObject::init() Scope scope(internalClass->engine); ScopedObject m(scope, this); - m->defineReadonlyProperty(QStringLiteral("E"), Primitive::fromDouble(M_E)); - m->defineReadonlyProperty(QStringLiteral("LN2"), Primitive::fromDouble(M_LN2)); - m->defineReadonlyProperty(QStringLiteral("LN10"), Primitive::fromDouble(M_LN10)); - m->defineReadonlyProperty(QStringLiteral("LOG2E"), Primitive::fromDouble(M_LOG2E)); - m->defineReadonlyProperty(QStringLiteral("LOG10E"), Primitive::fromDouble(M_LOG10E)); - m->defineReadonlyProperty(QStringLiteral("PI"), Primitive::fromDouble(M_PI)); - m->defineReadonlyProperty(QStringLiteral("SQRT1_2"), Primitive::fromDouble(M_SQRT1_2)); - m->defineReadonlyProperty(QStringLiteral("SQRT2"), Primitive::fromDouble(M_SQRT2)); + m->defineReadonlyProperty(QStringLiteral("E"), Value::fromDouble(M_E)); + m->defineReadonlyProperty(QStringLiteral("LN2"), Value::fromDouble(M_LN2)); + m->defineReadonlyProperty(QStringLiteral("LN10"), Value::fromDouble(M_LN10)); + m->defineReadonlyProperty(QStringLiteral("LOG2E"), Value::fromDouble(M_LOG2E)); + m->defineReadonlyProperty(QStringLiteral("LOG10E"), Value::fromDouble(M_LOG10E)); + m->defineReadonlyProperty(QStringLiteral("PI"), Value::fromDouble(M_PI)); + m->defineReadonlyProperty(QStringLiteral("SQRT1_2"), Value::fromDouble(M_SQRT1_2)); + m->defineReadonlyProperty(QStringLiteral("SQRT2"), Value::fromDouble(M_SQRT2)); m->defineDefaultProperty(QStringLiteral("abs"), QV4::MathObject::method_abs, 1); m->defineDefaultProperty(QStringLiteral("acos"), QV4::MathObject::method_acos, 1); - m->defineDefaultProperty(QStringLiteral("asin"), QV4::MathObject::method_asin, 0); + m->defineDefaultProperty(QStringLiteral("acosh"), QV4::MathObject::method_acosh, 1); + m->defineDefaultProperty(QStringLiteral("asin"), QV4::MathObject::method_asin, 1); + m->defineDefaultProperty(QStringLiteral("asinh"), QV4::MathObject::method_asinh, 1); m->defineDefaultProperty(QStringLiteral("atan"), QV4::MathObject::method_atan, 1); + m->defineDefaultProperty(QStringLiteral("atanh"), QV4::MathObject::method_atanh, 1); m->defineDefaultProperty(QStringLiteral("atan2"), QV4::MathObject::method_atan2, 2); + m->defineDefaultProperty(QStringLiteral("cbrt"), QV4::MathObject::method_cbrt, 1); m->defineDefaultProperty(QStringLiteral("ceil"), QV4::MathObject::method_ceil, 1); + m->defineDefaultProperty(QStringLiteral("clz32"), QV4::MathObject::method_clz32, 1); m->defineDefaultProperty(QStringLiteral("cos"), QV4::MathObject::method_cos, 1); + m->defineDefaultProperty(QStringLiteral("cosh"), QV4::MathObject::method_cosh, 1); m->defineDefaultProperty(QStringLiteral("exp"), QV4::MathObject::method_exp, 1); + m->defineDefaultProperty(QStringLiteral("expm1"), QV4::MathObject::method_expm1, 1); m->defineDefaultProperty(QStringLiteral("floor"), QV4::MathObject::method_floor, 1); + m->defineDefaultProperty(QStringLiteral("fround"), QV4::MathObject::method_fround, 1); + m->defineDefaultProperty(QStringLiteral("hypot"), QV4::MathObject::method_hypot, 2); + m->defineDefaultProperty(QStringLiteral("imul"), QV4::MathObject::method_imul, 2); m->defineDefaultProperty(QStringLiteral("log"), QV4::MathObject::method_log, 1); + m->defineDefaultProperty(QStringLiteral("log10"), QV4::MathObject::method_log10, 1); + m->defineDefaultProperty(QStringLiteral("log1p"), QV4::MathObject::method_log1p, 1); + m->defineDefaultProperty(QStringLiteral("log2"), QV4::MathObject::method_log2, 1); m->defineDefaultProperty(QStringLiteral("max"), QV4::MathObject::method_max, 2); m->defineDefaultProperty(QStringLiteral("min"), QV4::MathObject::method_min, 2); m->defineDefaultProperty(QStringLiteral("pow"), QV4::MathObject::method_pow, 2); @@ -83,8 +99,14 @@ void Heap::MathObject::init() m->defineDefaultProperty(QStringLiteral("round"), QV4::MathObject::method_round, 1); m->defineDefaultProperty(QStringLiteral("sign"), QV4::MathObject::method_sign, 1); m->defineDefaultProperty(QStringLiteral("sin"), QV4::MathObject::method_sin, 1); + m->defineDefaultProperty(QStringLiteral("sinh"), QV4::MathObject::method_sinh, 1); m->defineDefaultProperty(QStringLiteral("sqrt"), QV4::MathObject::method_sqrt, 1); m->defineDefaultProperty(QStringLiteral("tan"), QV4::MathObject::method_tan, 1); + m->defineDefaultProperty(QStringLiteral("tanh"), QV4::MathObject::method_tanh, 1); + m->defineDefaultProperty(QStringLiteral("trunc"), QV4::MathObject::method_trunc, 1); + + ScopedString name(scope, scope.engine->newString(QStringLiteral("Math"))); + m->defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), name); } static Q_ALWAYS_INLINE double copySign(double x, double y) @@ -92,54 +114,99 @@ static Q_ALWAYS_INLINE double copySign(double x, double y) return ::copysign(x, y); } -void MathObject::method_abs(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_abs(const FunctionObject *, const Value *, const Value *argv, int argc) { - if (!callData->argc) + if (!argc) RETURN_RESULT(Encode(qt_qnan())); - if (callData->args[0].isInteger()) { - int i = callData->args[0].integerValue(); + if (argv[0].isInteger()) { + int i = argv[0].integerValue(); RETURN_RESULT(Encode(i < 0 ? - i : i)); } - double v = callData->args[0].toNumber(); + double v = argv[0].toNumber(); if (v == 0) // 0 | -0 RETURN_RESULT(Encode(0)); RETURN_RESULT(Encode(v < 0 ? -v : v)); } -void MathObject::method_acos(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_acos(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : 2; + double v = argc ? argv[0].toNumber() : 2; if (v > 1) RETURN_RESULT(Encode(qt_qnan())); RETURN_RESULT(Encode(std::acos(v))); } -void MathObject::method_asin(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_acosh(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : 2; + if (v < 1) + RETURN_RESULT(Encode(qt_qnan())); + +#ifdef Q_OS_ANDROID // incomplete std :-( + RETURN_RESULT(Encode(std::log(v +std::sqrt(v + 1) * std::sqrt(v - 1)))); +#else + RETURN_RESULT(Encode(std::acosh(v))); +#endif +} + +ReturnedValue MathObject::method_asin(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : 2; + double v = argc ? argv[0].toNumber() : 2; if (v > 1) RETURN_RESULT(Encode(qt_qnan())); else RETURN_RESULT(Encode(std::asin(v))); } -void MathObject::method_atan(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_asinh(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + double v = argc ? argv[0].toNumber() : 2; + if (v == 0.0) + RETURN_RESULT(Encode(v)); + +#ifdef Q_OS_ANDROID // incomplete std :-( + RETURN_RESULT(Encode(std::log(v +std::sqrt(1 + v * v)))); +#else + RETURN_RESULT(Encode(std::asinh(v))); +#endif +} + +ReturnedValue MathObject::method_atan(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); if (v == 0.0) RETURN_RESULT(Encode(v)); else RETURN_RESULT(Encode(std::atan(v))); } -void MathObject::method_atan2(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_atanh(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (v == 0.0) + RETURN_RESULT(Encode(v)); + +#ifdef Q_OS_ANDROID // incomplete std :-( + if (-1 < v && v < 1) + RETURN_RESULT(Encode(0.5 * (std::log(v + 1) - std::log(v - 1)))); + + if (v > 1 || v < -1) + RETURN_RESULT(Encode(qt_qnan())); + + RETURN_RESULT(Encode(copySign(qt_inf(), v))); +#else + RETURN_RESULT(Encode(std::atanh(v))); +#endif +} + +ReturnedValue MathObject::method_atan2(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v1 = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - double v2 = callData->argc > 1 ? callData->args[1].toNumber() : qt_qnan(); + double v1 = argc ? argv[0].toNumber() : qt_qnan(); + double v2 = argc > 1 ? argv[1].toNumber() : qt_qnan(); if ((v1 < 0) && qt_is_finite(v1) && qt_is_inf(v2) && (copySign(1.0, v2) == 1.0)) RETURN_RESULT(Encode(copySign(0, -1.0))); @@ -154,24 +221,46 @@ void MathObject::method_atan2(const BuiltinFunction *, Scope &scope, CallData *c RETURN_RESULT(Encode(std::atan2(v1, v2))); } -void MathObject::method_ceil(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_cbrt(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); +#ifdef Q_OS_ANDROID // incomplete std :-( + RETURN_RESULT(Encode(copySign(std::exp(std::log(std::abs(v)) / 3), v))); +#else + RETURN_RESULT(Encode(std::cbrt(v))); // cube root +#endif +} + +ReturnedValue MathObject::method_ceil(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + double v = argc ? argv[0].toNumber() : qt_qnan(); if (v < 0.0 && v > -1.0) RETURN_RESULT(Encode(copySign(0, -1.0))); else RETURN_RESULT(Encode(std::ceil(v))); } -void MathObject::method_cos(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_clz32(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + quint32 v = argc ? argv[0].toUInt32() : 0; + RETURN_RESULT(Encode(qint32(qCountLeadingZeroBits(v)))); +} + +ReturnedValue MathObject::method_cos(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); RETURN_RESULT(Encode(std::cos(v))); } -void MathObject::method_exp(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_cosh(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + double v = argc ? argv[0].toNumber() : qt_qnan(); + RETURN_RESULT(Encode(std::cosh(v))); +} + +ReturnedValue MathObject::method_exp(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); if (qt_is_inf(v)) { if (copySign(1.0, v) == -1.0) RETURN_RESULT(Encode(0)); @@ -182,49 +271,156 @@ void MathObject::method_exp(const BuiltinFunction *, Scope &scope, CallData *cal } } -void MathObject::method_floor(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_expm1(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (std::isnan(v) || qIsNull(v)) { + RETURN_RESULT(Encode(v)); + } else if (qt_is_inf(v)) { + if (copySign(1.0, v) == -1.0) + RETURN_RESULT(Encode(-1.0)); + else + RETURN_RESULT(Encode(qt_inf())); + } else { +#ifdef Q_OS_ANDROID // incomplete std :-( + RETURN_RESULT(Encode(std::exp(v) - 1)); +#else + RETURN_RESULT(Encode(std::expm1(v))); +#endif + } +} + +ReturnedValue MathObject::method_floor(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); + Value result = Value::fromDouble(std::floor(v)); + result.isInt32(); + RETURN_RESULT(result); +} + +ReturnedValue MathObject::method_fround(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (std::isnan(v) || qt_is_inf(v) || qIsNull(v)) + RETURN_RESULT(Encode(v)); + else // convert to 32-bit float using roundTiesToEven, then convert back to 64-bit double + RETURN_RESULT(Encode(double(float(v)))); +} + +ReturnedValue MathObject::method_hypot(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + // ES6 Math.hypot(v1, ..., vn) -> sqrt(sum(vi**2)) but "should take care to + // avoid the loss of precision from overflows and underflows" (as std::hypot does). + double v = argc ? argv[0].toNumber() : 0; + // Spec mandates +0 on no args; and says nothing about what to do if toNumber() signals ... +#ifdef Q_OS_ANDROID // incomplete std :-( + bool big = qt_is_inf(v), bad = std::isnan(v); + v *= v; + for (int i = 1; !big && i < argc; i++) { + double u = argv[i].toNumber(); + if (qt_is_inf(u)) + big = true; + if (std::isnan(u)) + bad = true; + v += u * u; + } + if (big) + RETURN_RESULT(Encode(qt_inf())); + if (bad) + RETURN_RESULT(Encode(qt_qnan())); + // Should actually check for {und,ov}erflow, but too fiddly ! + RETURN_RESULT(Value::fromDouble(sqrt(v))); +#else + for (int i = 1; i < argc; i++) + v = std::hypot(v, argv[i].toNumber()); +#endif + RETURN_RESULT(Value::fromDouble(v)); +} + +ReturnedValue MathObject::method_imul(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - RETURN_RESULT(Encode(std::floor(v))); + quint32 a = argc ? argv[0].toUInt32() : 0; + quint32 b = argc > 0 ? argv[1].toUInt32() : 0; + qint32 product = a * b; + RETURN_RESULT(Encode(product)); } -void MathObject::method_log(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_log(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + double v = argc ? argv[0].toNumber() : qt_qnan(); if (v < 0) RETURN_RESULT(Encode(qt_qnan())); else RETURN_RESULT(Encode(std::log(v))); } -void MathObject::method_max(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_log10(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (v < 0) + RETURN_RESULT(Encode(qt_qnan())); + else + RETURN_RESULT(Encode(std::log10(v))); +} + +ReturnedValue MathObject::method_log1p(const FunctionObject *, const Value *, const Value *argv, int argc) +{ +#if !defined(__ANDROID__) + using std::log1p; +#endif + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (v < -1) + RETURN_RESULT(Encode(qt_qnan())); + else + RETURN_RESULT(Encode(log1p(v))); +} + +ReturnedValue MathObject::method_log2(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (v < 0) { + RETURN_RESULT(Encode(qt_qnan())); + } else { +#ifdef Q_OS_ANDROID // incomplete std :-( + // Android ndk r10e doesn't have std::log2, so fall back. + const double ln2 = std::log(2.0); + RETURN_RESULT(Encode(std::log(v) / ln2)); +#else + RETURN_RESULT(Encode(std::log2(v))); +#endif + } +} + +ReturnedValue MathObject::method_max(const FunctionObject *, const Value *, const Value *argv, int argc) { double mx = -qt_inf(); - for (int i = 0; i < callData->argc; ++i) { - double x = callData->args[i].toNumber(); - if (x > mx || std::isnan(x)) + for (int i = 0, ei = argc; i < ei; ++i) { + double x = argv[i].toNumber(); + if ((x == 0 && mx == x && copySign(1.0, x) == 1.0) + || (x > mx) || std::isnan(x)) { mx = x; + } } - RETURN_RESULT(Encode(mx)); + RETURN_RESULT(Encode::smallestNumber(mx)); } -void MathObject::method_min(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_min(const FunctionObject *, const Value *, const Value *argv, int argc) { double mx = qt_inf(); - for (int i = 0; i < callData->argc; ++i) { - double x = callData->args[i].toNumber(); + for (int i = 0, ei = argc; i < ei; ++i) { + double x = argv[i].toNumber(); if ((x == 0 && mx == x && copySign(1.0, x) == -1.0) || (x < mx) || std::isnan(x)) { mx = x; } } - RETURN_RESULT(Encode(mx)); + RETURN_RESULT(Encode::smallestNumber(mx)); } -void MathObject::method_pow(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_pow(const FunctionObject *, const Value *, const Value *argv, int argc) { - double x = callData->argc > 0 ? callData->args[0].toNumber() : qt_qnan(); - double y = callData->argc > 1 ? callData->args[1].toNumber() : qt_qnan(); + double x = argc > 0 ? argv[0].toNumber() : qt_qnan(); + double y = argc > 1 ? argv[1].toNumber() : qt_qnan(); if (std::isnan(y)) RETURN_RESULT(Encode(qt_qnan())); @@ -271,32 +467,24 @@ void MathObject::method_pow(const BuiltinFunction *, Scope &scope, CallData *cal RETURN_RESULT(Encode(qt_qnan())); } -Q_GLOBAL_STATIC(QThreadStorage<bool *>, seedCreatedStorage); - -void MathObject::method_random(const BuiltinFunction *, Scope &scope, CallData *) +ReturnedValue MathObject::method_random(const FunctionObject *, const Value *, const Value *, int) { - if (!seedCreatedStorage()->hasLocalData()) { - int msecs = QTime(0,0,0).msecsTo(QTime::currentTime()); - Q_ASSERT(msecs >= 0); - qsrand(uint(uint(msecs) ^ reinterpret_cast<quintptr>(scope.engine))); - seedCreatedStorage()->setLocalData(new bool(true)); - } - // rand()/qrand() return a value where the upperbound is RAND_MAX inclusive. So, instead of - // dividing by RAND_MAX (which would return 0..RAND_MAX inclusive), we divide by RAND_MAX + 1. - qint64 upperLimit = qint64(RAND_MAX) + 1; - RETURN_RESULT(Encode(qrand() / double(upperLimit))); + RETURN_RESULT(Encode(QRandomGenerator::global()->generateDouble())); } -void MathObject::method_round(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_round(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - v = copySign(std::floor(v + 0.5), v); - RETURN_RESULT(Encode(v)); + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (std::isnan(v) || qt_is_inf(v) || qIsNull(v)) + RETURN_RESULT(Encode(v)); + + v = copySign(std::floor(v + 0.5), v); + RETURN_RESULT(Encode(v)); } -void MathObject::method_sign(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_sign(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + double v = argc ? argv[0].toNumber() : qt_qnan(); if (std::isnan(v)) RETURN_RESULT(Encode(qt_qnan())); @@ -307,24 +495,58 @@ void MathObject::method_sign(const BuiltinFunction *, Scope &scope, CallData *ca RETURN_RESULT(Encode(std::signbit(v) ? -1 : 1)); } -void MathObject::method_sin(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_sin(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); - RETURN_RESULT(Encode(std::sin(v))); + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (v == 0.0) + RETURN_RESULT(Encode(v)); + else + RETURN_RESULT(Encode(std::sin(v))); } -void MathObject::method_sqrt(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_sinh(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (v == 0.0) + RETURN_RESULT(Encode(v)); + else + RETURN_RESULT(Encode(std::sinh(v))); +} + +ReturnedValue MathObject::method_sqrt(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); RETURN_RESULT(Encode(std::sqrt(v))); } -void MathObject::method_tan(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue MathObject::method_tan(const FunctionObject *, const Value *, const Value *argv, int argc) { - double v = callData->argc ? callData->args[0].toNumber() : qt_qnan(); + double v = argc ? argv[0].toNumber() : qt_qnan(); if (v == 0.0) RETURN_RESULT(Encode(v)); else RETURN_RESULT(Encode(std::tan(v))); } +ReturnedValue MathObject::method_tanh(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); + if (v == 0.0) + RETURN_RESULT(Encode(v)); + else + RETURN_RESULT(Encode(std::tanh(v))); +} + +ReturnedValue MathObject::method_trunc(const FunctionObject *, const Value *, const Value *argv, int argc) +{ + double v = argc ? argv[0].toNumber() : qt_qnan(); +#ifdef Q_OS_ANDROID // incomplete std :-( + if (std::isnan(v) || qt_is_inf(v) || qIsNull(v)) + RETURN_RESULT(Encode(v)); + // Nearest integer not greater in magnitude: + quint64 whole = std::abs(v); + RETURN_RESULT(Encode(copySign(whole, v))); +#else + RETURN_RESULT(Encode(std::trunc(v))); +#endif +} diff --git a/src/qml/jsruntime/qv4mathobject_p.h b/src/qml/jsruntime/qv4mathobject_p.h index e617712905..2658e25438 100644 --- a/src/qml/jsruntime/qv4mathobject_p.h +++ b/src/qml/jsruntime/qv4mathobject_p.h @@ -69,25 +69,41 @@ struct MathObject: Object V4_OBJECT2(MathObject, Object) Q_MANAGED_TYPE(MathObject) - static void method_abs(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_acos(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_asin(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_atan(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_atan2(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_ceil(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_cos(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_exp(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_floor(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_log(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_max(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_min(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_pow(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_random(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_round(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_sign(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_sin(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_sqrt(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_tan(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_abs(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_acos(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_acosh(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_asin(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_asinh(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_atan(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_atanh(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_atan2(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_cbrt(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_ceil(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_clz32(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_cos(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_cosh(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_exp(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_expm1(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_floor(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_fround(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_hypot(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_imul(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_log(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_log10(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_log1p(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_log2(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_max(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_min(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_pow(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_random(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_round(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_sign(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_sin(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_sinh(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_sqrt(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_tan(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_tanh(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_trunc(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; } diff --git a/src/qml/jsruntime/qv4memberdata.cpp b/src/qml/jsruntime/qv4memberdata.cpp index 8f862d63e9..246f857643 100644 --- a/src/qml/jsruntime/qv4memberdata.cpp +++ b/src/qml/jsruntime/qv4memberdata.cpp @@ -45,12 +45,30 @@ using namespace QV4; DEFINE_MANAGED_VTABLE(MemberData); +static size_t nextPowerOfTwo(size_t s) +{ + --s; + s |= s >> 1; + s |= s >> 2; + s |= s >> 4; + s |= s >> 8; + s |= s >> 16; +#if (QT_POINTER_SIZE == 8) + s |= s >> 32; +#endif + ++s; + return s; +} + Heap::MemberData *MemberData::allocate(ExecutionEngine *e, uint n, Heap::MemberData *old) { - Q_ASSERT(!old || old->values.size < n); - Q_ASSERT(n); + Q_ASSERT(!old || old->values.size <= n); + if (!n) + n = 4; size_t alloc = MemoryManager::align(sizeof(Heap::MemberData) + (n - 1)*sizeof(Value)); + // round up to next power of two to avoid quadratic behaviour for very large objects + alloc = nextPowerOfTwo(alloc); Heap::MemberData *m = e->memoryManager->allocManaged<MemberData>(alloc); if (old) // no write barrier required here diff --git a/src/qml/jsruntime/qv4memberdata_p.h b/src/qml/jsruntime/qv4memberdata_p.h index 9d333011fc..186083b83a 100644 --- a/src/qml/jsruntime/qv4memberdata_p.h +++ b/src/qml/jsruntime/qv4memberdata_p.h @@ -63,9 +63,9 @@ namespace Heap { Member(class, ValueArray, ValueArray, values) DECLARE_HEAP_OBJECT(MemberData, Base) { - DECLARE_MARK_TABLE(MemberData); + DECLARE_MARKOBJECTS(MemberData); }; -V4_ASSERT_IS_TRIVIAL(MemberData) +Q_STATIC_ASSERT(std::is_trivial< MemberData >::value); } @@ -74,26 +74,14 @@ struct MemberData : Managed V4_MANAGED(MemberData, Managed) V4_INTERNALCLASS(MemberData) - struct Index { - Heap::Base *base; - Value *slot; - - void set(EngineBase *e, Value newVal) { - WriteBarrier::write(e, base, slot, newVal); - } - const Value *operator->() const { return slot; } - const Value &operator*() const { return *slot; } - bool isNull() const { return !slot; } - }; - const Value &operator[] (uint idx) const { return d()->values[idx]; } const Value *data() const { return d()->values.data(); } - void set(ExecutionEngine *e, uint index, Value v) { d()->values.set(e, index, v); } - void set(ExecutionEngine *e, uint index, Heap::Base *b) { d()->values.set(e, index, b); } + void set(EngineBase *e, uint index, Value v) { d()->values.set(e, index, v); } + void set(EngineBase *e, uint index, Heap::Base *b) { d()->values.set(e, index, b); } inline uint size() const { return d()->values.size; } - static Heap::MemberData *allocate(QV4::ExecutionEngine *e, uint n, Heap::MemberData *old = 0); + static Heap::MemberData *allocate(QV4::ExecutionEngine *e, uint n, Heap::MemberData *old = nullptr); }; } diff --git a/src/qml/jsruntime/qv4module.cpp b/src/qml/jsruntime/qv4module.cpp new file mode 100644 index 0000000000..237ada8321 --- /dev/null +++ b/src/qml/jsruntime/qv4module.cpp @@ -0,0 +1,282 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qv4module_p.h" + +#include <private/qv4mm_p.h> +#include <private/qv4vme_moth_p.h> +#include <private/qv4context_p.h> +#include <private/qv4symbol_p.h> +#include <private/qv4identifiertable_p.h> + +#include <QScopeGuard> + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(Module); + +void Heap::Module::init(ExecutionEngine *engine, CompiledData::CompilationUnit *moduleUnit) +{ + Object::init(); + + // This is a back pointer and there is no need to call addref() on the unit, because the unit + // owns this object instead. + unit = moduleUnit; + self.set(engine, this); + + Function *moduleFunction = unit->runtimeFunctions[unit->unitData()->indexOfRootFunction]; + + const uint locals = moduleFunction->compiledFunction->nLocals; + const size_t requiredMemory = sizeof(QV4::CallContext::Data) - sizeof(Value) + sizeof(Value) * locals; + scope.set(engine, engine->memoryManager->allocManaged<QV4::CallContext>(requiredMemory, moduleFunction->internalClass)); + scope->init(); + scope->outer.set(engine, engine->rootContext()->d()); + scope->locals.size = locals; + scope->locals.alloc = locals; + scope->nArgs = 0; + + // Prepare the temporal dead zone + scope->setupLocalTemporalDeadZone(moduleFunction->compiledFunction); + + Scope valueScope(engine); + + // It's possible for example to re-export an import, for example: + // import * as foo from "./bar.js" + // export { foo } + // Since we don't add imports to the locals, it won't be found typically. + // Except now we add imports at the end of the internal class in the index + // space past the locals, so that resolveExport can find it. + { + Scoped<QV4::InternalClass> ic(valueScope, scope->internalClass); + + for (uint i = 0; i < unit->data->importEntryTableSize; ++i) { + const CompiledData::ImportEntry &import = unit->data->importEntryTable()[i]; + ic = ic->addMember(engine->identifierTable->asPropertyKey(unit->runtimeStrings[import.localName]), Attr_NotConfigurable); + } + scope->internalClass.set(engine, ic->d()); + } + + + Scoped<QV4::Module> This(valueScope, this); + ScopedString name(valueScope, engine->newString(QStringLiteral("Module"))); + This->insertMember(engine->symbol_toStringTag(), name, Attr_ReadOnly); + This->setPrototypeUnchecked(nullptr); +} + +void Module::evaluate() +{ + if (d()->evaluated) + return; + d()->evaluated = true; + + CompiledData::CompilationUnit *unit = d()->unit; + + unit->evaluateModuleRequests(); + + ExecutionEngine *v4 = engine(); + Function *moduleFunction = unit->runtimeFunctions[unit->data->indexOfRootFunction]; + CppStackFrame frame; + frame.init(v4, moduleFunction, nullptr, 0); + frame.setupJSFrame(v4->jsStackTop, Value::undefinedValue(), d()->scope, + Value::undefinedValue(), Value::undefinedValue()); + + frame.push(); + v4->jsStackTop += frame.requiredJSStackFrameSize(); + auto frameCleanup = qScopeGuard([&frame]() { + frame.pop(); + }); + Moth::VME::exec(&frame, v4); +} + +const Value *Module::resolveExport(PropertyKey id) const +{ + if (d()->unit->isESModule()) { + if (!id.isString()) + return nullptr; + Scope scope(engine()); + ScopedString name(scope, id.asStringOrSymbol()); + return d()->unit->resolveExport(name); + } else { + InternalClassEntry entry = d()->scope->internalClass->find(id); + if (entry.isValid()) + return &d()->scope->locals[entry.index]; + return nullptr; + } +} + +ReturnedValue Module::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) +{ + if (id.isSymbol()) + return Object::virtualGet(m, id, receiver, hasProperty); + + const Module *module = static_cast<const Module *>(m); + const Value *v = module->resolveExport(id); + if (hasProperty) + *hasProperty = v != nullptr; + if (!v) + return Encode::undefined(); + if (v->isEmpty()) { + Scope scope(m->engine()); + ScopedValue propName(scope, id.toStringOrSymbol(scope.engine)); + return scope.engine->throwReferenceError(propName); + } + return v->asReturnedValue(); +} + +PropertyAttributes Module::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) +{ + if (id.isSymbol()) + return Object::virtualGetOwnProperty(m, id, p); + + const Module *module = static_cast<const Module *>(m); + const Value *v = module->resolveExport(id); + if (!v) { + if (p) + p->value = Encode::undefined(); + return Attr_Invalid; + } + if (p) + p->value = v->isEmpty() ? Encode::undefined() : v->asReturnedValue(); + if (v->isEmpty()) { + Scope scope(m->engine()); + ScopedValue propName(scope, id.toStringOrSymbol(scope.engine)); + scope.engine->throwReferenceError(propName); + } + return Attr_Data | Attr_NotConfigurable; +} + +bool Module::virtualHasProperty(const Managed *m, PropertyKey id) +{ + if (id.isSymbol()) + return Object::virtualHasProperty(m, id); + + const Module *module = static_cast<const Module *>(m); + const Value *v = module->resolveExport(id); + return v != nullptr; +} + +bool Module::virtualPreventExtensions(Managed *) +{ + return true; +} + +bool Module::virtualDefineOwnProperty(Managed *, PropertyKey, const Property *, PropertyAttributes) +{ + return false; +} + +bool Module::virtualPut(Managed *, PropertyKey, const Value &, Value *) +{ + return false; +} + +bool Module::virtualDeleteProperty(Managed *m, PropertyKey id) +{ + if (id.isSymbol()) + return Object::virtualDeleteProperty(m, id); + const Module *module = static_cast<const Module *>(m); + const Value *v = module->resolveExport(id); + if (v) + return false; + return true; +} + +struct ModuleNamespaceIterator : ObjectOwnPropertyKeyIterator +{ + QStringList exportedNames; + int exportIndex = 0; + ModuleNamespaceIterator(const QStringList &names) : exportedNames(names) {} + ~ModuleNamespaceIterator() override = default; + PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; + +}; + +PropertyKey ModuleNamespaceIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) +{ + const Module *module = static_cast<const Module *>(o); + if (exportIndex < exportedNames.count()) { + if (attrs) + *attrs = Attr_Data; + Scope scope(module->engine()); + ScopedString exportName(scope, scope.engine->newString(exportedNames.at(exportIndex))); + exportIndex++; + const Value *v = module->resolveExport(exportName->toPropertyKey()); + if (pd) { + if (v->isEmpty()) + scope.engine->throwReferenceError(exportName); + else + pd->value = *v; + } + return exportName->toPropertyKey(); + } + return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); +} + +OwnPropertyKeyIterator *Module::virtualOwnPropertyKeys(const Object *o, Value *target) +{ + const Module *module = static_cast<const Module *>(o); + *target = *o; + + QStringList names; + if (module->d()->unit->isESModule()) { + names = module->d()->unit->exportedNames(); + } else { + Heap::InternalClass *scopeClass = module->d()->scope->internalClass; + for (uint i = 0; i < scopeClass->size; ++i) + names << scopeClass->keyAt(i); + } + + return new ModuleNamespaceIterator(names); +} + +Heap::Object *Module::virtualGetPrototypeOf(const Managed *) +{ + return nullptr; +} + +bool Module::virtualSetPrototypeOf(Managed *, const Object *proto) +{ + return proto == nullptr; +} + +bool Module::virtualIsExtensible(const Managed *) +{ + return false; +} diff --git a/src/qml/jsruntime/qv4module_p.h b/src/qml/jsruntime/qv4module_p.h new file mode 100644 index 0000000000..dca0678fe9 --- /dev/null +++ b/src/qml/jsruntime/qv4module_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4MODULE +#define QV4MODULE + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4context_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Heap { + +#define ModuleMembers(class, Member) \ + Member(class, NoMark, CompiledData::CompilationUnit *, unit) \ + Member(class, Pointer, CallContext *, scope) \ + Member(class, HeapValue, HeapValue, self) \ + Member(class, NoMark, bool, evaluated) + +DECLARE_EXPORTED_HEAP_OBJECT(Module, Object) { + DECLARE_MARKOBJECTS(Module) + + void init(ExecutionEngine *engine, CompiledData::CompilationUnit *moduleUnit); +}; + +} + +struct Q_QML_EXPORT Module : public Object { + V4_OBJECT2(Module, Object) + + void evaluate(); + const Value *resolveExport(PropertyKey key) const; + + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); + static bool virtualHasProperty(const Managed *m, PropertyKey id); + static bool virtualPreventExtensions(Managed *); + static bool virtualDefineOwnProperty(Managed *, PropertyKey, const Property *, PropertyAttributes); + static bool virtualPut(Managed *, PropertyKey, const Value &, Value *); + static bool virtualDeleteProperty(Managed *m, PropertyKey id); + static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); + static Heap::Object *virtualGetPrototypeOf(const Managed *); + static bool virtualSetPrototypeOf(Managed *, const Object *proto); + static bool virtualIsExtensible(const Managed *); +}; + +} + +QT_END_NAMESPACE + +#endif // QV4MODULE diff --git a/src/qml/jsruntime/qv4numberobject.cpp b/src/qml/jsruntime/qv4numberobject.cpp index 8cfa930888..13f6912371 100644 --- a/src/qml/jsruntime/qv4numberobject.cpp +++ b/src/qml/jsruntime/qv4numberobject.cpp @@ -78,16 +78,24 @@ void Heap::NumberCtor::init(QV4::ExecutionContext *scope) Heap::FunctionObject::init(scope, QStringLiteral("Number")); } -void NumberCtor::construct(const Managed *, Scope &scope, CallData *callData) +ReturnedValue NumberCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - double dbl = callData->argc ? callData->args[0].toNumber() : 0.; - scope.result = Encode(scope.engine->newNumberObject(dbl)); + auto v4 = f->engine(); + double dbl = argc ? argv[0].toNumber() : 0.; + + ReturnedValue o = Encode(f->engine()->newNumberObject(dbl)); + if (!newTarget) + return o; + Scope scope(v4); + ScopedObject obj(scope, o); + obj->setProtoFromNewTarget(newTarget); + return obj->asReturnedValue(); } -void NumberCtor::call(const Managed *, Scope &scope, CallData *callData) +ReturnedValue NumberCtor::virtualCall(const FunctionObject *, const Value *, const Value *argv, int argc) { - double dbl = callData->argc ? callData->args[0].toNumber() : 0.; - scope.result = Encode(dbl); + double dbl = argc ? argv[0].toNumber() : 0.; + return Encode(dbl); } void NumberPrototype::init(ExecutionEngine *engine, Object *ctor) @@ -95,19 +103,19 @@ void NumberPrototype::init(ExecutionEngine *engine, Object *ctor) Scope scope(engine); ScopedObject o(scope); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); - ctor->defineReadonlyProperty(QStringLiteral("NaN"), Primitive::fromDouble(qt_qnan())); - ctor->defineReadonlyProperty(QStringLiteral("NEGATIVE_INFINITY"), Primitive::fromDouble(-qInf())); - ctor->defineReadonlyProperty(QStringLiteral("POSITIVE_INFINITY"), Primitive::fromDouble(qInf())); - ctor->defineReadonlyProperty(QStringLiteral("MAX_VALUE"), Primitive::fromDouble(1.7976931348623158e+308)); - ctor->defineReadonlyProperty(QStringLiteral("EPSILON"), Primitive::fromDouble(std::numeric_limits<double>::epsilon())); - ctor->defineReadonlyProperty(QStringLiteral("MAX_SAFE_INTEGER"), Primitive::fromDouble(9007199254740991)); - ctor->defineReadonlyProperty(QStringLiteral("MIN_SAFE_INTEGER"), Primitive::fromDouble(-9007199254740991)); + ctor->defineReadonlyProperty(QStringLiteral("NaN"), Value::fromDouble(qt_qnan())); + ctor->defineReadonlyProperty(QStringLiteral("NEGATIVE_INFINITY"), Value::fromDouble(-qInf())); + ctor->defineReadonlyProperty(QStringLiteral("POSITIVE_INFINITY"), Value::fromDouble(qInf())); + ctor->defineReadonlyProperty(QStringLiteral("MAX_VALUE"), Value::fromDouble(1.7976931348623158e+308)); + ctor->defineReadonlyProperty(QStringLiteral("EPSILON"), Value::fromDouble(std::numeric_limits<double>::epsilon())); + ctor->defineReadonlyProperty(QStringLiteral("MAX_SAFE_INTEGER"), Value::fromDouble(9007199254740991)); + ctor->defineReadonlyProperty(QStringLiteral("MIN_SAFE_INTEGER"), Value::fromDouble(-9007199254740991)); QT_WARNING_PUSH QT_WARNING_DISABLE_INTEL(239) - ctor->defineReadonlyProperty(QStringLiteral("MIN_VALUE"), Primitive::fromDouble(5e-324)); + ctor->defineReadonlyProperty(QStringLiteral("MIN_VALUE"), Value::fromDouble(5e-324)); QT_WARNING_POP ctor->defineDefaultProperty(QStringLiteral("isFinite"), method_isFinite, 1); @@ -117,190 +125,141 @@ QT_WARNING_POP defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); defineDefaultProperty(engine->id_toString(), method_toString, 1); - defineDefaultProperty(QStringLiteral("toLocaleString"), method_toLocaleString); + defineDefaultProperty(engine->id_toLocaleString(), method_toLocaleString); defineDefaultProperty(engine->id_valueOf(), method_valueOf); defineDefaultProperty(QStringLiteral("toFixed"), method_toFixed, 1); defineDefaultProperty(QStringLiteral("toExponential"), method_toExponential, 1); defineDefaultProperty(QStringLiteral("toPrecision"), method_toPrecision, 1); } -inline ReturnedValue thisNumberValue(Scope &scope, CallData *callData) +inline ReturnedValue thisNumberValue(ExecutionEngine *v4, const Value *thisObject) { - if (callData->thisObject.isNumber()) - return callData->thisObject.asReturnedValue(); - NumberObject *n = callData->thisObject.as<NumberObject>(); + if (thisObject->isNumber()) + return thisObject->asReturnedValue(); + const NumberObject *n = thisObject->as<NumberObject>(); if (!n) { - scope.engine->throwTypeError(); + v4->throwTypeError(); return Encode::undefined(); } return Encode(n->value()); } -inline double thisNumber(Scope &scope, CallData *callData) +inline double thisNumber(ExecutionEngine *engine, const Value *thisObject) { - if (callData->thisObject.isNumber()) - return callData->thisObject.asDouble(); - NumberObject *n = callData->thisObject.as<NumberObject>(); + if (thisObject->isNumber()) + return thisObject->asDouble(); + const NumberObject *n = thisObject->as<NumberObject>(); if (!n) { - scope.engine->throwTypeError(); + engine->throwTypeError(); return 0; } return n->value(); } -void NumberPrototype::method_isFinite(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_isFinite(const FunctionObject *, const Value *, const Value *argv, int argc) { - if (!callData->argc) { - scope.result = Encode(false); - return; - } + if (!argc || !argv[0].isNumber()) + return Encode(false); - double v = callData->args[0].toNumber(); - scope.result = Encode(!std::isnan(v) && !qt_is_inf(v)); + double v = argv[0].toNumber(); + return Encode(!std::isnan(v) && !qt_is_inf(v)); } -void NumberPrototype::method_isInteger(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_isInteger(const FunctionObject *, const Value *, const Value *argv, int argc) { - if (!callData->argc) { - scope.result = Encode(false); - return; - } + if (!argc) + return Encode(false); - const Value &v = callData->args[0]; - if (!v.isNumber()) { - scope.result = Encode(false); - return; - } + const Value &v = argv[0]; + if (!v.isNumber()) + return Encode(false); double dv = v.toNumber(); - if (std::isnan(dv) || qt_is_inf(dv)) { - scope.result = Encode(false); - return; - } + if (std::isnan(dv) || qt_is_inf(dv)) + return Encode(false); double iv = v.toInteger(); - scope.result = Encode(dv == iv); + return Encode(dv == iv); } -void NumberPrototype::method_isSafeInteger(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_isSafeInteger(const FunctionObject *, const Value *, const Value *argv, int argc) { - if (!callData->argc) { - scope.result = Encode(false); - return; - } + if (!argc) + return Encode(false); - const Value &v = callData->args[0]; - if (!v.isNumber()) { - scope.result = Encode(false); - return; - } + const Value &v = argv[0]; + if (!v.isNumber()) + return Encode(false); double dv = v.toNumber(); - if (std::isnan(dv) || qt_is_inf(dv)) { - scope.result = Encode(false); - return; - } + if (std::isnan(dv) || qt_is_inf(dv)) + return Encode(false); double iv = v.toInteger(); - scope.result = Encode(dv == iv && std::fabs(iv) <= (2^53)-1); + return Encode(dv == iv && std::fabs(iv) <= (2^53)-1); } -void NumberPrototype::method_isNaN(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_isNaN(const FunctionObject *, const Value *, const Value *argv, int argc) { - if (!callData->argc) { - scope.result = Encode(false); - return; - } + if (!argc || !argv[0].isNumber()) + return Encode(false); - double v = callData->args[0].toNumber(); - scope.result = Encode(std::isnan(v)); + double v = argv[0].toNumber(); + // cast to bool explicitly as std::isnan() may give us ::isnan(), which + // sometimes returns an int and we don't want the Encode(int) overload. + return Encode(bool(std::isnan(v))); } -void NumberPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - double num = thisNumber(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + double num = thisNumber(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - if (callData->argc && !callData->args[0].isUndefined()) { - int radix = callData->args[0].toInt32(); + if (argc && !argv[0].isUndefined()) { + int radix = argv[0].toInt32(); if (radix < 2 || radix > 36) { - scope.result = scope.engine->throwError(QStringLiteral("Number.prototype.toString: %0 is not a valid radix") - .arg(radix)); - return; - } - - if (std::isnan(num)) { - scope.result = scope.engine->newString(QStringLiteral("NaN")); - return; - } else if (qt_is_inf(num)) { - scope.result = scope.engine->newString(QLatin1String(num < 0 ? "-Infinity" : "Infinity")); - return; + return v4->throwError(QStringLiteral("Number.prototype.toString: %0 is not a valid radix").arg(radix)); } - if (radix != 10) { - QString str; - bool negative = false; - if (num < 0) { - negative = true; - num = -num; - } - double frac = num - std::floor(num); - num = Primitive::toInteger(num); - do { - char c = (char)std::fmod(num, radix); - c = (c < 10) ? (c + '0') : (c - 10 + 'a'); - str.prepend(QLatin1Char(c)); - num = std::floor(num / radix); - } while (num != 0); - if (frac != 0) { - str.append(QLatin1Char('.')); - do { - frac = frac * radix; - char c = (char)std::floor(frac); - c = (c < 10) ? (c + '0') : (c - 10 + 'a'); - str.append(QLatin1Char(c)); - frac = frac - std::floor(frac); - } while (frac != 0); - } - if (negative) - str.prepend(QLatin1Char('-')); - scope.result = scope.engine->newString(str); - return; - } + QString str; + RuntimeHelpers::numberToString(&str, num, radix); + return Encode(v4->newString(str)); } - scope.result = Primitive::fromDouble(num).toString(scope.engine); + return Encode(Value::fromDouble(num).toString(v4)); } -void NumberPrototype::method_toLocaleString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - ScopedValue v(scope, thisNumberValue(scope, callData)); - scope.result = v->toString(scope.engine); - CHECK_EXCEPTION(); + Scope scope(b); + ScopedValue v(scope, thisNumberValue(b->engine(), thisObject)); + return Encode(v->toString(scope.engine)); } -void NumberPrototype::method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_valueOf(const FunctionObject *b, const Value *thisObject, const Value *, int) { - scope.result = thisNumberValue(scope, callData); + return thisNumberValue(b->engine(), thisObject); } -void NumberPrototype::method_toFixed(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_toFixed(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - double v = thisNumber(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + double v = thisNumber(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); double fdigits = 0; - if (callData->argc > 0) - fdigits = callData->args[0].toInteger(); + if (argc > 0) + fdigits = argv[0].toInteger(); if (std::isnan(fdigits)) fdigits = 0; - if (fdigits < 0 || fdigits > 20) { - scope.result = scope.engine->throwRangeError(callData->thisObject); - return; - } + if (fdigits < 0 || fdigits > 100) + return v4->throwRangeError(*thisObject); QString str; if (std::isnan(v)) @@ -310,49 +269,65 @@ void NumberPrototype::method_toFixed(const BuiltinFunction *, Scope &scope, Call else if (v < 1.e21) str = NumberLocale::instance()->toString(v, 'f', int(fdigits)); else { - scope.result = RuntimeHelpers::stringFromNumber(scope.engine, v); - return; + return Encode(RuntimeHelpers::stringFromNumber(v4, v)); } - scope.result = scope.engine->newString(str); + return Encode(v4->newString(str)); } -void NumberPrototype::method_toExponential(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_toExponential(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - double d = thisNumber(scope, callData); - CHECK_EXCEPTION(); - - int fdigits = NumberLocale::instance()->defaultDoublePrecision; - - if (callData->argc && !callData->args[0].isUndefined()) { - fdigits = callData->args[0].toInt32(); - if (fdigits < 0 || fdigits > 20) { - ScopedString error(scope, scope.engine->newString(QStringLiteral("Number.prototype.toExponential: fractionDigits out of range"))); - scope.result = scope.engine->throwRangeError(error); - return; - } + ExecutionEngine *v4 = b->engine(); + double d = thisNumber(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); + + bool defaultDigits = !argc || argv[0].isUndefined(); + int fdigits = !defaultDigits ? argv[0].toInteger() : NumberLocale::instance()->defaultDoublePrecision; + if (v4->hasException) + return QV4::Encode::undefined(); + + if (std::isnan(d)) + return Encode(v4->newString(QLatin1String("NaN"))); + + if (qIsInf(d)) + return Encode(v4->newString(QLatin1String(d < 0 ? "-Infinity" : "Infinity"))); + + if (!defaultDigits && (fdigits < 0 || fdigits > 100)) { + Scope scope(v4); + ScopedString error(scope, v4->newString(QStringLiteral("Number.prototype.toExponential: fractionDigits out of range"))); + return v4->throwRangeError(error); } QString result = NumberLocale::instance()->toString(d, 'e', fdigits); - scope.result = scope.engine->newString(result); + return Encode(v4->newString(result)); } -void NumberPrototype::method_toPrecision(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue NumberPrototype::method_toPrecision(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedValue v(scope, thisNumberValue(scope, callData)); - CHECK_EXCEPTION(); + Scope scope(b); + ScopedValue v(scope, thisNumberValue(scope.engine, thisObject)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + double d = v->asDouble(); - if (!callData->argc || callData->args[0].isUndefined()) { - scope.result = v->toString(scope.engine); - return; - } + if (!argc || argv[0].isUndefined()) + return Encode(v->toString(scope.engine)); + + int precision = argv[0].toInt32(); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + + if (std::isnan(d)) + return Encode(scope.engine->newString(QLatin1String("NaN"))); + + if (qIsInf(d)) + return Encode(scope.engine->newString(QLatin1String(d < 0 ? "-Infinity" : "Infinity"))); - int precision = callData->args[0].toInt32(); - if (precision < 1 || precision > 21) { + if (precision < 1 || precision > 100) { ScopedString error(scope, scope.engine->newString(QStringLiteral("Number.prototype.toPrecision: precision out of range"))); - scope.result = scope.engine->throwRangeError(error); - return; + return scope.engine->throwRangeError(error); } - QString result = NumberLocale::instance()->toString(v->asDouble(), 'g', precision); - scope.result = scope.engine->newString(result); + QString result = NumberLocale::instance()->toString(d, 'g', precision); + return Encode(scope.engine->newString(result)); } diff --git a/src/qml/jsruntime/qv4numberobject_p.h b/src/qml/jsruntime/qv4numberobject_p.h index 0bc2cd8c65..576817cf36 100644 --- a/src/qml/jsruntime/qv4numberobject_p.h +++ b/src/qml/jsruntime/qv4numberobject_p.h @@ -79,8 +79,8 @@ struct NumberCtor: FunctionObject { V4_OBJECT2(NumberCtor, FunctionObject) - static void construct(const Managed *that, Scope &scope, CallData *callData); - static void call(const Managed *, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; struct NumberPrototype: NumberObject @@ -88,16 +88,16 @@ struct NumberPrototype: NumberObject V4_PROTOTYPE(objectPrototype) void init(ExecutionEngine *engine, Object *ctor); - static void method_isFinite(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isInteger(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isSafeInteger(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isNaN(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toLocaleString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toFixed(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toExponential(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toPrecision(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_isFinite(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isInteger(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isSafeInteger(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isNaN(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_valueOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toFixed(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toExponential(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toPrecision(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 94d5a74fe5..3d2d54f651 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -38,8 +38,6 @@ ****************************************************************************/ #include "qv4object_p.h" -#include "qv4jsir_p.h" -#include "qv4isel_p.h" #include "qv4objectproto_p.h" #include "qv4stringobject_p.h" #include "qv4argumentsobject_p.h" @@ -51,6 +49,9 @@ #include "qv4identifier_p.h" #include "qv4string_p.h" #include "qv4identifiertable_p.h" +#include "qv4jscall_p.h" +#include "qv4symbol_p.h" +#include "qv4proxy_p.h" #include <stdint.h> @@ -58,9 +59,11 @@ using namespace QV4; DEFINE_OBJECT_VTABLE(Object); -void Object::setInternalClass(InternalClass *ic) +void Object::setInternalClass(Heap::InternalClass *ic) { - d()->internalClass = ic; + d()->internalClass.set(engine(), ic); + if (ic->isUsedAsProto) + ic->updateProtoUsage(d()); Q_ASSERT(ic && ic->vtable); uint nInline = d()->vtable()->nInlineProperties; if (ic->size <= nInline) @@ -71,35 +74,26 @@ void Object::setInternalClass(InternalClass *ic) d()->memberData.set(ic->engine, MemberData::allocate(ic->engine, requiredSize, d()->memberData)); } -void Object::getProperty(uint index, Property *p, PropertyAttributes *attrs) const +void Object::getProperty(const InternalClassEntry &entry, Property *p) const { - p->value = *propertyData(index); - *attrs = internalClass()->propertyData.at(index); - if (attrs->isAccessor()) - p->set = *propertyData(index + SetterOffset); + p->value = *propertyData(entry.index); + if (entry.attributes.isAccessor()) + p->set = *propertyData(entry.setterIndex); } -void Object::setProperty(uint index, const Property *p) +void Object::setProperty(const InternalClassEntry &entry, const Property *p) { - setProperty(index, p->value); - if (internalClass()->propertyData.at(index).isAccessor()) - setProperty(index + SetterOffset, p->set); + setProperty(entry.index, p->value); + if (entry.attributes.isAccessor()) + setProperty(entry.setterIndex, p->set); } -bool Object::setPrototype(Object *proto) +void Heap::Object::setUsedAsProto() { - Heap::Object *p = proto ? proto->d() : 0; - Heap::Object *pp = p; - while (pp) { - if (pp == d()) - return false; - pp = pp->prototype(); - } - setInternalClass(internalClass()->changePrototype(p)); - return true; + internalClass.set(internalClass->engine, internalClass->asProtoClass()); } -ReturnedValue Object::getValue(const Value &thisObject, const Value &v, PropertyAttributes attrs) +ReturnedValue Object::getValueAccessor(const Value &thisObject, const Value &v, PropertyAttributes attrs) { if (!attrs.isAccessor()) return v.asReturnedValue(); @@ -108,77 +102,66 @@ ReturnedValue Object::getValue(const Value &thisObject, const Value &v, Property return Encode::undefined(); Scope scope(f->engine()); - ScopedCallData callData(scope); - callData->thisObject = thisObject; - f->call(scope, callData); - return scope.result.asReturnedValue(); + JSCallData jsCallData(scope); + *jsCallData->thisObject = thisObject; + return f->call(jsCallData); } -bool Object::putValue(uint memberIndex, const Value &value) +bool Object::putValue(uint memberIndex, PropertyAttributes attrs, const Value &value) { - QV4::InternalClass *ic = internalClass(); + Heap::InternalClass *ic = internalClass(); if (ic->engine->hasException) return false; - PropertyAttributes attrs = ic->propertyData[memberIndex]; - if (attrs.isAccessor()) { - const FunctionObject *set = propertyData(memberIndex + SetterOffset)->as<FunctionObject>(); + const FunctionObject *set = propertyData(memberIndex)->as<FunctionObject>(); if (set) { Scope scope(ic->engine); ScopedFunctionObject setter(scope, set); - ScopedCallData callData(scope, 1); - callData->args[0] = value; - callData->thisObject = this; - setter->call(scope, callData); + JSCallData jsCallData(scope, 1); + jsCallData->args[0] = value; + *jsCallData->thisObject = this; + setter->call(jsCallData); return !ic->engine->hasException; } - goto reject; + return false; } if (!attrs.isWritable()) - goto reject; + return false; setProperty(memberIndex, value); return true; - - reject: - if (engine()->current->strictMode) - engine()->throwTypeError(); - return false; } -void Object::defineDefaultProperty(const QString &name, const Value &value) +void Object::defineDefaultProperty(const QString &name, const Value &value, PropertyAttributes attributes) { ExecutionEngine *e = engine(); Scope scope(e); ScopedString s(scope, e->newIdentifier(name)); - defineDefaultProperty(s, value); + defineDefaultProperty(s, value, attributes); } -void Object::defineDefaultProperty(const QString &name, void (*code)(const BuiltinFunction *, Scope &, CallData *), int argumentCount) +void Object::defineDefaultProperty(const QString &name, VTable::Call code, + int argumentCount, PropertyAttributes attributes) { ExecutionEngine *e = engine(); Scope scope(e); ScopedString s(scope, e->newIdentifier(name)); - ExecutionContext *global = e->rootContext(); - ScopedFunctionObject function(scope, BuiltinFunction::create(global, s, code)); - function->defineReadonlyConfigurableProperty(e->id_length(), Primitive::fromInt32(argumentCount)); - defineDefaultProperty(s, function); + ScopedFunctionObject function(scope, FunctionObject::createBuiltinFunction(e, s, code, argumentCount)); + defineDefaultProperty(s, function, attributes); } -void Object::defineDefaultProperty(String *name, void (*code)(const BuiltinFunction *, Scope &, CallData *), int argumentCount) +void Object::defineDefaultProperty(StringOrSymbol *nameOrSymbol, VTable::Call code, + int argumentCount, PropertyAttributes attributes) { ExecutionEngine *e = engine(); Scope scope(e); - ExecutionContext *global = e->rootContext(); - ScopedFunctionObject function(scope, BuiltinFunction::create(global, name, code)); - function->defineReadonlyConfigurableProperty(e->id_length(), Primitive::fromInt32(argumentCount)); - defineDefaultProperty(name, function); + ScopedFunctionObject function(scope, FunctionObject::createBuiltinFunction(e, nameOrSymbol, code, argumentCount)); + defineDefaultProperty(nameOrSymbol, function, attributes); } -void Object::defineAccessorProperty(const QString &name, void (*getter)(const BuiltinFunction *, Scope &, CallData *), - void (*setter)(const BuiltinFunction *, Scope &, CallData *)) +void Object::defineAccessorProperty(const QString &name, VTable::Call getter, VTable::Call setter) { ExecutionEngine *e = engine(); Scope scope(e); @@ -186,18 +169,31 @@ void Object::defineAccessorProperty(const QString &name, void (*getter)(const Bu defineAccessorProperty(s, getter, setter); } -void Object::defineAccessorProperty(String *name, void (*getter)(const BuiltinFunction *, Scope &, CallData *), - void (*setter)(const BuiltinFunction *, Scope &, CallData *)) +void Object::defineAccessorProperty(StringOrSymbol *name, VTable::Call getter, VTable::Call setter) { ExecutionEngine *v4 = engine(); QV4::Scope scope(v4); ScopedProperty p(scope); - ExecutionContext *global = v4->rootContext(); - p->setGetter(ScopedFunctionObject(scope, (getter ? BuiltinFunction::create(global, name, getter) : 0))); - p->setSetter(ScopedFunctionObject(scope, (setter ? BuiltinFunction::create(global, name, setter) : 0))); - insertMember(name, p, QV4::Attr_Accessor|QV4::Attr_NotConfigurable|QV4::Attr_NotEnumerable); + QString n = name->toQString(); + if (n.at(0) == QLatin1Char('@')) + n = QChar::fromLatin1('[') + n.midRef(1) + QChar::fromLatin1(']'); + if (getter) { + ScopedString getName(scope, v4->newString(QString::fromLatin1("get ") + n)); + p->setGetter(ScopedFunctionObject(scope, FunctionObject::createBuiltinFunction(v4, getName, getter, 0))); + } else { + p->setGetter(nullptr); + } + if (setter) { + ScopedString setName(scope, v4->newString(QString::fromLatin1("set ") + n)); + p->setSetter(ScopedFunctionObject(scope, FunctionObject::createBuiltinFunction(v4, setName, setter, 0))); + } else { + p->setSetter(nullptr); + } + insertMember(name, p, QV4::Attr_Accessor|QV4::Attr_NotEnumerable); } + + void Object::defineReadonlyProperty(const QString &name, const Value &value) { QV4::ExecutionEngine *e = engine(); @@ -219,14 +215,28 @@ void Object::defineReadonlyConfigurableProperty(const QString &name, const Value defineReadonlyConfigurableProperty(s, value); } -void Object::defineReadonlyConfigurableProperty(String *name, const Value &value) +void Object::defineReadonlyConfigurableProperty(StringOrSymbol *name, const Value &value) { insertMember(name, value, Attr_ReadOnly_ButConfigurable); } -void Object::markObjects(Heap::Base *b, MarkStack *stack) +void Object::addSymbolSpecies() { - Heap::Object *o = static_cast<Heap::Object *>(b); + Scope scope(engine()); + ScopedProperty p(scope); + p->setGetter(scope.engine->getSymbolSpecies()); + p->setSetter(nullptr); + insertMember(scope.engine->symbol_species(), p, QV4::Attr_Accessor|QV4::Attr_NotWritable|QV4::Attr_NotEnumerable); +} + +void Heap::Object::markObjects(Heap::Base *b, MarkStack *stack) +{ + Base::markObjects(b, stack); + Object *o = static_cast<Object *>(b); + if (o->memberData) + o->memberData->mark(stack); + if (o->arrayData) + o->arrayData->mark(stack); uint nInline = o->vtable()->nInlineProperties; Value *v = reinterpret_cast<Value *>(o) + o->vtable()->inlinePropertyOffset; const Value *end = v + nInline; @@ -236,460 +246,208 @@ void Object::markObjects(Heap::Base *b, MarkStack *stack) } } -void Object::insertMember(String *s, const Property *p, PropertyAttributes attributes) -{ - uint idx; - InternalClass::addMember(this, s, attributes, &idx); - - if (attributes.isAccessor()) { - setProperty(idx + GetterOffset, p->value); - setProperty(idx + SetterOffset, p->set); - } else { - setProperty(idx, p->value); - } -} - -// Section 8.12.1 -void Object::getOwnProperty(String *name, PropertyAttributes *attrs, Property *p) +void Object::insertMember(StringOrSymbol *s, const Property *p, PropertyAttributes attributes) { - uint idx = name->asArrayIndex(); - if (idx != UINT_MAX) - return getOwnProperty(idx, attrs, p); - - name->makeIdentifier(); - Identifier *id = name->identifier(); - - uint member = internalClass()->find(id); - if (member < UINT_MAX) { - *attrs = internalClass()->propertyData[member]; - if (p) { - p->value = *propertyData(member); - if (attrs->isAccessor()) - p->set = *propertyData(member + SetterOffset); - } - return; - } + InternalClassEntry idx; + PropertyKey key = s->toPropertyKey(); + Heap::InternalClass::addMember(this, key, attributes, &idx); - if (attrs) - *attrs = Attr_Invalid; - return; + setProperty(idx.index, p->value); + if (attributes.isAccessor()) + setProperty(idx.setterIndex, p->set); } -void Object::getOwnProperty(uint index, PropertyAttributes *attrs, Property *p) +void Object::setPrototypeUnchecked(const Object *p) { - if (arrayData()) { - if (arrayData()->getProperty(index, p, attrs)) - return; - } - if (isStringObject()) { - *attrs = Attr_NotConfigurable|Attr_NotWritable; - if (p) - p->value = static_cast<StringObject *>(this)->getIndex(index); - return; - } - - if (attrs) - *attrs = Attr_Invalid; - return; + setInternalClass(internalClass()->changePrototype(p ? p->d() : nullptr)); } // Section 8.12.2 -MemberData::Index Object::getValueOrSetter(String *name, PropertyAttributes *attrs) -{ - Q_ASSERT(name->asArrayIndex() == UINT_MAX); - - name->makeIdentifier(); - Identifier *id = name->identifier(); - - Heap::Object *o = d(); - while (o) { - uint idx = o->internalClass->find(id); - if (idx < UINT_MAX) { - *attrs = o->internalClass->propertyData[idx]; - return o->writablePropertyData(attrs->isAccessor() ? idx + SetterOffset : idx ); - } - - o = o->prototype(); - } - *attrs = Attr_Invalid; - return { 0, 0 }; -} - -ArrayData::Index Object::getValueOrSetter(uint index, PropertyAttributes *attrs) +PropertyIndex Object::getValueOrSetter(PropertyKey id, PropertyAttributes *attrs) { - Heap::Object *o = d(); - while (o) { - if (o->arrayData) { - uint idx = o->arrayData->mappedIndex(index); - if (idx != UINT_MAX) { - *attrs = o->arrayData->attributes(index); - return { o->arrayData , attrs->isAccessor() ? idx + SetterOffset : idx }; + if (id.isArrayIndex()) { + uint index = id.asArrayIndex(); + Heap::Object *o = d(); + while (o) { + if (o->arrayData) { + uint idx = o->arrayData->mappedIndex(index); + if (idx != UINT_MAX) { + *attrs = o->arrayData->attributes(index); + return { o->arrayData , o->arrayData->values.values + (attrs->isAccessor() ? idx + SetterOffset : idx) }; + } } + if (o->vtable()->type == Type_StringObject) { + if (index < static_cast<const Heap::StringObject *>(o)->length()) { + // this is an evil hack, but it works, as the method is only ever called from put, + // where we don't use the returned pointer there for non writable attributes + *attrs = (Attr_NotWritable|Attr_NotConfigurable); + return { reinterpret_cast<Heap::ArrayData *>(0x1), nullptr }; + } + } + o = o->prototype(); } - if (o->vtable()->type == Type_StringObject) { - if (index < static_cast<const Heap::StringObject *>(o)->length()) { - // this is an evil hack, but it works, as the method is only ever called from putIndexed, - // where we don't use the returned pointer there for non writable attributes - *attrs = (Attr_NotWritable|Attr_NotConfigurable); - return { reinterpret_cast<Heap::ArrayData *>(0x1), 0 }; + } else { + Heap::Object *o = d(); + while (o) { + auto idx = o->internalClass->findValueOrSetter(id); + if (idx.isValid()) { + *attrs = idx.attrs; + return o->writablePropertyData(idx.index); } + + o = o->prototype(); } - o = o->prototype(); } *attrs = Attr_Invalid; - return { 0, 0 }; -} - -bool Object::hasProperty(String *name) const -{ - uint idx = name->asArrayIndex(); - if (idx != UINT_MAX) - return hasProperty(idx); - - Scope scope(engine()); - ScopedObject o(scope, d()); - while (o) { - if (o->hasOwnProperty(name)) - return true; - - o = o->prototype(); - } - - return false; -} - -bool Object::hasProperty(uint index) const -{ - Scope scope(engine()); - ScopedObject o(scope, d()); - while (o) { - if (o->hasOwnProperty(index)) - return true; - - o = o->prototype(); - } - - return false; -} - -bool Object::hasOwnProperty(String *name) const -{ - uint idx = name->asArrayIndex(); - if (idx != UINT_MAX) - return hasOwnProperty(idx); - - name->makeIdentifier(); - Identifier *id = name->identifier(); - - if (internalClass()->find(id) < UINT_MAX) - return true; - if (!query(name).isEmpty()) - return true; - return false; -} - -bool Object::hasOwnProperty(uint index) const -{ - if (arrayData() && !arrayData()->isEmpty(index)) - return true; - - if (isStringObject()) { - if (index < static_cast<const StringObject *>(this)->length()) - return true; - } - if (!queryIndexed(index).isEmpty()) - return true; - return false; -} - -void Object::construct(const Managed *m, Scope &scope, CallData *) -{ - scope.result = static_cast<const Object *>(m)->engine()->throwTypeError(); -} - -void Object::call(const Managed *m, Scope &scope, CallData *) -{ - scope.result = static_cast<const Object *>(m)->engine()->throwTypeError(); -} - -ReturnedValue Object::get(const Managed *m, String *name, bool *hasProperty) -{ - return static_cast<const Object *>(m)->internalGet(name, hasProperty); -} - -ReturnedValue Object::getIndexed(const Managed *m, uint index, bool *hasProperty) -{ - return static_cast<const Object *>(m)->internalGetIndexed(index, hasProperty); -} - -bool Object::put(Managed *m, String *name, const Value &value) -{ - return static_cast<Object *>(m)->internalPut(name, value); -} - -bool Object::putIndexed(Managed *m, uint index, const Value &value) -{ - return static_cast<Object *>(m)->internalPutIndexed(index, value); -} - -PropertyAttributes Object::query(const Managed *m, String *name) -{ - uint idx = name->asArrayIndex(); - if (idx != UINT_MAX) - return queryIndexed(m, idx); - - name->makeIdentifier(); - Identifier *id = name->identifier(); - - const Object *o = static_cast<const Object *>(m); - idx = o->internalClass()->find(id); - if (idx < UINT_MAX) - return o->internalClass()->propertyData[idx]; - - return Attr_Invalid; + return { nullptr, nullptr }; } -PropertyAttributes Object::queryIndexed(const Managed *m, uint index) +ReturnedValue Object::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { - const Object *o = static_cast<const Object *>(m); - if (o->arrayData() && !o->arrayData()->isEmpty(index)) - return o->arrayData()->attributes(index); - - if (o->isStringObject()) { - if (index < static_cast<const StringObject *>(o)->length()) - return (Attr_NotWritable|Attr_NotConfigurable); - } - return Attr_Invalid; -} - -bool Object::deleteProperty(Managed *m, String *name) -{ - return static_cast<Object *>(m)->internalDeleteProperty(name); + return static_cast<const Object *>(m)->internalGet(id, receiver, hasProperty); } -bool Object::deleteIndexedProperty(Managed *m, uint index) +bool Object::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { - return static_cast<Object *>(m)->internalDeleteIndexedProperty(index); -} - -ReturnedValue Object::getLookup(const Managed *m, Lookup *l) -{ - const Object *o = static_cast<const Object *>(m); - PropertyAttributes attrs; - ReturnedValue v = l->lookup(o, &attrs); - if (v != Primitive::emptyValue().asReturnedValue()) { - l->proto = l->classList[0]->prototype; - if (attrs.isData()) { - Q_ASSERT(l->classList[0] == o->internalClass()); - if (l->level == 0) { - uint nInline = o->d()->vtable()->nInlineProperties; - if (l->index < nInline) - l->getter = Lookup::getter0Inline; - else { - l->index -= nInline; - l->getter = Lookup::getter0MemberData; - } - } - else if (l->level == 1) - l->getter = Lookup::getter1; - else if (l->level == 2) - l->getter = Lookup::getter2; - else - l->getter = Lookup::getterFallback; - return v; - } else { - if (l->level == 0) - l->getter = Lookup::getterAccessor0; - else if (l->level == 1) - l->getter = Lookup::getterAccessor1; - else if (l->level == 2) - l->getter = Lookup::getterAccessor2; - else - l->getter = Lookup::getterFallback; - return v; - } - } - return Encode::undefined(); + return static_cast<Object *>(m)->internalPut(id, value, receiver); } -void Object::setLookup(Managed *m, Lookup *l, const Value &value) +bool Object::virtualDeleteProperty(Managed *m, PropertyKey id) { - Scope scope(static_cast<Object *>(m)->engine()); - ScopedObject o(scope, static_cast<Object *>(m)); - ScopedString name(scope, scope.engine->current->compilationUnit->runtimeStrings[l->nameIndex]); - - InternalClass *c = o->internalClass(); - uint idx = c->find(name); - if (!o->isArrayObject() || idx != Heap::ArrayObject::LengthPropertyIndex) { - if (idx != UINT_MAX && o->internalClass()->propertyData[idx].isData() && o->internalClass()->propertyData[idx].isWritable()) { - l->classList[0] = o->internalClass(); - l->index = idx; - l->setter = idx < o->d()->vtable()->nInlineProperties ? Lookup::setter0Inline : Lookup::setter0; - o->setProperty(idx, value); - return; - } - - if (idx != UINT_MAX) { - o->putValue(idx, value); - return; - } - } - - o->put(name, value); - - if (o->internalClass() == c) - return; - idx = o->internalClass()->find(name); - if (idx == UINT_MAX) - return; - l->classList[0] = c; - l->classList[3] = o->internalClass(); - l->index = idx; - if (!o->prototype()) { - l->setter = Lookup::setterInsert0; - return; - } - o = o->prototype(); - l->classList[1] = o->internalClass(); - if (!o->prototype()) { - l->setter = Lookup::setterInsert1; - return; - } - o = o->prototype(); - l->classList[2] = o->internalClass(); - if (!o->prototype()) { - l->setter = Lookup::setterInsert2; - return; - } - l->setter = Lookup::setterGeneric; + return static_cast<Object *>(m)->internalDeleteProperty(id); } -void Object::advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *pd, PropertyAttributes *attrs) +PropertyKey ObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) { - Object *o = static_cast<Object *>(m); - name->setM(0); - *index = UINT_MAX; - - if (o->arrayData()) { - if (!it->arrayIndex) - it->arrayNode = o->sparseBegin(); + if (arrayIndex != UINT_MAX && o->arrayData()) { + if (!arrayIndex) + arrayNode = o->sparseBegin(); // sparse arrays - if (it->arrayNode) { - while (it->arrayNode != o->sparseEnd()) { - int k = it->arrayNode->key(); - uint pidx = it->arrayNode->value; + if (arrayNode) { + while (arrayNode != o->sparseEnd()) { + uint k = arrayNode->key(); + uint pidx = arrayNode->value; Heap::SparseArrayData *sa = o->d()->arrayData.cast<Heap::SparseArrayData>(); const Property *p = reinterpret_cast<const Property *>(sa->values.data() + pidx); - it->arrayNode = it->arrayNode->nextNode(); + arrayNode = arrayNode->nextNode(); PropertyAttributes a = sa->attrs ? sa->attrs[pidx] : Attr_Data; - if (!(it->flags & ObjectIterator::EnumerableOnly) || a.isEnumerable()) { - it->arrayIndex = k + 1; - *index = k; - *attrs = a; + arrayIndex = k + 1; + if (pd) pd->copy(p, a); - return; - } + if (attrs) + *attrs = a; + return PropertyKey::fromArrayIndex(k); } - it->arrayNode = 0; - it->arrayIndex = UINT_MAX; + arrayNode = nullptr; + arrayIndex = UINT_MAX; } // dense arrays - while (it->arrayIndex < o->d()->arrayData->values.size) { + while (arrayIndex < o->d()->arrayData->values.size) { Heap::SimpleArrayData *sa = o->d()->arrayData.cast<Heap::SimpleArrayData>(); - const Value &val = sa->data(it->arrayIndex); - PropertyAttributes a = o->arrayData()->attributes(it->arrayIndex); - ++it->arrayIndex; - if (!val.isEmpty() - && (!(it->flags & ObjectIterator::EnumerableOnly) || a.isEnumerable())) { - *index = it->arrayIndex - 1; - *attrs = a; - pd->value = val; - return; + const Value &val = sa->data(arrayIndex); + PropertyAttributes a = o->arrayData()->attributes(arrayIndex); + int index = arrayIndex; + ++arrayIndex; + if (!val.isEmpty()) { + if (pd) + pd->value = val; + if (attrs) + *attrs = a; + return PropertyKey::fromArrayIndex(index); } } + arrayIndex = UINT_MAX; } - while (it->memberIndex < o->internalClass()->size) { - Identifier *n = o->internalClass()->nameMap.at(it->memberIndex); - if (!n) { - // accessor properties have a dummy entry with n == 0 - ++it->memberIndex; - continue; - } - - int idx = it->memberIndex; - PropertyAttributes a = o->internalClass()->propertyData[it->memberIndex]; - ++it->memberIndex; - if (!(it->flags & ObjectIterator::EnumerableOnly) || a.isEnumerable()) { - name->setM(o->engine()->identifierTable->stringFromIdentifier(n)); - *attrs = a; - pd->value = *o->propertyData(idx); - if (a.isAccessor()) - pd->set = *o->propertyData(idx + SetterOffset); - return; + while (true) { + while (memberIndex < o->internalClass()->size) { + PropertyKey n = o->internalClass()->nameMap.at(memberIndex); + ++memberIndex; + if (!n.isStringOrSymbol()) + // accessor properties have a dummy entry with n == 0 + continue; + if (!iterateOverSymbols && n.isSymbol()) + continue; + if (iterateOverSymbols && !n.isSymbol()) + continue; + + InternalClassEntry e = o->internalClass()->find(n); + if (!e.isValid()) + continue; + if (pd) { + pd->value = *o->propertyData(e.index); + if (e.attributes.isAccessor()) + pd->set = *o->propertyData(e.setterIndex); + } + if (attrs) + *attrs = e.attributes; + return n; } + if (iterateOverSymbols) + break; + iterateOverSymbols = true; + memberIndex = 0; } - *attrs = PropertyAttributes(); + return PropertyKey::invalid(); } -// Section 8.12.3 -ReturnedValue Object::internalGet(String *name, bool *hasProperty) const +OwnPropertyKeyIterator *Object::virtualOwnPropertyKeys(const Object *o, Value *target) { - uint idx = name->asArrayIndex(); - if (idx != UINT_MAX) - return getIndexed(idx, hasProperty); - - name->makeIdentifier(); - Identifier *id = name->identifier(); - - Scope scope(engine()); - ScopedObject o(scope, this); - while (o) { - uint idx = o->internalClass()->find(id); - if (idx < UINT_MAX) { - if (hasProperty) - *hasProperty = true; - return getValue(*o->propertyData(idx), o->internalClass()->propertyData.at(idx)); - } - - o = o->prototype(); - } - - if (hasProperty) - *hasProperty = false; - return Encode::undefined(); + *target = *o; + return new ObjectOwnPropertyKeyIterator; } -ReturnedValue Object::internalGetIndexed(uint index, bool *hasProperty) const +// Section 8.12.3 +ReturnedValue Object::internalGet(PropertyKey id, const Value *receiver, bool *hasProperty) const { - PropertyAttributes attrs; - Scope scope(engine()); - ScopedObject o(scope, this); - ScopedProperty pd(scope); - bool exists = false; - while (o) { - if (o->arrayData() && o->arrayData()->getProperty(index, pd, &attrs)) { - exists = true; - break; + Heap::Object *o = d(); + + uint index = id.asArrayIndex(); + if (index != UINT_MAX) { + Scope scope(this); + PropertyAttributes attrs; + ScopedProperty pd(scope); + while (1) { + if (o->arrayData && o->arrayData->getProperty(index, pd, &attrs)) { + if (hasProperty) + *hasProperty = true; + return Object::getValue(*receiver, pd->value, attrs); + } + if (o->internalClass->vtable->type == Type_StringObject) { + ScopedString str(scope, static_cast<Heap::StringObject *>(o)->getIndex(index)); + if (str) { + attrs = (Attr_NotWritable|Attr_NotConfigurable); + if (hasProperty) + *hasProperty = true; + return str.asReturnedValue(); + } + } + o = o->prototype(); + if (!o || o->internalClass->vtable->get != Object::virtualGet) + break; } - if (o->isStringObject()) { - ScopedString str(scope, static_cast<StringObject *>(o.getPointer())->getIndex(index)); - if (str) { - attrs = (Attr_NotWritable|Attr_NotConfigurable); + } else { + Q_ASSERT(!id.isArrayIndex()); + + while (1) { + auto idx = o->internalClass->findValueOrGetter(id); + if (idx.isValid()) { if (hasProperty) *hasProperty = true; - return str.asReturnedValue(); + return Object::getValue(*receiver, *o->propertyData(idx.index), idx.attrs); } + o = o->prototype(); + if (!o || o->internalClass->vtable->get != Object::virtualGet) + break; } - o = o->prototype(); } - if (exists) { - if (hasProperty) - *hasProperty = true; - return getValue(pd->value, attrs); + if (o) { + const Value v = Value::fromHeapObject(o); + const Object &obj = static_cast<const Object &>(v); + return obj.get(id, receiver, hasProperty); } if (hasProperty) @@ -697,326 +455,141 @@ ReturnedValue Object::internalGetIndexed(uint index, bool *hasProperty) const return Encode::undefined(); } - // Section 8.12.5 -bool Object::internalPut(String *name, const Value &value) +bool Object::internalPut(PropertyKey id, const Value &value, Value *receiver) { - ExecutionEngine *engine = this->engine(); - if (engine->hasException) + Scope scope(this); + if (scope.engine->hasException) return false; - uint idx = name->asArrayIndex(); - if (idx != UINT_MAX) - return putIndexed(idx, value); - - name->makeIdentifier(); - Identifier *id = name->identifier(); - - MemberData::Index memberIndex{0, 0}; - uint member = internalClass()->find(id); - PropertyAttributes attrs; - if (member < UINT_MAX) { - attrs = internalClass()->propertyData[member]; - memberIndex = d()->writablePropertyData(attrs.isAccessor() ? member + SetterOffset : member); - } - - // clause 1 - if (!memberIndex.isNull()) { - if (attrs.isAccessor()) { - if (memberIndex->as<FunctionObject>()) - goto cont; - goto reject; - } else if (!attrs.isWritable()) - goto reject; - else if (isArrayObject() && name->equals(engine->id_length())) { - bool ok; - uint l = value.asArrayLength(&ok); - if (!ok) { - engine->throwRangeError(value); - return false; + Object *r = receiver->objectValue(); + if (r && r->d() == d()) { + // receiver and this object are the same + if (d()->internalClass->vtable->getOwnProperty == Object::virtualGetOwnProperty) { + // This object standard methods in the vtable, so we can take a shortcut + // and avoid the calls to getOwnProperty and defineOwnProperty + uint index = id.asArrayIndex(); + + PropertyAttributes attrs; + PropertyIndex propertyIndex{nullptr, nullptr}; + + if (index != UINT_MAX) { + if (arrayData()) + propertyIndex = arrayData()->getValueOrSetter(index, &attrs); + } else { + auto member = internalClass()->findValueOrSetter(id); + if (member.isValid()) { + attrs = member.attrs; + propertyIndex = d()->writablePropertyData(member.index); + } } - ok = setArrayLength(l); - if (!ok) - goto reject; - } else { - memberIndex.set(engine, value); - } - return true; - } else if (!prototype()) { - if (!isExtensible()) - goto reject; - } else { - // clause 4 - Scope scope(engine); - memberIndex = ScopedObject(scope, prototype())->getValueOrSetter(name, &attrs); - if (!memberIndex.isNull()) { - if (attrs.isAccessor()) { - if (!memberIndex->as<FunctionObject>()) - goto reject; - } else if (!isExtensible() || !attrs.isWritable()) { - goto reject; + + if (!propertyIndex.isNull() && !attrs.isAccessor()) { + if (!attrs.isWritable()) + return false; + else if (isArrayObject() && id == scope.engine->id_length()->propertyKey()) { + bool ok; + uint l = value.asArrayLength(&ok); + if (!ok) { + scope.engine->throwRangeError(value); + return false; + } + ok = setArrayLength(l); + if (!ok) + return false; + } else { + propertyIndex.set(scope.engine, value); + } + return true; } - } else if (!isExtensible()) { - goto reject; } } - cont: - - // Clause 5 - if (!memberIndex.isNull() && attrs.isAccessor()) { - Q_ASSERT(memberIndex->as<FunctionObject>()); - - Scope scope(engine); - ScopedFunctionObject setter(scope, *memberIndex); - ScopedCallData callData(scope, 1); - callData->args[0] = value; - callData->thisObject = this; - setter->call(scope, callData); - return !internalClass()->engine->hasException; + ScopedProperty p(scope); + PropertyAttributes attrs; + attrs = getOwnProperty(id, p); + if (attrs == Attr_Invalid) { + ScopedObject p(scope, getPrototypeOf()); + if (p) + return p->put(id, value, receiver); + attrs = Attr_Data; } - insertMember(name, value); - return true; - - reject: - // ### this should be removed once everything is ported to use Object::set() - if (engine->current->strictMode) { - QString message = QLatin1String("Cannot assign to read-only property \"") + - name->toQString() + QLatin1Char('\"'); - engine->throwTypeError(message); + if (attrs.isAccessor()) { + ScopedFunctionObject setter(scope, p->setter()); + if (!setter) + return false; + JSCallData jsCallData(scope, 1); + jsCallData->args[0] = value; + *jsCallData->thisObject = *receiver; + setter->call(jsCallData); + return !scope.engine->hasException; } - return false; -} -bool Object::internalPutIndexed(uint index, const Value &value) -{ - ExecutionEngine *engine = this->engine(); - if (engine->hasException) + // Data property + if (!attrs.isWritable()) return false; + if (!r) + return false; + attrs = r->getOwnProperty(id, p); - PropertyAttributes attrs; - - ArrayData::Index arrayIndex = arrayData() ? arrayData()->getValueOrSetter(index, &attrs) : ArrayData::Index{ 0, 0 }; - - if (arrayIndex.isNull() && isStringObject()) { - if (index < static_cast<StringObject *>(this)->length()) - // not writable - goto reject; - } - - // clause 1 - if (!arrayIndex.isNull()) { - if (attrs.isAccessor()) { - if (arrayIndex->as<FunctionObject>()) - goto cont; - goto reject; - } else if (!attrs.isWritable()) - goto reject; - - arrayIndex.set(engine, value); - return true; - } else if (!prototype()) { - if (!isExtensible()) - goto reject; + if (attrs != Attr_Invalid) { + if (attrs.isAccessor() || !attrs.isWritable()) + return false; } else { - // clause 4 - Scope scope(engine); - arrayIndex = ScopedObject(scope, prototype())->getValueOrSetter(index, &attrs); - if (!arrayIndex.isNull()) { - if (attrs.isAccessor()) { - if (!arrayIndex->as<FunctionObject>()) - goto reject; - } else if (!isExtensible() || !attrs.isWritable()) { - goto reject; - } - } else if (!isExtensible()) { - goto reject; - } + if (!r->isExtensible()) + return false; + attrs = Attr_Data; } - cont: - - // Clause 5 - if (!arrayIndex.isNull() && attrs.isAccessor()) { - Q_ASSERT(arrayIndex->as<FunctionObject>()); - - Scope scope(engine); - ScopedFunctionObject setter(scope, *arrayIndex); - ScopedCallData callData(scope, 1); - callData->args[0] = value; - callData->thisObject = this; - setter->call(scope, callData); - return !internalClass()->engine->hasException; + if (r->internalClass()->vtable->defineOwnProperty == virtualDefineOwnProperty) { + // standard object, we can avoid some more checks + uint index = id.asArrayIndex(); + if (index == UINT_MAX) { + ScopedStringOrSymbol s(scope, id.asStringOrSymbol()); + r->insertMember(s, value); + } else { + r->arraySet(index, value); + } + return true; } - arraySet(index, value); - return true; - - reject: - // ### this should be removed once everything is ported to use Object::setIndexed() - if (engine->current->strictMode) - engine->throwTypeError(); - return false; + p->value = value; + return r->defineOwnProperty(id, p, attrs); } // Section 8.12.7 -bool Object::internalDeleteProperty(String *name) +bool Object::internalDeleteProperty(PropertyKey id) { if (internalClass()->engine->hasException) return false; - uint idx = name->asArrayIndex(); - if (idx != UINT_MAX) - return deleteIndexedProperty(idx); - - name->makeIdentifier(); + if (id.isArrayIndex()) { + uint index = id.asArrayIndex(); + Scope scope(engine()); + if (scope.engine->hasException) + return false; - uint memberIdx = internalClass()->find(name->identifier()); - if (memberIdx != UINT_MAX) { - if (internalClass()->propertyData[memberIdx].isConfigurable()) { - InternalClass::removeMember(this, name->identifier()); + Scoped<ArrayData> ad(scope, arrayData()); + if (!ad || ad->vtable()->del(this, index)) return true; - } - if (engine()->current->strictMode) - engine()->throwTypeError(); - return false; - } - return true; -} - -bool Object::internalDeleteIndexedProperty(uint index) -{ - Scope scope(engine()); - if (scope.engine->hasException) return false; - - Scoped<ArrayData> ad(scope, arrayData()); - if (!ad || ad->vtable()->del(this, index)) - return true; - - if (engine()->current->strictMode) - engine()->throwTypeError(); - return false; -} - -// Section 8.12.9 -bool Object::__defineOwnProperty__(ExecutionEngine *engine, String *name, const Property *p, PropertyAttributes attrs) -{ - uint idx = name->asArrayIndex(); - if (idx != UINT_MAX) - return __defineOwnProperty__(engine, idx, p, attrs); - - Scope scope(engine); - name->makeIdentifier(); - - uint memberIndex; - - if (isArrayObject() && name->equals(engine->id_length())) { - Q_ASSERT(Heap::ArrayObject::LengthPropertyIndex == internalClass()->find(engine->id_length())); - ScopedProperty lp(scope); - PropertyAttributes cattrs; - getProperty(Heap::ArrayObject::LengthPropertyIndex, lp, &cattrs); - if (attrs.isEmpty() || p->isSubset(attrs, lp, cattrs)) - return true; - if (!cattrs.isWritable() || attrs.type() == PropertyAttributes::Accessor || attrs.isConfigurable() || attrs.isEnumerable()) - goto reject; - bool succeeded = true; - if (attrs.type() == PropertyAttributes::Data) { - bool ok; - uint l = p->value.asArrayLength(&ok); - if (!ok) { - ScopedValue v(scope, p->value); - engine->throwRangeError(v); - return false; - } - succeeded = setArrayLength(l); - } - if (attrs.hasWritable() && !attrs.isWritable()) { - cattrs.setWritable(false); - InternalClass::changeMember(this, engine->id_length(), cattrs); - } - if (!succeeded) - goto reject; - return true; } - // Clause 1 - memberIndex = internalClass()->find(name->identifier()); - - if (memberIndex == UINT_MAX) { - // clause 3 - if (!isExtensible()) - goto reject; - // clause 4 - ScopedProperty pd(scope); - pd->copy(p, attrs); - pd->fullyPopulated(&attrs); - insertMember(name, pd, attrs); - return true; - } - - return __defineOwnProperty__(engine, memberIndex, name, p, attrs); -reject: - if (engine->current->strictMode) - engine->throwTypeError(); - return false; -} - -bool Object::__defineOwnProperty__(ExecutionEngine *engine, uint index, const Property *p, PropertyAttributes attrs) -{ - // 15.4.5.1, 4b - if (isArrayObject() && index >= getLength() && !internalClass()->propertyData[Heap::ArrayObject::LengthPropertyIndex].isWritable()) - goto reject; - - if (ArgumentsObject::isNonStrictArgumentsObject(this)) - return static_cast<ArgumentsObject *>(this)->defineOwnProperty(engine, index, p, attrs); - - return defineOwnProperty2(engine, index, p, attrs); -reject: - if (engine->current->strictMode) - engine->throwTypeError(); - return false; -} - -bool Object::defineOwnProperty2(ExecutionEngine *engine, uint index, const Property *p, PropertyAttributes attrs) -{ - bool hasProperty = 0; - - // Clause 1 - if (arrayData()) { - hasProperty = arrayData()->mappedIndex(index) != UINT_MAX; - if (!hasProperty && isStringObject()) - hasProperty = (index < static_cast<StringObject *>(this)->length()); - } - - if (!hasProperty) { - // clause 3 - if (!isExtensible()) - goto reject; - // clause 4 - Scope scope(engine); - ScopedProperty pp(scope); - pp->copy(p, attrs); - pp->fullyPopulated(&attrs); - if (attrs == Attr_Data) { - ScopedValue v(scope, pp->value); - arraySet(index, v); - } else { - arraySet(index, pp, attrs); + auto memberIdx = internalClass()->findValueOrGetter(id); + if (memberIdx.isValid()) { + if (memberIdx.attrs.isConfigurable()) { + Heap::InternalClass::removeMember(this, id); + return true; } - return true; + return false; } - return __defineOwnProperty__(engine, index, 0, p, attrs); -reject: - if (engine->current->strictMode) - engine->throwTypeError(); - return false; + return true; } -bool Object::__defineOwnProperty__(ExecutionEngine *engine, uint index, String *member, const Property *p, PropertyAttributes attrs) +bool Object::internalDefineOwnProperty(ExecutionEngine *engine, uint index, const InternalClassEntry *memberEntry, const Property *p, PropertyAttributes attrs) { // clause 5 if (attrs.isEmpty()) @@ -1025,9 +598,9 @@ bool Object::__defineOwnProperty__(ExecutionEngine *engine, uint index, String * Scope scope(engine); ScopedProperty current(scope); PropertyAttributes cattrs; - if (member) { - getProperty(index, current, &cattrs); - cattrs = internalClass()->propertyData[index]; + if (memberEntry) { + getProperty(*memberEntry, current); + cattrs = memberEntry->attributes; } else if (arrayData()) { arrayData()->getProperty(index, current, &cattrs); cattrs = arrayData()->attributes(index); @@ -1040,9 +613,9 @@ bool Object::__defineOwnProperty__(ExecutionEngine *engine, uint index, String * // clause 7 if (!cattrs.isConfigurable()) { if (attrs.isConfigurable()) - goto reject; + return false; if (attrs.hasEnumerable() && attrs.isEnumerable() != cattrs.isEnumerable()) - goto reject; + return false; } // clause 8 @@ -1053,70 +626,59 @@ bool Object::__defineOwnProperty__(ExecutionEngine *engine, uint index, String * if (cattrs.isData() != attrs.isData()) { // 9a if (!cattrs.isConfigurable()) - goto reject; + return false; if (cattrs.isData()) { // 9b cattrs.setType(PropertyAttributes::Accessor); cattrs.clearWritable(); - if (!member) { + if (!memberEntry) { // need to convert the array and the slot initSparseArray(); Q_ASSERT(arrayData()); setArrayAttributes(index, cattrs); } - current->setGetter(0); - current->setSetter(0); + current->setGetter(nullptr); + current->setSetter(nullptr); } else { // 9c cattrs.setType(PropertyAttributes::Data); cattrs.setWritable(false); - if (!member) { + if (!memberEntry) { // need to convert the array and the slot setArrayAttributes(index, cattrs); } - current->value = Primitive::undefinedValue(); + current->value = Value::undefinedValue(); } } else if (cattrs.isData() && attrs.isData()) { // clause 10 if (!cattrs.isConfigurable() && !cattrs.isWritable()) { if (attrs.isWritable() || !current->value.sameValue(p->value)) - goto reject; + return false; } } else { // clause 10 Q_ASSERT(cattrs.isAccessor() && attrs.isAccessor()); if (!cattrs.isConfigurable()) { if (!p->value.isEmpty() && current->value.rawValue() != p->value.rawValue()) - goto reject; + return false; if (!p->set.isEmpty() && current->set.rawValue() != p->set.rawValue()) - goto reject; + return false; } } accept: current->merge(cattrs, p, attrs); - if (member) { - InternalClass::changeMember(this, member, cattrs); - setProperty(index, current); + if (memberEntry) { + PropertyKey key = internalClass()->nameMap.at(memberEntry->index); + InternalClassEntry e; + Heap::InternalClass::changeMember(this, key, cattrs, &e); + setProperty(e, current); } else { setArrayAttributes(index, cattrs); arrayData()->setProperty(scope.engine, index, current); } return true; - reject: - if (engine->current->strictMode) - engine->throwTypeError(); - return false; -} - - -bool Object::__defineOwnProperty__(ExecutionEngine *engine, const QString &name, const Property *p, PropertyAttributes attrs) -{ - Scope scope(engine); - ScopedString s(scope, engine->newString(name)); - return __defineOwnProperty__(engine, s, p, attrs); } - void Object::copyArrayData(Object *other) { Q_ASSERT(isArrayObject()); @@ -1129,7 +691,7 @@ void Object::copyArrayData(Object *other) ScopedValue v(scope); for (uint i = 0; i < len; ++i) { - arraySet(i, (v = other->getIndexed(i))); + arraySet(i, (v = other->get(i))); } } else if (!other->arrayData()) { ; @@ -1141,7 +703,6 @@ void Object::copyArrayData(Object *other) Heap::ArrayData *od = other->d()->arrayData; Heap::ArrayData *dd = d()->arrayData; dd->sparse = new SparseArray(*od->sparse); - dd->freeList = od->freeList; } else { Heap::ArrayData *dd = d()->arrayData; dd->values.size = other->d()->arrayData->values.size; @@ -1153,15 +714,15 @@ void Object::copyArrayData(Object *other) setArrayLengthUnchecked(other->getLength()); } -uint Object::getLength(const Managed *m) +qint64 Object::virtualGetLength(const Managed *m) { Scope scope(static_cast<const Object *>(m)->engine()); ScopedValue v(scope, static_cast<Object *>(const_cast<Managed *>(m))->get(scope.engine->id_length())); - return v->toUInt32(); + return v->toLength(); } // 'var' is 'V' in 15.3.5.3. -ReturnedValue Object::instanceOf(const Object *typeObject, const Value &var) +ReturnedValue Object::virtualInstanceOf(const Object *typeObject, const Value &var) { QV4::ExecutionEngine *engine = typeObject->internalClass()->engine; @@ -1170,9 +731,16 @@ ReturnedValue Object::instanceOf(const Object *typeObject, const Value &var) if (!function) return engine->throwTypeError(); - Heap::FunctionObject *f = function->d(); - if (function->isBoundFunction()) - f = function->cast<BoundFunction>()->target(); + return checkedInstanceOf(engine, function, var); +} + +ReturnedValue Object::checkedInstanceOf(ExecutionEngine *engine, const FunctionObject *f, const Value &var) +{ + Scope scope(engine); + if (f->isBoundFunction()) { + ScopedValue v(scope, static_cast<const BoundFunction *>(f)->target()); + f = v->as<FunctionObject>(); + } // 15.3.5.3, 1: HasInstance can only be used on an object const Object *lhs = var.as<Object>(); @@ -1180,9 +748,10 @@ ReturnedValue Object::instanceOf(const Object *typeObject, const Value &var) return Encode(false); // 15.3.5.3, 2 - const Object *o = f->protoProperty(); + Value p = Value::fromReturnedValue(f->protoProperty()); + const Object *o = p.objectValue(); if (!o) // 15.3.5.3, 3 - return engine->throwTypeError(); + return f->engine()->throwTypeError(); Heap::Object *v = lhs->d(); @@ -1203,6 +772,142 @@ ReturnedValue Object::instanceOf(const Object *typeObject, const Value &var) return Encode(false); } +bool Object::virtualHasProperty(const Managed *m, PropertyKey id) +{ + Scope scope(m->engine()); + ScopedObject o(scope, m); + ScopedProperty p(scope); + + if (o->getOwnProperty(id, p) != Attr_Invalid) + return true; + + o = o->getPrototypeOf(); + if (o) + return o->hasProperty(id); + + return false; +} + +PropertyAttributes Object::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) +{ + PropertyAttributes attrs; + const Object *o = static_cast<const Object *>(m); + if (id.isArrayIndex()) { + uint index = id.asArrayIndex(); + if (o->arrayData()) { + if (o->arrayData()->getProperty(index, p, &attrs)) + return attrs; + } + } else { + Q_ASSERT(id.asStringOrSymbol()); + + auto member = o->internalClass()->find(id); + if (member.isValid()) { + attrs = member.attributes; + if (p) { + p->value = *o->propertyData(member.index); + if (attrs.isAccessor()) + p->set = *o->propertyData(member.setterIndex); + } + return attrs; + } + } + + return Attr_Invalid; +} + +bool Object::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) +{ + Object *o = static_cast<Object *>(m); + Scope scope(o); + + if (id.isArrayIndex()) { + uint index = id.asArrayIndex(); + + bool hasProperty = false; + + if (o->arrayData()) { + hasProperty = o->arrayData()->mappedIndex(index) != UINT_MAX; + if (!hasProperty && o->isStringObject()) + hasProperty = (index < static_cast<StringObject *>(o)->length()); + } + + if (!hasProperty) { + if (!o->isExtensible()) + return false; + + ScopedProperty pp(scope); + pp->copy(p, attrs); + pp->fullyPopulated(&attrs); + if (attrs == Attr_Data) { + ScopedValue v(scope, pp->value); + o->arraySet(index, v); + } else { + o->arraySet(index, pp, attrs); + } + return true; + } + + return o->internalDefineOwnProperty(scope.engine, index, nullptr, p, attrs); + } + + auto memberIndex = o->internalClass()->find(id); + + if (!memberIndex.isValid()) { + if (!o->isExtensible()) + return false; + + Scoped<StringOrSymbol> name(scope, id.asStringOrSymbol()); + ScopedProperty pd(scope); + pd->copy(p, attrs); + pd->fullyPopulated(&attrs); + o->insertMember(name, pd, attrs); + return true; + } + + return o->internalDefineOwnProperty(scope.engine, UINT_MAX, &memberIndex, p, attrs); +} + +bool Object::virtualIsExtensible(const Managed *m) +{ + return m->d()->internalClass->extensible; +} + +bool Object::virtualPreventExtensions(Managed *m) +{ + Q_ASSERT(m->isObject()); + Object *o = static_cast<Object *>(m); + o->setInternalClass(o->internalClass()->nonExtensible()); + return true; +} + +Heap::Object *Object::virtualGetPrototypeOf(const Managed *m) +{ + return m->internalClass()->prototype; +} + +bool Object::virtualSetPrototypeOf(Managed *m, const Object *proto) +{ + Q_ASSERT(m->isObject()); + Object *o = static_cast<Object *>(m); + Heap::Object *current = o->internalClass()->prototype; + Heap::Object *protod = proto ? proto->d() : nullptr; + if (current == protod) + return true; + if (!o->internalClass()->extensible) + return false; + Heap::Object *p = protod; + while (p) { + if (p == o->d()) + return false; + if (p->vtable()->getPrototypeOf != Object::staticVTable()->getPrototypeOf) + break; + p = p->prototype(); + } + o->setInternalClass(o->internalClass()->changePrototype(protod)); + return true; +} + bool Object::setArrayLength(uint newLen) { Q_ASSERT(isArrayObject()); @@ -1211,9 +916,7 @@ bool Object::setArrayLength(uint newLen) uint oldLen = getLength(); bool ok = true; if (newLen < oldLen) { - if (!arrayData()) { - Q_ASSERT(!newLen); - } else { + if (arrayData()) { uint l = arrayData()->vtable()->truncate(this, newLen); if (l != newLen) ok = false; @@ -1235,6 +938,69 @@ void Object::initSparseArray() ArrayData::realloc(this, Heap::ArrayData::Sparse, 0, false); } +bool Object::isConcatSpreadable() const +{ + Scope scope(this); + ScopedValue spreadable(scope, get(scope.engine->symbol_isConcatSpreadable())); + if (!spreadable->isUndefined()) + return spreadable->toBoolean(); + return isArray(); +} + +bool Object::isArray() const +{ + if (isArrayObject()) + return true; + if (vtable() == ProxyObject::staticVTable()) { + const ProxyObject *p = static_cast<const ProxyObject *>(this); + Scope scope(this); + if (!p->d()->handler) { + scope.engine->throwTypeError(); + return false; + } + ScopedObject o(scope, p->d()->target); + return o->isArray(); + } + return false; +} + +const FunctionObject *Object::speciesConstructor(Scope &scope, const FunctionObject *defaultConstructor) const +{ + ScopedValue C(scope, get(scope.engine->id_constructor())); + if (C->isUndefined()) + return defaultConstructor; + const Object *c = C->objectValue(); + if (!c) { + scope.engine->throwTypeError(); + return nullptr; + } + ScopedValue S(scope, c->get(scope.engine->symbol_species())); + if (S->isNullOrUndefined()) + return defaultConstructor; + const FunctionObject *f = S->as<FunctionObject>(); + if (!f || !f->isConstructor()) { + scope.engine->throwTypeError(); + return nullptr; + } + Q_ASSERT(f->isFunctionObject()); + return static_cast<const FunctionObject *>(f); +} + +bool Object::setProtoFromNewTarget(const Value *newTarget) +{ + if (!newTarget || newTarget->isUndefined()) + return false; + + Q_ASSERT(newTarget->isFunctionObject()); + Scope scope(this); + ScopedObject proto(scope, static_cast<const FunctionObject *>(newTarget)->protoProperty()); + if (proto) { + setPrototypeOf(proto); + return true; + } + return false; +} + DEFINE_OBJECT_VTABLE(ArrayObject); @@ -1257,25 +1023,10 @@ void Heap::ArrayObject::init(const QStringList &list) a->setArrayLengthUnchecked(len); } -ReturnedValue ArrayObject::getLookup(const Managed *m, Lookup *l) -{ - Scope scope(static_cast<const Object *>(m)->engine()); - ScopedString name(scope, scope.engine->current->compilationUnit->runtimeStrings[l->nameIndex]); - if (name->equals(scope.engine->id_length())) { - // special case, as the property is on the object itself - l->getter = Lookup::arrayLengthGetter; - const ArrayObject *a = static_cast<const ArrayObject *>(m); - return a->propertyData(Heap::ArrayObject::LengthPropertyIndex)->asReturnedValue(); - } - return Object::getLookup(m, l); -} - -uint ArrayObject::getLength(const Managed *m) +qint64 ArrayObject::virtualGetLength(const Managed *m) { const ArrayObject *a = static_cast<const ArrayObject *>(m); - if (a->propertyData(Heap::ArrayObject::LengthPropertyIndex)->isInteger()) - return a->propertyData(Heap::ArrayObject::LengthPropertyIndex)->integerValue(); - return Primitive::toUInt32(a->propertyData(Heap::ArrayObject::LengthPropertyIndex)->doubleValue()); + return a->propertyData(Heap::ArrayObject::LengthPropertyIndex)->toLength(); } QStringList ArrayObject::toQStringList() const @@ -1288,8 +1039,62 @@ QStringList ArrayObject::toQStringList() const uint length = getLength(); for (uint i = 0; i < length; ++i) { - v = const_cast<ArrayObject *>(this)->getIndexed(i); + v = const_cast<ArrayObject *>(this)->get(i); result.append(v->toQStringNoThrow()); } return result; } + +bool ArrayObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) +{ + Q_ASSERT(m->isArrayObject()); + ArrayObject *a = static_cast<ArrayObject *>(m); + + if (id.isArrayIndex()) { + uint index = id.asArrayIndex(); + uint len = a->getLength(); + if (index >= len && !a->internalClass()->propertyData[Heap::ArrayObject::LengthPropertyIndex].isWritable()) + return false; + + bool succeeded = Object::virtualDefineOwnProperty(m, id, p, attrs); + if (!succeeded) + return false; + + if (index >= len) + a->setArrayLengthUnchecked(index + 1); + + return true; + } + + ExecutionEngine *engine = m->engine(); + if (id == engine->id_length()->propertyKey()) { + Scope scope(engine); + Q_ASSERT(a->internalClass()->verifyIndex(engine->id_length()->propertyKey(), Heap::ArrayObject::LengthPropertyIndex)); + ScopedProperty lp(scope); + InternalClassEntry e = a->internalClass()->find(scope.engine->id_length()->propertyKey()); + a->getProperty(e, lp); + if (attrs.isEmpty() || p->isSubset(attrs, lp, e.attributes)) + return true; + if (!e.attributes.isWritable() || attrs.type() == PropertyAttributes::Accessor || attrs.isConfigurable() || attrs.isEnumerable()) + return false; + bool succeeded = true; + if (attrs.type() == PropertyAttributes::Data) { + bool ok; + uint l = p->value.asArrayLength(&ok); + if (!ok) { + ScopedValue v(scope, p->value); + engine->throwRangeError(v); + return false; + } + succeeded = a->setArrayLength(l); + } + if (attrs.hasWritable() && !attrs.isWritable()) { + e.attributes.setWritable(false); + Heap::InternalClass::changeMember(a, engine->id_length()->propertyKey(), e.attributes); + } + if (!succeeded) + return false; + return true; + } + return Object::virtualDefineOwnProperty(m, id, p, attrs); +} diff --git a/src/qml/jsruntime/qv4object_p.h b/src/qml/jsruntime/qv4object_p.h index 066a93cc61..72b6703554 100644 --- a/src/qml/jsruntime/qv4object_p.h +++ b/src/qml/jsruntime/qv4object_p.h @@ -57,46 +57,52 @@ #include "qv4scopedvalue_p.h" #include "qv4value_p.h" #include "qv4internalclass_p.h" +#include "qv4string_p.h" QT_BEGIN_NAMESPACE namespace QV4 { -struct BuiltinFunction; - namespace Heap { #define ObjectMembers(class, Member) \ Member(class, Pointer, MemberData *, memberData) \ Member(class, Pointer, ArrayData *, arrayData) -DECLARE_HEAP_OBJECT(Object, Base) { - DECLARE_MARK_TABLE(Object); +DECLARE_EXPORTED_HEAP_OBJECT(Object, Base) { + static void markObjects(Heap::Base *base, MarkStack *stack); void init() { Base::init(); } - void destroy() { Base::destroy(); } + const VTable *vtable() const { + return internalClass->vtable; + } + + const Value *inlinePropertyDataWithOffset(uint indexWithOffset) const { + Q_ASSERT(indexWithOffset >= vtable()->inlinePropertyOffset && indexWithOffset < uint(vtable()->inlinePropertyOffset + vtable()->nInlineProperties)); + return reinterpret_cast<const Value *>(this) + indexWithOffset; + } const Value *inlinePropertyData(uint index) const { Q_ASSERT(index < vtable()->nInlineProperties); return reinterpret_cast<const Value *>(this) + vtable()->inlinePropertyOffset + index; } - void setInlineProperty(ExecutionEngine *e, uint index, Value v) { - Q_ASSERT(index < vtable()->nInlineProperties); - Value *prop = reinterpret_cast<Value *>(this) + vtable()->inlinePropertyOffset + index; - WriteBarrier::write(e, this, prop, v); + void setInlinePropertyWithOffset(ExecutionEngine *e, uint indexWithOffset, Value v) { + Q_ASSERT(indexWithOffset >= vtable()->inlinePropertyOffset && indexWithOffset < uint(vtable()->inlinePropertyOffset + vtable()->nInlineProperties)); + Value *prop = reinterpret_cast<Value *>(this) + indexWithOffset; + WriteBarrier::write(e, this, prop->data_ptr(), v.asReturnedValue()); } - void setInlineProperty(ExecutionEngine *e, uint index, Heap::Base *b) { - Q_ASSERT(index < vtable()->nInlineProperties); - Value *prop = reinterpret_cast<Value *>(this) + vtable()->inlinePropertyOffset + index; - WriteBarrier::write(e, this, prop, b); + void setInlinePropertyWithOffset(ExecutionEngine *e, uint indexWithOffset, Heap::Base *b) { + Q_ASSERT(indexWithOffset >= vtable()->inlinePropertyOffset && indexWithOffset < uint(vtable()->inlinePropertyOffset + vtable()->nInlineProperties)); + Value *prop = reinterpret_cast<Value *>(this) + indexWithOffset; + WriteBarrier::write(e, this, prop->data_ptr(), Value::fromHeapObject(b).asReturnedValue()); } - QV4::MemberData::Index writablePropertyData(uint index) { + PropertyIndex writablePropertyData(uint index) { uint nInline = vtable()->nInlineProperties; if (index < nInline) - return { this, reinterpret_cast<Value *>(this) + vtable()->inlinePropertyOffset + index}; + return PropertyIndex{ this, reinterpret_cast<Value *>(this) + vtable()->inlinePropertyOffset + index}; index -= nInline; - return { memberData, memberData->values.values + index }; + return PropertyIndex{ memberData, memberData->values.values + index }; } const Value *propertyData(uint index) const { @@ -109,7 +115,7 @@ DECLARE_HEAP_OBJECT(Object, Base) { void setProperty(ExecutionEngine *e, uint index, Value v) { uint nInline = vtable()->nInlineProperties; if (index < nInline) { - setInlineProperty(e, index, v); + setInlinePropertyWithOffset(e, index + vtable()->inlinePropertyOffset, v); return; } index -= nInline; @@ -118,198 +124,120 @@ DECLARE_HEAP_OBJECT(Object, Base) { void setProperty(ExecutionEngine *e, uint index, Heap::Base *b) { uint nInline = vtable()->nInlineProperties; if (index < nInline) { - setInlineProperty(e, index, b); + setInlinePropertyWithOffset(e, index + vtable()->inlinePropertyOffset, b); return; } index -= nInline; memberData->values.set(e, index, b); } - Heap::Object *prototype() const { return internalClass->prototype; } -}; - -Q_STATIC_ASSERT(Object::markTable == ((2 << 2) | (2 << 4))); - -} + void setUsedAsProto(); -#define V4_OBJECT(superClass) \ - public: \ - Q_MANAGED_CHECK \ - typedef superClass SuperClass; \ - static const QV4::ObjectVTable static_vtbl; \ - static inline const QV4::VTable *staticVTable() { return &static_vtbl.vTable; } \ - V4_MANAGED_SIZE_TEST \ - Data *d_unchecked() const { return static_cast<Data *>(m()); } \ - Data *d() const { \ - Data *dptr = d_unchecked(); \ - dptr->_checkIsInitialized(); \ - return dptr; \ - } \ - V4_ASSERT_IS_TRIVIAL(Data); - -#define V4_OBJECT2(DataClass, superClass) \ - private: \ - DataClass() Q_DECL_EQ_DELETE; \ - Q_DISABLE_COPY(DataClass) \ - public: \ - Q_MANAGED_CHECK \ - typedef QV4::Heap::DataClass Data; \ - typedef superClass SuperClass; \ - static const QV4::ObjectVTable static_vtbl; \ - static inline const QV4::VTable *staticVTable() { return &static_vtbl.vTable; } \ - V4_MANAGED_SIZE_TEST \ - QV4::Heap::DataClass *d_unchecked() const { return static_cast<QV4::Heap::DataClass *>(m()); } \ - QV4::Heap::DataClass *d() const { \ - QV4::Heap::DataClass *dptr = d_unchecked(); \ - dptr->_checkIsInitialized(); \ - return dptr; \ - } \ - V4_ASSERT_IS_TRIVIAL(QV4::Heap::DataClass); \ - static Q_CONSTEXPR quint64 markTable = QV4::Heap::DataClass::markTable; - -#define V4_PROTOTYPE(p) \ - static QV4::Object *defaultPrototype(QV4::ExecutionEngine *e) \ - { return e->p(); } - -struct ObjectVTable -{ - VTable vTable; - void (*call)(const Managed *, Scope &scope, CallData *data); - void (*construct)(const Managed *, Scope &scope, CallData *data); - ReturnedValue (*get)(const Managed *, String *name, bool *hasProperty); - ReturnedValue (*getIndexed)(const Managed *, uint index, bool *hasProperty); - bool (*put)(Managed *, String *name, const Value &value); - bool (*putIndexed)(Managed *, uint index, const Value &value); - PropertyAttributes (*query)(const Managed *, String *name); - PropertyAttributes (*queryIndexed)(const Managed *, uint index); - bool (*deleteProperty)(Managed *m, String *name); - bool (*deleteIndexedProperty)(Managed *m, uint index); - ReturnedValue (*getLookup)(const Managed *m, Lookup *l); - void (*setLookup)(Managed *m, Lookup *l, const Value &v); - uint (*getLength)(const Managed *m); - void (*advanceIterator)(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attributes); - ReturnedValue (*instanceOf)(const Object *typeObject, const Value &var); + Heap::Object *prototype() const { return internalClass->prototype; } }; -#define DEFINE_OBJECT_VTABLE_BASE(classname) \ -const QV4::ObjectVTable classname::static_vtbl = \ -{ \ - DEFINE_MANAGED_VTABLE_INT(classname, (std::is_same<classname::SuperClass, Object>::value) ? nullptr : &classname::SuperClass::static_vtbl.vTable), \ - call, \ - construct, \ - get, \ - getIndexed, \ - put, \ - putIndexed, \ - query, \ - queryIndexed, \ - deleteProperty, \ - deleteIndexedProperty, \ - getLookup, \ - setLookup, \ - getLength, \ - advanceIterator, \ - instanceOf \ } -#define DEFINE_OBJECT_VTABLE(classname) \ -QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_ON \ -DEFINE_OBJECT_VTABLE_BASE(classname) \ -QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_OFF - -#define DEFINE_OBJECT_TEMPLATE_VTABLE(classname) \ -QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_ON \ -template<> DEFINE_OBJECT_VTABLE_BASE(classname) \ -QT_WARNING_SUPPRESS_GCC_TAUTOLOGICAL_COMPARE_OFF - struct Q_QML_EXPORT Object: Managed { V4_OBJECT2(Object, Object) Q_MANAGED_TYPE(Object) V4_INTERNALCLASS(Object) V4_PROTOTYPE(objectPrototype) + enum { NInlineProperties = 2 }; + enum { IsObject = true, GetterOffset = 0, SetterOffset = 1 }; - void setInternalClass(InternalClass *ic); + void setInternalClass(Heap::InternalClass *ic); const Value *propertyData(uint index) const { return d()->propertyData(index); } Heap::ArrayData *arrayData() const { return d()->arrayData; } void setArrayData(ArrayData *a) { d()->arrayData.set(engine(), a->d()); } - void getProperty(uint index, Property *p, PropertyAttributes *attrs) const; - void setProperty(uint index, const Property *p); + void getProperty(const InternalClassEntry &entry, Property *p) const; + void setProperty(const InternalClassEntry &entry, const Property *p); void setProperty(uint index, Value v) const { d()->setProperty(engine(), index, v); } void setProperty(uint index, Heap::Base *b) const { d()->setProperty(engine(), index, b); } void setProperty(ExecutionEngine *engine, uint index, Value v) const { d()->setProperty(engine, index, v); } void setProperty(ExecutionEngine *engine, uint index, Heap::Base *b) const { d()->setProperty(engine, index, b); } - const ObjectVTable *vtable() const { return reinterpret_cast<const ObjectVTable *>(d()->vtable()); } - Heap::Object *prototype() const { return d()->prototype(); } - bool setPrototype(Object *proto); - - void getOwnProperty(String *name, PropertyAttributes *attrs, Property *p = 0); - void getOwnProperty(uint index, PropertyAttributes *attrs, Property *p = 0); + const VTable *vtable() const { return d()->vtable(); } - MemberData::Index getValueOrSetter(String *name, PropertyAttributes *attrs); - ArrayData::Index getValueOrSetter(uint index, PropertyAttributes *attrs); + PropertyAttributes getOwnProperty(PropertyKey id, Property *p = nullptr) const { + return vtable()->getOwnProperty(this, id, p); + } - bool hasProperty(String *name) const; - bool hasProperty(uint index) const; + PropertyIndex getValueOrSetter(PropertyKey id, PropertyAttributes *attrs); - bool hasOwnProperty(String *name) const; - bool hasOwnProperty(uint index) const; + bool hasProperty(PropertyKey id) const { + return vtable()->hasProperty(this, id); + } - bool __defineOwnProperty__(ExecutionEngine *engine, uint index, String *member, const Property *p, PropertyAttributes attrs); - bool __defineOwnProperty__(ExecutionEngine *engine, String *name, const Property *p, PropertyAttributes attrs); - bool __defineOwnProperty__(ExecutionEngine *engine, uint index, const Property *p, PropertyAttributes attrs); - bool __defineOwnProperty__(ExecutionEngine *engine, const QString &name, const Property *p, PropertyAttributes attrs); - bool defineOwnProperty2(ExecutionEngine *engine, uint index, const Property *p, PropertyAttributes attrs); + bool defineOwnProperty(PropertyKey id, const Property *p, PropertyAttributes attrs) { + return vtable()->defineOwnProperty(this, id, p, attrs); + } // // helpers // - static ReturnedValue getValue(const Value &thisObject, const Value &v, PropertyAttributes attrs); + static ReturnedValue getValue(const Value &thisObject, const Value &v, PropertyAttributes attrs) { + if (attrs.isData()) + return v.asReturnedValue(); + return getValueAccessor(thisObject, v, attrs); + } ReturnedValue getValue(const Value &v, PropertyAttributes attrs) const { - Scope scope(this->engine()); - ScopedValue t(scope, const_cast<Object *>(this)); - return getValue(t, v, attrs); + return getValue(*this, v, attrs); + } + ReturnedValue getValueByIndex(uint propertyIndex) const { + PropertyAttributes attrs = internalClass()->propertyData.at(propertyIndex); + const Value *v = propertyData(propertyIndex); + if (!attrs.isAccessor()) + return v->asReturnedValue(); + return getValueAccessor(*this, *v, attrs); } + static ReturnedValue getValueAccessor(const Value &thisObject, const Value &v, PropertyAttributes attrs); - bool putValue(uint memberIndex, const Value &value); + bool putValue(uint memberIndex, PropertyAttributes attrs, const Value &value); /* The spec default: Writable: true, Enumerable: false, Configurable: true */ - void defineDefaultProperty(String *name, const Value &value) { - insertMember(name, value, Attr_Data|Attr_NotEnumerable); - } - void defineDefaultProperty(const QString &name, const Value &value); - void defineDefaultProperty(const QString &name, void (*code)(const BuiltinFunction *, Scope &, CallData *), int argumentCount = 0); - void defineDefaultProperty(String *name, void (*code)(const BuiltinFunction *, Scope &, CallData *), int argumentCount = 0); - void defineAccessorProperty(const QString &name, void (*getter)(const BuiltinFunction *, Scope &, CallData *), - void (*setter)(const BuiltinFunction *, Scope &, CallData *)); - void defineAccessorProperty(String *name, void (*getter)(const BuiltinFunction *, Scope &, CallData *), - void (*setter)(const BuiltinFunction *, Scope &, CallData *)); + void defineDefaultProperty(StringOrSymbol *name, const Value &value, PropertyAttributes attributes = Attr_Data|Attr_NotEnumerable) { + insertMember(name, value, attributes); + } + void defineDefaultProperty(const QString &name, const Value &value, PropertyAttributes attributes = Attr_Data|Attr_NotEnumerable); + void defineDefaultProperty(const QString &name, VTable::Call code, + int argumentCount = 0, PropertyAttributes attributes = Attr_Data|Attr_NotEnumerable); + void defineDefaultProperty(StringOrSymbol *name, VTable::Call code, + int argumentCount = 0, PropertyAttributes attributes = Attr_Data|Attr_NotEnumerable); + void defineAccessorProperty(const QString &name, VTable::Call getter, VTable::Call setter); + void defineAccessorProperty(StringOrSymbol *name, VTable::Call getter, VTable::Call setter); /* Fixed: Writable: false, Enumerable: false, Configurable: false */ void defineReadonlyProperty(const QString &name, const Value &value); void defineReadonlyProperty(String *name, const Value &value); /* Fixed: Writable: false, Enumerable: false, Configurable: true */ void defineReadonlyConfigurableProperty(const QString &name, const Value &value); - void defineReadonlyConfigurableProperty(String *name, const Value &value); + void defineReadonlyConfigurableProperty(StringOrSymbol *name, const Value &value); + + void addSymbolSpecies(); - void insertMember(String *s, const Value &v, PropertyAttributes attributes = Attr_Data) { + void insertMember(StringOrSymbol *s, const Value &v, PropertyAttributes attributes = Attr_Data) { Scope scope(engine()); ScopedProperty p(scope); p->value = v; insertMember(s, p, attributes); } - void insertMember(String *s, const Property *p, PropertyAttributes attributes); + void insertMember(StringOrSymbol *s, const Property *p, PropertyAttributes attributes); - bool isExtensible() const { return d()->internalClass->extensible; } + bool isExtensible() const { return vtable()->isExtensible(this); } + bool preventExtensions() { return vtable()->preventExtensions(this); } + Heap::Object *getPrototypeOf() const { return vtable()->getPrototypeOf(this); } + bool setPrototypeOf(const Object *p) { return vtable()->setPrototypeOf(this, p); } + void setPrototypeUnchecked(const Object *p); // Array handling @@ -362,117 +290,130 @@ public: } void initSparseArray(); - SparseArrayNode *sparseBegin() { return arrayType() == Heap::ArrayData::Sparse ? d()->arrayData->sparse->begin() : 0; } - SparseArrayNode *sparseEnd() { return arrayType() == Heap::ArrayData::Sparse ? d()->arrayData->sparse->end() : 0; } + SparseArrayNode *sparseBegin() const { return arrayType() == Heap::ArrayData::Sparse ? d()->arrayData->sparse->begin() : nullptr; } + SparseArrayNode *sparseEnd() const { return arrayType() == Heap::ArrayData::Sparse ? d()->arrayData->sparse->end() : nullptr; } inline bool protoHasArray() { Scope scope(engine()); ScopedObject p(scope, this); - while ((p = p->prototype())) + while ((p = p->getPrototypeOf())) if (p->arrayData()) return true; return false; } - inline ReturnedValue get(String *name, bool *hasProperty = 0) const - { return vtable()->get(this, name, hasProperty); } - inline ReturnedValue getIndexed(uint idx, bool *hasProperty = 0) const - { return vtable()->getIndexed(this, idx, hasProperty); } + inline ReturnedValue get(StringOrSymbol *name, bool *hasProperty = nullptr, const Value *receiver = nullptr) const + { if (!receiver) receiver = this; return vtable()->get(this, name->toPropertyKey(), receiver, hasProperty); } + inline ReturnedValue get(uint idx, bool *hasProperty = nullptr, const Value *receiver = nullptr) const + { if (!receiver) receiver = this; return vtable()->get(this, PropertyKey::fromArrayIndex(idx), receiver, hasProperty); } + QT_DEPRECATED inline ReturnedValue getIndexed(uint idx, bool *hasProperty = nullptr) const + { return get(idx, hasProperty); } + inline ReturnedValue get(PropertyKey id, const Value *receiver = nullptr, bool *hasProperty = nullptr) const + { if (!receiver) receiver = this; return vtable()->get(this, id, receiver, hasProperty); } // use the set variants instead, to customize throw behavior - inline bool put(String *name, const Value &v) - { return vtable()->put(this, name, v); } - inline bool putIndexed(uint idx, const Value &v) - { return vtable()->putIndexed(this, idx, v); } + inline bool put(StringOrSymbol *name, const Value &v, Value *receiver = nullptr) + { if (!receiver) receiver = this; return vtable()->put(this, name->toPropertyKey(), v, receiver); } + inline bool put(uint idx, const Value &v, Value *receiver = nullptr) + { if (!receiver) receiver = this; return vtable()->put(this, PropertyKey::fromArrayIndex(idx), v, receiver); } + QT_DEPRECATED inline bool putIndexed(uint idx, const Value &v) + { return put(idx, v); } + inline bool put(PropertyKey id, const Value &v, Value *receiver = nullptr) + { if (!receiver) receiver = this; return vtable()->put(this, id, v, receiver); } enum ThrowOnFailure { DoThrowOnRejection, DoNotThrow }; - // ES6: 7.3.3 Set (O, P, V, Throw) - inline bool set(String *name, const Value &v, ThrowOnFailure shouldThrow) + // This is the same as set(), but it doesn't require creating a string key, + // which is much more efficient for the array case. + inline bool setIndexed(uint idx, const Value &v, ThrowOnFailure shouldThrow) { - bool ret = vtable()->put(this, name, v); + bool ret = vtable()->put(this, PropertyKey::fromArrayIndex(idx), v, this); // ES6: 7.3.3, 6: If success is false and Throw is true, throw a TypeError exception. if (!ret && shouldThrow == ThrowOnFailure::DoThrowOnRejection) { ExecutionEngine *e = engine(); if (!e->hasException) { // allow a custom set impl to throw itself QString message = QLatin1String("Cannot assign to read-only property \"") + - name->toQString() + QLatin1Char('\"'); + QString::number(idx) + QLatin1Char('\"'); e->throwTypeError(message); } } return ret; } - inline bool setIndexed(uint idx, const Value &v, ThrowOnFailure shouldThrow) + // ES6: 7.3.3 Set (O, P, V, Throw) + inline bool set(StringOrSymbol *name, const Value &v, ThrowOnFailure shouldThrow) { - bool ret = vtable()->putIndexed(this, idx, v); + bool ret = vtable()->put(this, name->toPropertyKey(), v, this); + // ES6: 7.3.3, 6: If success is false and Throw is true, throw a TypeError exception. if (!ret && shouldThrow == ThrowOnFailure::DoThrowOnRejection) { ExecutionEngine *e = engine(); if (!e->hasException) { // allow a custom set impl to throw itself - e->throwTypeError(); + QString message = QLatin1String("Cannot assign to read-only property \"") + + name->toQString() + QLatin1Char('\"'); + e->throwTypeError(message); } } return ret; } - - PropertyAttributes query(String *name) const - { return vtable()->query(this, name); } - PropertyAttributes queryIndexed(uint index) const - { return vtable()->queryIndexed(this, index); } - bool deleteProperty(String *name) - { return vtable()->deleteProperty(this, name); } - bool deleteIndexedProperty(uint index) - { return vtable()->deleteIndexedProperty(this, index); } - ReturnedValue getLookup(Lookup *l) const - { return vtable()->getLookup(this, l); } - void setLookup(Lookup *l, const Value &v) - { vtable()->setLookup(this, l, v); } - void advanceIterator(ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attributes) - { vtable()->advanceIterator(this, it, name, index, p, attributes); } - uint getLength() const { return vtable()->getLength(this); } + bool deleteProperty(PropertyKey id) + { return vtable()->deleteProperty(this, id); } + OwnPropertyKeyIterator *ownPropertyKeys(Value *target) const + { return vtable()->ownPropertyKeys(this, target); } + qint64 getLength() const { return vtable()->getLength(this); } ReturnedValue instanceOf(const Value &var) const { return vtable()->instanceOf(this, var); } - inline void construct(Scope &scope, CallData *d) const - { return vtable()->construct(this, scope, d); } - inline void call(Scope &scope, CallData *d) const - { vtable()->call(this, scope, d); } + bool isConcatSpreadable() const; + bool isArray() const; + const FunctionObject *speciesConstructor(Scope &scope, const FunctionObject *defaultConstructor) const; + + bool setProtoFromNewTarget(const Value *newTarget); + protected: - static void construct(const Managed *m, Scope &scope, CallData *); - static void call(const Managed *m, Scope &scope, CallData *); - static ReturnedValue get(const Managed *m, String *name, bool *hasProperty); - static ReturnedValue getIndexed(const Managed *m, uint index, bool *hasProperty); - static bool put(Managed *m, String *name, const Value &value); - static bool putIndexed(Managed *m, uint index, const Value &value); - static PropertyAttributes query(const Managed *m, String *name); - static PropertyAttributes queryIndexed(const Managed *m, uint index); - static bool deleteProperty(Managed *m, String *name); - static bool deleteIndexedProperty(Managed *m, uint index); - static ReturnedValue getLookup(const Managed *m, Lookup *l); - static void setLookup(Managed *m, Lookup *l, const Value &v); - static void advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attributes); - static uint getLength(const Managed *m); - static ReturnedValue instanceOf(const Object *typeObject, const Value &var); - static void markObjects(Heap::Base *, MarkStack *); + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver,bool *hasProperty); + static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); + static bool virtualDeleteProperty(Managed *m, PropertyKey id); + static bool virtualHasProperty(const Managed *m, PropertyKey id); + static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); + static bool virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs); + static bool virtualIsExtensible(const Managed *m); + static bool virtualPreventExtensions(Managed *); + static Heap::Object *virtualGetPrototypeOf(const Managed *); + static bool virtualSetPrototypeOf(Managed *, const Object *); + static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); + static qint64 virtualGetLength(const Managed *m); + static ReturnedValue virtualInstanceOf(const Object *typeObject, const Value &var); +public: + // qv4runtime uses this directly + static ReturnedValue checkedInstanceOf(ExecutionEngine *engine, const FunctionObject *typeObject, const Value &var); private: - ReturnedValue internalGet(String *name, bool *hasProperty) const; - ReturnedValue internalGetIndexed(uint index, bool *hasProperty) const; - bool internalPut(String *name, const Value &value); - bool internalPutIndexed(uint index, const Value &value); - bool internalDeleteProperty(String *name); - bool internalDeleteIndexedProperty(uint index); + bool internalDefineOwnProperty(ExecutionEngine *engine, uint index, const InternalClassEntry *memberEntry, const Property *p, PropertyAttributes attrs); + ReturnedValue internalGet(PropertyKey id, const Value *receiver, bool *hasProperty) const; + bool internalPut(PropertyKey id, const Value &value, Value *receiver); + bool internalDeleteProperty(PropertyKey id); friend struct ObjectIterator; friend struct ObjectPrototype; }; +struct ObjectOwnPropertyKeyIterator : OwnPropertyKeyIterator +{ + uint arrayIndex = 0; + uint memberIndex = 0; + bool iterateOverSymbols = false; + SparseArrayNode *arrayNode = nullptr; + ~ObjectOwnPropertyKeyIterator() override = default; + PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; + +}; + namespace Heap { struct BooleanObject : Object { @@ -509,7 +450,7 @@ struct ArrayObject : Object { private: void commonInit() - { setProperty(internalClass->engine, LengthPropertyIndex, Primitive::fromInt32(0)); } + { setProperty(internalClass->engine, LengthPropertyIndex, Value::fromInt32(0)); } }; } @@ -539,17 +480,18 @@ struct ArrayObject: Object { void init(ExecutionEngine *engine); - static ReturnedValue getLookup(const Managed *m, Lookup *l); - using Object::getLength; - static uint getLength(const Managed *m); + static qint64 virtualGetLength(const Managed *m); QStringList toQStringList() const; +protected: + static bool virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs); + }; inline void Object::setArrayLengthUnchecked(uint l) { if (isArrayObject()) - setProperty(Heap::ArrayObject::LengthPropertyIndex, Primitive::fromUInt32(l)); + setProperty(Heap::ArrayObject::LengthPropertyIndex, Value::fromUInt32(l)); } inline void Object::push_back(const Value &v) @@ -592,7 +534,7 @@ inline void Object::arraySet(uint index, const Value &value) template<> inline const ArrayObject *Value::as() const { - return isManaged() && m()->vtable()->type == Managed::Type_ArrayObject ? static_cast<const ArrayObject *>(this) : 0; + return isManaged() && m()->internalClass->vtable->type == Managed::Type_ArrayObject ? static_cast<const ArrayObject *>(this) : nullptr; } #ifndef V4_BOOTSTRAP diff --git a/src/qml/jsruntime/qv4objectiterator.cpp b/src/qml/jsruntime/qv4objectiterator.cpp index 3427ee89fe..e529b8e86b 100644 --- a/src/qml/jsruntime/qv4objectiterator.cpp +++ b/src/qml/jsruntime/qv4objectiterator.cpp @@ -42,145 +42,170 @@ #include "qv4identifier_p.h" #include "qv4argumentsobject_p.h" #include "qv4string_p.h" +#include "qv4iterator_p.h" +#include "qv4propertykey_p.h" using namespace QV4; -void ObjectIterator::init(const Object *o) +void ForInIteratorPrototype::init(ExecutionEngine *) { - object->setM(o ? o->m() : 0); - current->setM(o ? o->m() : 0); - - if (object->as<ArgumentsObject>()) { - Scope scope(engine); - Scoped<ArgumentsObject> (scope, object->asReturnedValue())->fullyCreate(); - } + defineDefaultProperty(QStringLiteral("next"), method_next, 0); } -void ObjectIterator::next(Value *name, uint *index, Property *pd, PropertyAttributes *attrs) +PropertyKey ObjectIterator::next(Property *pd, PropertyAttributes *attrs) { - name->setM(0); - *index = UINT_MAX; + if (!object || !iterator) + return PropertyKey::invalid(); - if (!object->as<Object>()) { - *attrs = PropertyAttributes(); - return; - } Scope scope(engine); - ScopedObject o(scope); - ScopedString n(scope); + ScopedPropertyKey key(scope); while (1) { - Object *co = current->objectValue(); - if (!co) - break; - - while (1) { - co->advanceIterator(this, name, index, pd, attrs); - if (attrs->isEmpty()) - break; - // check the property is not already defined earlier in the proto chain - if (co->heapObject() != object->heapObject()) { - o = object->as<Object>(); - n = *name; - bool shadowed = false; - while (o->d() != current->heapObject()) { - if ((!!n && o->hasOwnProperty(n)) || - (*index != UINT_MAX && o->hasOwnProperty(*index))) { - shadowed = true; - break; - } - o = o->prototype(); - } - if (shadowed) - continue; - } - return; + key = iterator->next(object, pd, attrs); + if (!key->isValid()) { + object = nullptr; + return key; } - - if (flags & WithProtoChain) - current->setM(co->prototype()); - else - current->setM(0); - - arrayIndex = 0; - memberIndex = 0; + if ((!(flags & WithSymbols) && key->isSymbol()) || + ((flags & EnumerableOnly) && !attrs->isEnumerable())) + continue; + return key; } - *attrs = PropertyAttributes(); } ReturnedValue ObjectIterator::nextPropertyName(Value *value) { - Object *o = object->objectValue(); - if (!o) + if (!object) return Encode::null(); PropertyAttributes attrs; - uint index; Scope scope(engine); ScopedProperty p(scope); - ScopedString name(scope); - next(name.getRef(), &index, p, &attrs); - if (attrs.isEmpty()) + ScopedPropertyKey key(scope, next(p, &attrs)); + if (!key->isValid()) return Encode::null(); - *value = o->getValue(p->value, attrs); - - if (!!name) - return name->asReturnedValue(); - Q_ASSERT(index < UINT_MAX); - return Encode(index); + *value = object->getValue(p->value, attrs); + if (key->isArrayIndex()) + return Encode(key->asArrayIndex()); + Q_ASSERT(key->isStringOrSymbol()); + return key->asStringOrSymbol()->asReturnedValue(); } ReturnedValue ObjectIterator::nextPropertyNameAsString(Value *value) { - Object *o = object->objectValue(); - if (!o) + if (!object) return Encode::null(); PropertyAttributes attrs; - uint index; Scope scope(engine); ScopedProperty p(scope); - ScopedString name(scope); - next(name.getRef(), &index, p, &attrs); - if (attrs.isEmpty()) + ScopedPropertyKey key(scope, next(p, &attrs)); + if (!key->isValid()) return Encode::null(); - *value = o->getValue(p->value, attrs); + *value = object->getValue(p->value, attrs); - if (!!name) - return name->asReturnedValue(); - Q_ASSERT(index < UINT_MAX); - return Encode(engine->newString(QString::number(index))); + return key->toStringOrSymbol(engine)->asReturnedValue(); } ReturnedValue ObjectIterator::nextPropertyNameAsString() { - if (!object->as<Object>()) + if (!object) return Encode::null(); PropertyAttributes attrs; - uint index; Scope scope(engine); - ScopedProperty p(scope); - ScopedString name(scope); - next(name.getRef(), &index, p, &attrs); - if (attrs.isEmpty()) + ScopedPropertyKey key(scope, next(nullptr, &attrs)); + if (!key->isValid()) return Encode::null(); - if (!!name) - return name->asReturnedValue(); - Q_ASSERT(index < UINT_MAX); - return Encode(engine->newString(QString::number(index))); + return key->toStringOrSymbol(engine)->asReturnedValue(); } -DEFINE_OBJECT_VTABLE(ForEachIteratorObject); +DEFINE_OBJECT_VTABLE(ForInIteratorObject); -void ForEachIteratorObject::markObjects(Heap::Base *that, MarkStack *markStack) +void Heap::ForInIteratorObject::markObjects(Heap::Base *that, MarkStack *markStack) { - ForEachIteratorObject::Data *o = static_cast<ForEachIteratorObject::Data *>(that); + ForInIteratorObject *o = static_cast<ForInIteratorObject *>(that); + if (o->object) + o->object->mark(markStack); + if (o->current) + o->current->mark(markStack); o->workArea[0].mark(markStack); o->workArea[1].mark(markStack); Object::markObjects(that, markStack); } + +void Heap::ForInIteratorObject::destroy() +{ + delete iterator; +} + +ReturnedValue ForInIteratorPrototype::method_next(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + const ForInIteratorObject *forIn = static_cast<const ForInIteratorObject *>(thisObject); + Q_ASSERT(forIn); + Scope scope(b); + + ScopedPropertyKey key(scope, forIn->nextProperty()); + bool done = false; + if (!key->isValid()) + done = true; + ScopedStringOrSymbol s(scope, key->toStringOrSymbol(scope.engine)); + return IteratorPrototype::createIterResultObject(scope.engine, s, done); +} + + +PropertyKey ForInIteratorObject::nextProperty() const +{ + if (!d()->current) + return PropertyKey::invalid(); + + Scope scope(this); + ScopedObject c(scope, d()->current); + ScopedObject t(scope, d()->target); + ScopedObject o(scope); + ScopedProperty p(scope); + ScopedPropertyKey key(scope); + PropertyAttributes attrs; + + while (1) { + while (1) { + key = d()->iterator->next(t, p, &attrs); + if (!key->isValid()) + break; + if (!attrs.isEnumerable() || key->isSymbol()) + continue; + // check the property is not already defined earlier in the proto chain + if (d()->current != d()->object) { + o = d()->object; + bool shadowed = false; + while (o->d() != c->heapObject()) { + if (o->getOwnProperty(key) != Attr_Invalid) { + shadowed = true; + break; + } + o = o->getPrototypeOf(); + } + if (shadowed) + continue; + } + return key; + } + + c = c->getPrototypeOf(); + d()->current.set(scope.engine, c->d()); + if (!c) + break; + delete d()->iterator; + d()->iterator = c->ownPropertyKeys(t.getRef()); + d()->target.set(scope.engine, t->d()); + if (!d()->iterator) { + scope.engine->throwTypeError(); + return PropertyKey::invalid(); + } + } + return PropertyKey::invalid(); +} diff --git a/src/qml/jsruntime/qv4objectiterator_p.h b/src/qml/jsruntime/qv4objectiterator_p.h index 6168d59914..a20ce9cb88 100644 --- a/src/qml/jsruntime/qv4objectiterator_p.h +++ b/src/qml/jsruntime/qv4objectiterator_p.h @@ -57,87 +57,86 @@ QT_BEGIN_NAMESPACE namespace QV4 { -struct Q_QML_EXPORT ObjectIteratorData +struct Q_QML_EXPORT ObjectIterator { enum Flags { NoFlags = 0, EnumerableOnly = 0x1, - WithProtoChain = 0x2, + WithSymbols = 0x2 }; ExecutionEngine *engine; - Value *object; - Value *current; - SparseArrayNode *arrayNode; - uint arrayIndex; - uint memberIndex; + Object *object; + OwnPropertyKeyIterator *iterator = nullptr; uint flags; -}; -V4_ASSERT_IS_TRIVIAL(ObjectIteratorData) - -struct Q_QML_EXPORT ObjectIterator: ObjectIteratorData -{ - ObjectIterator(ExecutionEngine *e, Value *scratch1, Value *scratch2, Object *o, uint flags) - { - engine = e; - object = scratch1; - current = scratch2; - arrayNode = nullptr; - arrayIndex = 0; - memberIndex = 0; - this->flags = flags; - init(o); - } ObjectIterator(Scope &scope, const Object *o, uint flags) { engine = scope.engine; - object = scope.alloc(1); - current = scope.alloc(1); - arrayNode = nullptr; - arrayIndex = 0; - memberIndex = 0; + object = static_cast<Object *>(scope.alloc()); this->flags = flags; - init(o); + object->setM(o ? o->m() : nullptr); + if (o) + iterator = object->ownPropertyKeys(object); + } + ~ObjectIterator() + { + delete iterator; } - void next(Value *name, uint *index, Property *pd, PropertyAttributes *attributes = 0); + PropertyKey next(Property *pd = nullptr, PropertyAttributes *attributes = nullptr); ReturnedValue nextPropertyName(Value *value); ReturnedValue nextPropertyNameAsString(Value *value); ReturnedValue nextPropertyNameAsString(); - -private: - void init(const Object *o); }; namespace Heap { -struct ForEachIteratorObject : Object { + +#define ForInIteratorObjectMembers(class, Member) \ + Member(class, Pointer, Object *, object) \ + Member(class, Pointer, Object *, current) \ + Member(class, Pointer, Object *, target) \ + Member(class, NoMark, OwnPropertyKeyIterator *, iterator) + +DECLARE_HEAP_OBJECT(ForInIteratorObject, Object) { void init(QV4::Object *o); - ObjectIterator &it() { return *reinterpret_cast<ObjectIterator*>(&itData); } Value workArea[2]; -private: - ObjectIteratorData itData; + static void markObjects(Heap::Base *that, MarkStack *markStack); + void destroy(); }; } -struct ForEachIteratorObject: Object { - V4_OBJECT2(ForEachIteratorObject, Object) - Q_MANAGED_TYPE(ForeachIteratorObject) +struct ForInIteratorPrototype : Object +{ + V4_PROTOTYPE(iteratorPrototype) + void init(ExecutionEngine *engine); + + static ReturnedValue method_next(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); +}; - ReturnedValue nextPropertyName() { return d()->it().nextPropertyNameAsString(); } +struct ForInIteratorObject: Object { + V4_OBJECT2(ForInIteratorObject, Object) + Q_MANAGED_TYPE(ForInIterator) + V4_PROTOTYPE(forInIteratorPrototype) + V4_NEEDS_DESTROY -protected: - static void markObjects(Heap::Base *that, MarkStack *markStack); + PropertyKey nextProperty() const; }; inline -void Heap::ForEachIteratorObject::init(QV4::Object *o) +void Heap::ForInIteratorObject::init(QV4::Object *o) { Object::init(); - it() = ObjectIterator(internalClass->engine, workArea, workArea + 1, o, - ObjectIterator::EnumerableOnly | ObjectIterator::WithProtoChain); + if (!o) + return; + object.set(o->engine(), o->d()); + current.set(o->engine(), o->d()); + Scope scope(o); + ScopedObject obj(scope); + iterator = o->ownPropertyKeys(obj.getRef()); + target.set(o->engine(), obj->d()); } diff --git a/src/qml/jsruntime/qv4objectproto.cpp b/src/qml/jsruntime/qv4objectproto.cpp index 2e72c0f13f..6b4c3ba71a 100644 --- a/src/qml/jsruntime/qv4objectproto.cpp +++ b/src/qml/jsruntime/qv4objectproto.cpp @@ -46,6 +46,9 @@ #include "qv4runtime_p.h" #include "qv4objectiterator_p.h" #include "qv4string_p.h" +#include "qv4jscall_p.h" +#include "qv4symbol_p.h" +#include "qv4propertykey_p.h" #include <QtCore/QDateTime> #include <QtCore/QStringList> @@ -60,27 +63,29 @@ void Heap::ObjectCtor::init(QV4::ExecutionContext *scope) Heap::FunctionObject::init(scope, QStringLiteral("Object")); } -void ObjectCtor::construct(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue ObjectCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - const ObjectCtor *ctor = static_cast<const ObjectCtor *>(that); - if (!callData->argc || callData->args[0].isUndefined() || callData->args[0].isNull()) { + ExecutionEngine *v4 = f->engine(); + const ObjectCtor *nt = static_cast<const ObjectCtor *>(newTarget); + if (!argc || argv[0].isUndefined() || argv[0].isNull()) { + Scope scope(v4); ScopedObject obj(scope, scope.engine->newObject()); - ScopedObject proto(scope, ctor->get(scope.engine->id_prototype())); + ScopedObject proto(scope, nt->get(scope.engine->id_prototype())); if (!!proto) - obj->setPrototype(proto); - scope.result = obj.asReturnedValue(); + obj->setPrototypeOf(proto); + return obj.asReturnedValue(); } else { - scope.result = callData->args[0].toObject(scope.engine); + return argv[0].toObject(v4)->asReturnedValue(); } } -void ObjectCtor::call(const Managed *, Scope &scope, CallData *callData) +ReturnedValue ObjectCtor::virtualCall(const FunctionObject *m, const Value *, const Value *argv, int argc) { - ExecutionEngine *v4 = scope.engine; - if (!callData->argc || callData->args[0].isUndefined() || callData->args[0].isNull()) { - scope.result = v4->newObject()->asReturnedValue(); + ExecutionEngine *v4 = m->engine(); + if (!argc || argv[0].isUndefined() || argv[0].isNull()) { + return v4->newObject()->asReturnedValue(); } else { - scope.result = callData->args[0].toObject(v4); + return argv[0].toObject(v4)->asReturnedValue(); } } @@ -90,25 +95,31 @@ void ObjectPrototype::init(ExecutionEngine *v4, Object *ctor) ScopedObject o(scope, this); ctor->defineReadonlyProperty(v4->id_prototype(), o); - ctor->defineReadonlyConfigurableProperty(v4->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyConfigurableProperty(v4->id_length(), Value::fromInt32(1)); ctor->defineDefaultProperty(QStringLiteral("getPrototypeOf"), method_getPrototypeOf, 1); ctor->defineDefaultProperty(QStringLiteral("getOwnPropertyDescriptor"), method_getOwnPropertyDescriptor, 2); + ctor->defineDefaultProperty(QStringLiteral("getOwnPropertyDescriptors"), method_getOwnPropertyDescriptors, 1); ctor->defineDefaultProperty(QStringLiteral("getOwnPropertyNames"), method_getOwnPropertyNames, 1); + ctor->defineDefaultProperty(QStringLiteral("getOwnPropertySymbols"), method_getOwnPropertySymbols, 1); ctor->defineDefaultProperty(QStringLiteral("assign"), method_assign, 2); ctor->defineDefaultProperty(QStringLiteral("create"), method_create, 2); ctor->defineDefaultProperty(QStringLiteral("defineProperty"), method_defineProperty, 3); ctor->defineDefaultProperty(QStringLiteral("defineProperties"), method_defineProperties, 2); + ctor->defineDefaultProperty(QStringLiteral("entries"), method_entries, 1); ctor->defineDefaultProperty(QStringLiteral("seal"), method_seal, 1); ctor->defineDefaultProperty(QStringLiteral("freeze"), method_freeze, 1); ctor->defineDefaultProperty(QStringLiteral("preventExtensions"), method_preventExtensions, 1); + ctor->defineDefaultProperty(QStringLiteral("is"), method_is, 2); ctor->defineDefaultProperty(QStringLiteral("isSealed"), method_isSealed, 1); ctor->defineDefaultProperty(QStringLiteral("isFrozen"), method_isFrozen, 1); ctor->defineDefaultProperty(QStringLiteral("isExtensible"), method_isExtensible, 1); ctor->defineDefaultProperty(QStringLiteral("keys"), method_keys, 1); + ctor->defineDefaultProperty(QStringLiteral("setPrototypeOf"), method_setPrototypeOf, 2); + ctor->defineDefaultProperty(QStringLiteral("values"), method_values, 1); defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); defineDefaultProperty(v4->id_toString(), method_toString, 0); - defineDefaultProperty(QStringLiteral("toLocaleString"), method_toLocaleString, 0); + defineDefaultProperty(v4->id_toLocaleString(), method_toLocaleString, 0); defineDefaultProperty(v4->id_valueOf(), method_valueOf, 0); defineDefaultProperty(QStringLiteral("hasOwnProperty"), method_hasOwnProperty, 1); defineDefaultProperty(QStringLiteral("isPrototypeOf"), method_isPrototypeOf, 1); @@ -116,76 +127,154 @@ void ObjectPrototype::init(ExecutionEngine *v4, Object *ctor) defineDefaultProperty(QStringLiteral("__defineGetter__"), method_defineGetter, 2); defineDefaultProperty(QStringLiteral("__defineSetter__"), method_defineSetter, 2); - ExecutionContext *global = v4->rootContext(); - ScopedProperty p(scope); - p->value = BuiltinFunction::create(global, v4->id___proto__(), method_get_proto); - p->set = BuiltinFunction::create(global, v4->id___proto__(), method_set_proto); - insertMember(v4->id___proto__(), p, Attr_Accessor|Attr_NotEnumerable); + defineAccessorProperty(v4->id___proto__(), method_get_proto, method_set_proto); } -void ObjectPrototype::method_getPrototypeOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_getPrototypeOf(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject o(scope, callData->args[0].toObject(scope.engine)); - CHECK_EXCEPTION(); + Scope scope(b); + if (argc < 1) + return scope.engine->throwTypeError(); - ScopedObject p(scope, o->prototype()); - scope.result = !!p ? p->asReturnedValue() : Encode::null(); + ScopedObject o(scope, argv[0].toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + + ScopedObject p(scope, o->getPrototypeOf()); + return (!!p ? p->asReturnedValue() : Encode::null()); } -void ObjectPrototype::method_getOwnPropertyDescriptor(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_is(const FunctionObject *, const Value *, const Value *argv, int argc) { - ScopedObject O(scope, callData->args[0].toObject(scope.engine)); - CHECK_EXCEPTION(); + if (!argc) + return Encode(true); + if (argc == 1) + return Encode((argv[0].isUndefined() ? true : false)); + return Encode(argv[0].sameValue(argv[1])); +} + +ReturnedValue ObjectPrototype::method_getOwnPropertyDescriptor(const FunctionObject *b, const Value *, const Value *argv, int argc) +{ + Scope scope(b); + if (argc < 1) + return scope.engine->throwTypeError(); + + ScopedObject O(scope, argv[0].toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); if (ArgumentsObject::isNonStrictArgumentsObject(O)) static_cast<ArgumentsObject *>(O.getPointer())->fullyCreate(); - ScopedValue v(scope, callData->argument(1)); - ScopedString name(scope, v->toString(scope.engine)); - CHECK_EXCEPTION(); + ScopedValue v(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + ScopedPropertyKey name(scope, v->toPropertyKey(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); - PropertyAttributes attrs; ScopedProperty desc(scope); - O->getOwnProperty(name, &attrs, desc); - scope.result = fromPropertyDescriptor(scope.engine, desc, attrs); + PropertyAttributes attrs = O->getOwnProperty(name, desc); + return fromPropertyDescriptor(scope.engine, desc, attrs); } -void ObjectPrototype::method_getOwnPropertyNames(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_getOwnPropertyDescriptors(const FunctionObject *f, const Value *, const Value *argv, int argc) { - ScopedObject O(scope, callData->args[0].toObject(scope.engine)); - CHECK_EXCEPTION(); + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + ScopedObject o(scope, argv[0].toObject(scope.engine)); + if (scope.engine->hasException) + return Encode::undefined(); + + ScopedObject descriptors(scope, scope.engine->newObject()); + + ObjectIterator it(scope, o, ObjectIterator::WithSymbols); + ScopedProperty pd(scope); + PropertyAttributes attrs; + ScopedPropertyKey key(scope); + ScopedObject entry(scope); + while (1) { + key = it.next(pd, &attrs); + if (!key->isValid()) + break; + entry = fromPropertyDescriptor(scope.engine, pd, attrs); + descriptors->put(key, entry); + } + + return descriptors.asReturnedValue(); - scope.result = getOwnPropertyNames(scope.engine, callData->args[0]); } -// 19.1.2.1 -void ObjectPrototype::method_assign(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_getOwnPropertyNames(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject to(scope, callData->args[0].toObject(scope.engine)); - CHECK_EXCEPTION(); + Scope scope(b); + if (argc < 1) + return scope.engine->throwTypeError(); - if (callData->argc == 1) { - scope.result = to; - return; + ScopedObject O(scope, argv[0].toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + + return Encode(getOwnPropertyNames(scope.engine, argv[0])); +} + +ReturnedValue ObjectPrototype::method_getOwnPropertySymbols(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + ScopedObject O(scope, argv[0].toObject(scope.engine)); + if (!O) + return Encode::undefined(); + + ScopedArrayObject array(scope, scope.engine->newArrayObject()); + if (O) { + ObjectIterator it(scope, O, ObjectIterator::WithSymbols); + ScopedValue name(scope); + while (1) { + name = it.nextPropertyNameAsString(); + if (name->isNull()) + break; + if (!name->isSymbol()) + continue; + array->push_back(name); + } } + return array->asReturnedValue(); +} + +// 19.1.2.1 +ReturnedValue ObjectPrototype::method_assign(const FunctionObject *b, const Value *, const Value *argv, int argc) +{ + Scope scope(b); + if (argc < 1) + return scope.engine->throwTypeError(); - for (int i = 1; i < callData->argc; ++i) { - if (callData->args[i].isUndefined() || callData->args[i].isNull()) + ScopedObject to(scope, argv[0].toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + + if (argc == 1) + return to.asReturnedValue(); + + for (int i = 1, ei = argc; i < ei; ++i) { + if (argv[i].isUndefined() || argv[i].isNull()) continue; - ScopedObject from(scope, callData->args[i].toObject(scope.engine)); - CHECK_EXCEPTION(); + ScopedObject from(scope, argv[i].toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); QV4::ScopedArrayObject keys(scope, QV4::ObjectPrototype::getOwnPropertyNames(scope.engine, from)); quint32 length = keys->getLength(); ScopedString nextKey(scope); ScopedValue propValue(scope); for (quint32 i = 0; i < length; ++i) { - nextKey = Value::fromReturnedValue(keys->getIndexed(i)).toString(scope.engine); + nextKey = Value::fromReturnedValue(keys->get(i)).toString(scope.engine); - PropertyAttributes attrs; ScopedProperty prop(scope); - from->getOwnProperty(nextKey, &attrs, prop); + PropertyAttributes attrs = from->getOwnProperty(nextKey->toPropertyKey(), prop); if (attrs == PropertyFlag::Attr_Invalid) continue; @@ -195,102 +284,144 @@ void ObjectPrototype::method_assign(const BuiltinFunction *, Scope &scope, CallD propValue = from->get(nextKey); to->set(nextKey, propValue, Object::DoThrowOnRejection); - CHECK_EXCEPTION(); + if (scope.engine->hasException) + return QV4::Encode::undefined(); } } - scope.result = to; + return to.asReturnedValue(); } -void ObjectPrototype::method_create(const BuiltinFunction *builtin, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_create(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) { - ScopedValue O(scope, callData->argument(0)); - if (!O->isObject() && !O->isNull()) { - scope.result = scope.engine->throwTypeError(); - return; - } + Scope scope(builtin); + if (!argc || (!argv[0].isObject() && !argv[0].isNull())) + return scope.engine->throwTypeError(); + + ScopedObject O(scope, argv[0]); ScopedObject newObject(scope, scope.engine->newObject()); - newObject->setPrototype(O->as<Object>()); + newObject->setPrototypeOf(O); - if (callData->argc > 1 && !callData->args[1].isUndefined()) { - callData->args[0] = newObject; - method_defineProperties(builtin, scope, callData); - return; + + if (argc > 1 && !argv[1].isUndefined()) { + Value *arguments = scope.alloc(argc); + arguments[0] = newObject; + memcpy(arguments + 1, argv + 1, (argc - 1)*sizeof(Value)); + return method_defineProperties(builtin, thisObject, arguments, argc); } - scope.result = newObject; + return newObject.asReturnedValue(); } -void ObjectPrototype::method_defineProperty(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_defineProperty(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject O(scope, callData->argument(0)); - if (!O) { - scope.result = scope.engine->throwTypeError(); - return; - } + Scope scope(b); + if (!argc || !argv[0].isObject()) + return scope.engine->throwTypeError(); - ScopedString name(scope, callData->argument(1), ScopedString::Convert); - CHECK_EXCEPTION(); + ScopedObject O(scope, argv[0]); + ScopedPropertyKey name(scope, (argc > 1 ? argv[1] : Value::undefinedValue()).toPropertyKey(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); - ScopedValue attributes(scope, callData->argument(2)); + ScopedValue attributes(scope, argc > 2 ? argv[2] : Value::undefinedValue()); ScopedProperty pd(scope); PropertyAttributes attrs; toPropertyDescriptor(scope.engine, attributes, pd, &attrs); - CHECK_EXCEPTION(); + if (scope.engine->hasException) + return QV4::Encode::undefined(); - if (!O->__defineOwnProperty__(scope.engine, name, pd, attrs)) + if (!O->defineOwnProperty(name, pd, attrs)) THROW_TYPE_ERROR(); - scope.result = O; + return O.asReturnedValue(); } -void ObjectPrototype::method_defineProperties(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_defineProperties(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject O(scope, callData->argument(0)); - if (!O) - THROW_TYPE_ERROR(); + Scope scope(b); + if (argc < 2 || !argv[0].isObject()) + return scope.engine->throwTypeError(); + + ScopedObject O(scope, argv[0]); - ScopedObject o(scope, callData->argument(1), ScopedObject::Convert); - CHECK_EXCEPTION(); + ScopedObject o(scope, argv[1].toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); ScopedValue val(scope); ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); - ScopedString name(scope); ScopedProperty pd(scope); ScopedProperty n(scope); + ScopedPropertyKey key(scope); while (1) { - uint index; PropertyAttributes attrs; - it.next(name.getRef(), &index, pd, &attrs); - if (attrs.isEmpty()) + key = it.next(pd, &attrs); + if (!key->isValid()) break; PropertyAttributes nattrs; val = o->getValue(pd->value, attrs); toPropertyDescriptor(scope.engine, val, n, &nattrs); - CHECK_EXCEPTION(); - bool ok; - if (name) - ok = O->__defineOwnProperty__(scope.engine, name, n, nattrs); - else - ok = O->__defineOwnProperty__(scope.engine, index, n, nattrs); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + bool ok = O->defineOwnProperty(key, n, nattrs); if (!ok) THROW_TYPE_ERROR(); } - scope.result = O; + return O.asReturnedValue(); } -void ObjectPrototype::method_seal(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_entries(const FunctionObject *f, const Value *, const Value *argv, int argc) { - ScopedObject o(scope, callData->argument(0)); - if (!o) { - // 19.1.2.17, 1 - scope.result = callData->argument(0); - return; + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + ScopedObject o(scope, argv[0].toObject(scope.engine)); + if (scope.engine->hasException) + return Encode::undefined(); + + ScopedArrayObject a(scope, scope.engine->newArrayObject()); + + ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); + ScopedString name(scope); + ScopedArrayObject entry(scope); + while (1) { + name = it.nextPropertyNameAsString(); + if (!name) + break; + entry = scope.engine->newArrayObject(); + entry->push_back(name); + a->push_back(entry); + } + + // now add values, do this after the loop above as reading out the values can have side effects + uint len = a->getLength(); + ScopedValue value(scope); + for (uint i = 0; i < len; ++i) { + entry = a->get(PropertyKey::fromArrayIndex(i)); + name = entry->get(PropertyKey::fromArrayIndex(0)); + value = o->get(name->toPropertyKey()); + if (scope.engine->hasException) + return Encode::undefined(); + entry->push_back(value); } + return a.asReturnedValue(); +} + +ReturnedValue ObjectPrototype::method_seal(const FunctionObject *b, const Value *, const Value *argv, int argc) +{ + const Value a = argc ? argv[0] : Value::undefinedValue(); + if (!a.isObject()) + // 19.1.2.17, 1 + return a.asReturnedValue(); + + Scope scope(b); + ScopedObject o(scope, a); o->setInternalClass(o->internalClass()->sealed()); if (o->arrayData()) { @@ -301,17 +432,18 @@ void ObjectPrototype::method_seal(const BuiltinFunction *, Scope &scope, CallDat } } - scope.result = o; + return o.asReturnedValue(); } -void ObjectPrototype::method_freeze(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_freeze(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject o(scope, callData->argument(0)); - if (!o) { + const Value a = argc ? argv[0] : Value::undefinedValue(); + if (!a.isObject()) // 19.1.2.5, 1 - scope.result = callData->argument(0); - return; - } + return a.asReturnedValue(); + + Scope scope(b); + ScopedObject o(scope, a); if (ArgumentsObject::isNonStrictArgumentsObject(o)) static_cast<ArgumentsObject *>(o.getPointer())->fullyCreate(); @@ -327,293 +459,349 @@ void ObjectPrototype::method_freeze(const BuiltinFunction *, Scope &scope, CallD o->arrayData()->attrs[i].setWritable(false); } } - scope.result = o; + return o.asReturnedValue(); } -void ObjectPrototype::method_preventExtensions(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_preventExtensions(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject o(scope, callData->args[0].toObject(scope.engine)); - if (!o) { - scope.result = callData->argument(0); - return; - } + Scope scope(b); + if (!argc) + return Encode::undefined(); + + ScopedObject o(scope, argv[0]); + if (!o) + return argv[0].asReturnedValue(); - o->setInternalClass(o->internalClass()->nonExtensible()); - scope.result = o; + o->preventExtensions(); + return o.asReturnedValue(); } -void ObjectPrototype::method_isSealed(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_isSealed(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject o(scope, callData->args[0].toObject(scope.engine)); - if (!o) { - scope.result = Encode(true); - return; - } + Scope scope(b); + if (!argc) + return Encode(true); - if (o->isExtensible()) { - scope.result = Encode(false); - return; - } + ScopedObject o(scope, argv[0]); + if (!o) + return Encode(true); - if (o->internalClass() != o->internalClass()->sealed()) { - scope.result = Encode(false); - return; - } + if (o->isExtensible()) + return Encode(false); - if (!o->arrayData() || !o->arrayData()->length()) { - scope.result = Encode(true); - return; - } + if (o->internalClass() != o->internalClass()->sealed()) + return Encode(false); + + if (!o->arrayData() || !o->arrayData()->length()) + return Encode(true); Q_ASSERT(o->arrayData() && o->arrayData()->length()); - if (!o->arrayData()->attrs) { - scope.result = Encode(false); - return; - } + if (!o->arrayData()->attrs) + return Encode(false); for (uint i = 0; i < o->arrayData()->values.alloc; ++i) { if (!o->arrayData()->isEmpty(i)) - if (o->arrayData()->attributes(i).isConfigurable()) { - scope.result = Encode(false); - return; - } + if (o->arrayData()->attributes(i).isConfigurable()) + return Encode(false); } - scope.result = Encode(true); + return Encode(true); } -void ObjectPrototype::method_isFrozen(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_isFrozen(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject o(scope, callData->args[0].toObject(scope.engine)); - if (!o) { - scope.result = Encode(true); - return; - } + Scope scope(b); + if (!argc) + return Encode(true); - if (o->isExtensible()) { - scope.result = Encode(false); - return; - } + ScopedObject o(scope, argv[0]); + if (!o) + return Encode(true); - if (o->internalClass() != o->internalClass()->frozen()) { - scope.result = Encode(false); - return; - } + if (o->isExtensible()) + return Encode(false); - if (!o->arrayData() || !o->arrayData()->length()) { - scope.result = Encode(true); - return; - } + if (o->internalClass() != o->internalClass()->frozen()) + return Encode(false); + + if (!o->arrayData() || !o->arrayData()->length()) + return Encode(true); Q_ASSERT(o->arrayData() && o->arrayData()->length()); - if (!o->arrayData()->attrs) { - scope.result = Encode(false); - return; - } + if (!o->arrayData()->attrs) + return Encode(false); for (uint i = 0; i < o->arrayData()->values.alloc; ++i) { if (!o->arrayData()->isEmpty(i)) - if (o->arrayData()->attributes(i).isConfigurable() || o->arrayData()->attributes(i).isWritable()) { - scope.result = Encode(false); - return; - } + if (o->arrayData()->attributes(i).isConfigurable() || o->arrayData()->attributes(i).isWritable()) + return Encode(false); } - scope.result = Encode(true); + return Encode(true); } -void ObjectPrototype::method_isExtensible(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_isExtensible(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject o(scope, callData->args[0].toObject(scope.engine)); - if (!o) { - scope.result = Encode(false); - return; - } + Scope scope(b); + if (!argc) + return Encode(false); + + ScopedObject o(scope, argv[0]); + if (!o) + return Encode(false); - scope.result = Encode((bool)o->isExtensible()); + return Encode((bool)o->isExtensible()); } -void ObjectPrototype::method_keys(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_keys(const FunctionObject *b, const Value *, const Value *argv, int argc) { - ScopedObject o(scope, callData->args[0].toObject(scope.engine)); - CHECK_EXCEPTION(); + Scope scope(b); + if (!argc) + return scope.engine->throwTypeError(); + + ScopedObject o(scope, argv[0].toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); ScopedArrayObject a(scope, scope.engine->newArrayObject()); ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); ScopedValue name(scope); + ScopedValue value(scope); while (1) { - name = it.nextPropertyNameAsString(); + name = it.nextPropertyNameAsString(value); if (name->isNull()) break; a->push_back(name); } - scope.result = a; + return a.asReturnedValue(); } -void ObjectPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_setPrototypeOf(const FunctionObject *f, const Value *, const Value *argv, int argc) { - if (callData->thisObject.isUndefined()) { - scope.result = scope.engine->newString(QStringLiteral("[object Undefined]")); - } else if (callData->thisObject.isNull()) { - scope.result = scope.engine->newString(QStringLiteral("[object Null]")); - } else { - ScopedObject obj(scope, callData->thisObject.toObject(scope.engine)); - QString className = obj->className(); - scope.result = scope.engine->newString(QStringLiteral("[object %1]").arg(className)); + Scope scope(f->engine()); + if (argc < 2 || argv[0].isNullOrUndefined() || !(argv[1].isObject() || argv[1].isNull())) + return scope.engine->throwTypeError(); + + if (!argv[0].isObject()) + return argv[0].asReturnedValue(); + + ScopedObject o(scope, argv[0]); + const Object *p = argv[1].isNull() ? nullptr : static_cast<const Object *>(argv + 1); + bool ok = o->setPrototypeOf(p); + if (!ok) + return scope.engine->throwTypeError(QStringLiteral("Could not change prototype.")); + return o->asReturnedValue(); +} + +ReturnedValue ObjectPrototype::method_values(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + ScopedObject o(scope, argv[0].toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + + ScopedArrayObject a(scope, scope.engine->newArrayObject()); + + ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); + ScopedPropertyKey key(scope); + ScopedProperty pd(scope); + ScopedValue value(scope); + PropertyAttributes attrs; + while (1) { + key = it.next(pd, &attrs); + if (!key->isValid()) + break; + value = o->getValue(pd->value, attrs); + a->push_back(value); } + + return a.asReturnedValue(); +} + +ReturnedValue ObjectPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + ExecutionEngine *v4 = b->engine(); + QString string; + if (thisObject->isUndefined()) { + string = QStringLiteral("[object Undefined]"); + } else if (thisObject->isNull()) { + string = QStringLiteral("[object Null]"); + } else { + const Object *o = thisObject->as<Object>(); + if (!o) { + // primitive, get the proper prototype + if (thisObject->isBoolean()) + o = v4->booleanPrototype(); + else if (thisObject->isNumber()) + o = v4->numberPrototype(); + else if (thisObject->isString()) + o = v4->stringPrototype(); + else if (thisObject->isSymbol()) + o = v4->symbolPrototype(); + Q_ASSERT(o); + } + QString name = o->className(); + Scope scope(v4); + ScopedString toStringTag(scope, o->get(v4->symbol_toStringTag())); + if (toStringTag) + name = toStringTag->toQString(); + string = QStringLiteral("[object %1]").arg(name); + } + return Encode(v4->newString(string)); } -void ObjectPrototype::method_toLocaleString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject o(scope, callData->thisObject.toObject(scope.engine)); + Scope scope(b); + ScopedObject o(scope, thisObject->toObject(scope.engine)); if (!o) RETURN_UNDEFINED(); ScopedFunctionObject f(scope, o->get(scope.engine->id_toString())); if (!f) THROW_TYPE_ERROR(); - ScopedCallData cData(scope); - cData->thisObject = o; - f->call(scope, callData); + + return f->call(thisObject, argv, argc); } -void ObjectPrototype::method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_valueOf(const FunctionObject *b, const Value *thisObject, const Value *, int) { - scope.result = callData->thisObject.toObject(scope.engine); + return Encode(thisObject->toObject(b->engine())); } -void ObjectPrototype::method_hasOwnProperty(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_hasOwnProperty(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedString P(scope, callData->argument(0), ScopedString::Convert); - CHECK_EXCEPTION(); - ScopedObject O(scope, callData->thisObject, ScopedObject::Convert); - CHECK_EXCEPTION(); - bool r = O->hasOwnProperty(P); - if (!r) - r = !O->query(P).isEmpty(); - scope.result = Encode(r); + Scope scope(b); + ScopedPropertyKey P(scope, (argc ? argv[0] : Value::undefinedValue()).toPropertyKey(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + ScopedObject O(scope, thisObject->toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + bool r = O->getOwnProperty(P) != Attr_Invalid; + return Encode(r); } -void ObjectPrototype::method_isPrototypeOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_isPrototypeOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject V(scope, callData->argument(0)); - if (!V) { - scope.result = Encode(false); - return; - } - - ScopedObject O(scope, callData->thisObject, ScopedObject::Convert); - CHECK_EXCEPTION(); - ScopedObject proto(scope, V->prototype()); + Scope scope(b); + if (!argc || !argv[0].isObject()) + return Encode(false); + + ScopedObject V(scope, argv[0]); + ScopedObject O(scope, thisObject->toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + ScopedObject proto(scope, V->getPrototypeOf()); while (proto) { - if (O->d() == proto->d()) { - scope.result = Encode(true); - return; - } - proto = proto->prototype(); + if (O->d() == proto->d()) + return Encode(true); + proto = proto->getPrototypeOf(); } - scope.result = Encode(false); + return Encode(false); } -void ObjectPrototype::method_propertyIsEnumerable(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_propertyIsEnumerable(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedString p(scope, callData->argument(0), ScopedString::Convert); - CHECK_EXCEPTION(); - - ScopedObject o(scope, callData->thisObject, ScopedObject::Convert); - CHECK_EXCEPTION(); - PropertyAttributes attrs; - o->getOwnProperty(p, &attrs); - scope.result = Encode(attrs.isEnumerable()); + Scope scope(b); + ScopedPropertyKey p(scope, (argc ? argv[0] : Value::undefinedValue()).toPropertyKey(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + + ScopedObject o(scope, thisObject->toObject(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + PropertyAttributes attrs = o->getOwnProperty(p); + return Encode(attrs.isEnumerable()); } -void ObjectPrototype::method_defineGetter(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_defineGetter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - if (callData->argc < 2) + Scope scope(b); + if (argc < 2) THROW_TYPE_ERROR(); - ScopedFunctionObject f(scope, callData->argument(1)); + ScopedFunctionObject f(scope, argv[1]); if (!f) THROW_TYPE_ERROR(); - ScopedString prop(scope, callData->argument(0), ScopedString::Convert); - CHECK_EXCEPTION(); + ScopedString prop(scope, argv[0], ScopedString::Convert); + if (scope.engine->hasException) + return QV4::Encode::undefined(); - ScopedObject o(scope, callData->thisObject); + ScopedObject o(scope, thisObject); if (!o) { - if (!callData->thisObject.isUndefined()) + if (!thisObject->isUndefined()) RETURN_UNDEFINED(); o = scope.engine->globalObject; } ScopedProperty pd(scope); pd->value = f; - pd->set = Primitive::emptyValue(); - o->__defineOwnProperty__(scope.engine, prop, pd, Attr_Accessor); + pd->set = Value::emptyValue(); + bool ok = o->defineOwnProperty(prop->toPropertyKey(), pd, Attr_Accessor); + if (!ok) + THROW_TYPE_ERROR(); RETURN_UNDEFINED(); } -void ObjectPrototype::method_defineSetter(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_defineSetter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - if (callData->argc < 2) + Scope scope(b); + if (argc < 2) THROW_TYPE_ERROR(); - ScopedFunctionObject f(scope, callData->argument(1)); + ScopedFunctionObject f(scope, argv[1]); if (!f) THROW_TYPE_ERROR(); - ScopedString prop(scope, callData->argument(0), ScopedString::Convert); - CHECK_EXCEPTION(); + ScopedString prop(scope, argv[0], ScopedString::Convert); + if (scope.engine->hasException) + return QV4::Encode::undefined(); - ScopedObject o(scope, callData->thisObject); + ScopedObject o(scope, thisObject); if (!o) { - if (!callData->thisObject.isUndefined()) + if (!thisObject->isUndefined()) RETURN_UNDEFINED(); o = scope.engine->globalObject; } ScopedProperty pd(scope); - pd->value = Primitive::emptyValue(); + pd->value = Value::emptyValue(); pd->set = f; - o->__defineOwnProperty__(scope.engine, prop, pd, Attr_Accessor); + bool ok = o->defineOwnProperty(prop->toPropertyKey(), pd, Attr_Accessor); + if (!ok) + THROW_TYPE_ERROR(); RETURN_UNDEFINED(); } -void ObjectPrototype::method_get_proto(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_get_proto(const FunctionObject *b, const Value *thisObject, const Value *, int) { - ScopedObject o(scope, callData->thisObject.as<Object>()); + Scope scope(b); + ScopedObject o(scope, thisObject->as<Object>()); if (!o) THROW_TYPE_ERROR(); - scope.result = o->prototype(); + return Encode(o->getPrototypeOf()); } -void ObjectPrototype::method_set_proto(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue ObjectPrototype::method_set_proto(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - ScopedObject o(scope, callData->thisObject); - if (!o || !callData->argc) + Scope scope(b); + ScopedObject o(scope, thisObject); + if (!o || !argc || (!argv[0].isObject() && !argv[0].isNull())) THROW_TYPE_ERROR(); - if (callData->args[0].isNull()) { - o->setPrototype(0); - RETURN_UNDEFINED(); - } - - ScopedObject p(scope, callData->args[0]); - bool ok = false; - if (!!p) { - if (o->prototype() == p->d()) { - ok = true; - } else if (o->isExtensible()) { - ok = o->setPrototype(p); - } - } - if (!ok) { - scope.result = scope.engine->throwTypeError(QStringLiteral("Cyclic __proto__ value")); - return; - } + const Object *p = argv[0].isNull() ? nullptr : static_cast<const Object *>(argv); + bool ok = o->setPrototypeOf(p); + if (!ok) + return scope.engine->throwTypeError(QStringLiteral("Could not change prototype.")); + return Encode::undefined(); RETURN_UNDEFINED(); } @@ -627,17 +815,17 @@ void ObjectPrototype::toPropertyDescriptor(ExecutionEngine *engine, const Value } attrs->clear(); - desc->value = Primitive::emptyValue(); - desc->set = Primitive::emptyValue(); + desc->value = Value::emptyValue(); + desc->set = Value::emptyValue(); ScopedValue tmp(scope); - if (o->hasProperty(engine->id_enumerable())) + if (o->hasProperty(engine->id_enumerable()->toPropertyKey())) attrs->setEnumerable((tmp = o->get(engine->id_enumerable()))->toBoolean()); - if (o->hasProperty(engine->id_configurable())) + if (o->hasProperty(engine->id_configurable()->toPropertyKey())) attrs->setConfigurable((tmp = o->get(engine->id_configurable()))->toBoolean()); - if (o->hasProperty(engine->id_get())) { + if (o->hasProperty(engine->id_get()->toPropertyKey())) { ScopedValue get(scope, o->get(engine->id_get())); FunctionObject *f = get->as<FunctionObject>(); if (f || get->isUndefined()) { @@ -649,7 +837,7 @@ void ObjectPrototype::toPropertyDescriptor(ExecutionEngine *engine, const Value attrs->setType(PropertyAttributes::Accessor); } - if (o->hasProperty(engine->id_set())) { + if (o->hasProperty(engine->id_set()->toPropertyKey())) { ScopedValue set(scope, o->get(engine->id_set())); FunctionObject *f = set->as<FunctionObject>(); if (f || set->isUndefined()) { @@ -661,17 +849,15 @@ void ObjectPrototype::toPropertyDescriptor(ExecutionEngine *engine, const Value attrs->setType(PropertyAttributes::Accessor); } - if (o->hasProperty(engine->id_writable())) { + if (o->hasProperty(engine->id_writable()->toPropertyKey())) { if (attrs->isAccessor()) { engine->throwTypeError(); return; } attrs->setWritable((tmp = o->get(engine->id_writable()))->toBoolean()); - // writable forces it to be a data descriptor - desc->value = Primitive::undefinedValue(); } - if (o->hasProperty(engine->id_value())) { + if (o->hasProperty(engine->id_value()->toPropertyKey())) { if (attrs->isAccessor()) { engine->throwTypeError(); return; @@ -681,7 +867,7 @@ void ObjectPrototype::toPropertyDescriptor(ExecutionEngine *engine, const Value } if (attrs->isGeneric()) - desc->value = Primitive::emptyValue(); + desc->value = Value::emptyValue(); } @@ -695,29 +881,28 @@ ReturnedValue ObjectPrototype::fromPropertyDescriptor(ExecutionEngine *engine, c // is the standard built-in constructor with that name. ScopedObject o(scope, engine->newObject()); ScopedString s(scope); + ScopedValue v(scope); - ScopedProperty pd(scope); if (attrs.isData()) { - pd->value = desc->value; s = engine->newString(QStringLiteral("value")); - o->__defineOwnProperty__(scope.engine, s, pd, Attr_Data); - pd->value = Primitive::fromBoolean(attrs.isWritable()); + o->put(s, desc->value); + v = Value::fromBoolean(attrs.isWritable()); s = engine->newString(QStringLiteral("writable")); - o->__defineOwnProperty__(scope.engine, s, pd, Attr_Data); + o->put(s, v); } else { - pd->value = desc->getter() ? desc->getter()->asReturnedValue() : Encode::undefined(); + v = desc->getter() ? desc->getter()->asReturnedValue() : Encode::undefined(); s = engine->newString(QStringLiteral("get")); - o->__defineOwnProperty__(scope.engine, s, pd, Attr_Data); - pd->value = desc->setter() ? desc->setter()->asReturnedValue() : Encode::undefined(); + o->put(s, v); + v = desc->setter() ? desc->setter()->asReturnedValue() : Encode::undefined(); s = engine->newString(QStringLiteral("set")); - o->__defineOwnProperty__(scope.engine, s, pd, Attr_Data); + o->put(s, v); } - pd->value = Primitive::fromBoolean(attrs.isEnumerable()); + v = Value::fromBoolean(attrs.isEnumerable()); s = engine->newString(QStringLiteral("enumerable")); - o->__defineOwnProperty__(scope.engine, s, pd, Attr_Data); - pd->value = Primitive::fromBoolean(attrs.isConfigurable()); + o->put(s, v); + v = Value::fromBoolean(attrs.isConfigurable()); s = engine->newString(QStringLiteral("configurable")); - o->__defineOwnProperty__(scope.engine, s, pd, Attr_Data); + o->put(s, v); return o.asReturnedValue(); } @@ -735,6 +920,8 @@ Heap::ArrayObject *ObjectPrototype::getOwnPropertyNames(ExecutionEngine *v4, con name = it.nextPropertyNameAsString(); if (name->isNull()) break; + if (name->isSymbol()) + continue; array->push_back(name); } } diff --git a/src/qml/jsruntime/qv4objectproto_p.h b/src/qml/jsruntime/qv4objectproto_p.h index 44b54267f3..e9515b5b68 100644 --- a/src/qml/jsruntime/qv4objectproto_p.h +++ b/src/qml/jsruntime/qv4objectproto_p.h @@ -70,41 +70,47 @@ struct ObjectCtor: FunctionObject { V4_OBJECT2(ObjectCtor, FunctionObject) - static void construct(const Managed *that, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *m, const Value *thisObject, const Value *argv, int argc); }; struct ObjectPrototype: Object { void init(ExecutionEngine *engine, Object *ctor); - static void method_getPrototypeOf(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getOwnPropertyDescriptor(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_getOwnPropertyNames(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_assign(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_create(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_defineProperty(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_defineProperties(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_seal(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_freeze(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_preventExtensions(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isSealed(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isFrozen(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isExtensible(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_keys(const BuiltinFunction *, Scope &scope, CallData *callData); - - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toLocaleString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_hasOwnProperty(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_isPrototypeOf(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_propertyIsEnumerable(const BuiltinFunction *, Scope &scope, CallData *callData); - - static void method_defineGetter(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_defineSetter(const BuiltinFunction *, Scope &scope, CallData *callData); - - static void method_get_proto(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_set_proto(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_assign(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_create(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_defineProperties(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_defineProperty(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_entries(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_freeze(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getOwnPropertyDescriptor(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getOwnPropertyDescriptors(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getOwnPropertyNames(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getOwnPropertySymbols(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getPrototypeOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_is(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isExtensible(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isFrozen(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isSealed(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_keys(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_preventExtensions(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_seal(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setPrototypeOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_values(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_valueOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_hasOwnProperty(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isPrototypeOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_propertyIsEnumerable(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_defineGetter(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_defineSetter(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_get_proto(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_set_proto(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); static void toPropertyDescriptor(ExecutionEngine *engine, const Value &v, Property *desc, PropertyAttributes *attrs); static ReturnedValue fromPropertyDescriptor(ExecutionEngine *engine, const Property *desc, PropertyAttributes attrs); diff --git a/src/qml/jsruntime/qv4persistent.cpp b/src/qml/jsruntime/qv4persistent.cpp index 0b31c971f9..79c372348f 100644 --- a/src/qml/jsruntime/qv4persistent.cpp +++ b/src/qml/jsruntime/qv4persistent.cpp @@ -68,6 +68,22 @@ Page *getPage(Value *val) { return reinterpret_cast<Page *>(reinterpret_cast<quintptr>(val) & ~((quintptr)(WTF::pageSize() - 1))); } +QML_NEARLY_ALWAYS_INLINE void insertInFront(PersistentValueStorage *storage, Page *p) +{ + p->header.next = reinterpret_cast<Page *>(storage->firstPage); + p->header.prev = reinterpret_cast<Page **>(&storage->firstPage); + if (p->header.next) + p->header.next->header.prev = &p->header.next; + storage->firstPage = p; +} + +QML_NEARLY_ALWAYS_INLINE void unlink(Page *p) +{ + if (p->header.prev) + *p->header.prev = p->header.next; + if (p->header.next) + p->header.next->header.prev = p->header.prev; +} Page *allocatePage(PersistentValueStorage *storage) { @@ -78,18 +94,13 @@ Page *allocatePage(PersistentValueStorage *storage) p->header.engine = storage->engine; p->header.alloc = page; - p->header.next = reinterpret_cast<Page *>(storage->firstPage); - p->header.prev = reinterpret_cast<Page **>(&storage->firstPage); p->header.refCount = 0; p->header.freeList = 0; - if (p->header.next) - p->header.next->header.prev = &p->header.next; + insertInFront(storage, p); for (int i = 0; i < kEntriesPerPage - 1; ++i) { - p->values[i].setEmpty(i + 1); + p->values[i] = Encode(i + 1); } - p->values[kEntriesPerPage - 1].setEmpty(-1); - - storage->firstPage = p; + p->values[kEntriesPerPage - 1] = Encode(-1); return p; } @@ -161,7 +172,7 @@ Value &PersistentValueStorage::Iterator::operator *() PersistentValueStorage::PersistentValueStorage(ExecutionEngine *engine) : engine(engine), - firstPage(0) + firstPage(nullptr) { } @@ -174,9 +185,9 @@ PersistentValueStorage::~PersistentValueStorage() p->values[i] = Encode::undefined(); } Page *n = p->header.next; - p->header.engine = 0; - p->header.prev = 0; - p->header.next = 0; + p->header.engine = nullptr; + p->header.prev = nullptr; + p->header.next = nullptr; Q_ASSERT(p->header.refCount); p = n; } @@ -195,6 +206,12 @@ Value *PersistentValueStorage::allocate() Value *v = p->values + p->header.freeList; p->header.freeList = v->int_32(); + + if (p->header.freeList != -1 && p != firstPage) { + unlink(p); + insertInFront(this, p); + } + ++p->header.refCount; v->setRawValue(Encode::undefined()); @@ -209,7 +226,7 @@ void PersistentValueStorage::free(Value *v) Page *p = getPage(v); - v->setEmpty(p->header.freeList); + *v = Encode(p->header.freeList); p->header.freeList = v - p->values; if (!--p->header.refCount) freePage(p); @@ -237,16 +254,13 @@ ExecutionEngine *PersistentValueStorage::getEngine(Value *v) void PersistentValueStorage::freePage(void *page) { Page *p = static_cast<Page *>(page); - if (p->header.prev) - *p->header.prev = p->header.next; - if (p->header.next) - p->header.next->header.prev = p->header.prev; + unlink(p); p->header.alloc.deallocate(); } PersistentValue::PersistentValue(const PersistentValue &other) - : val(0) + : val(nullptr) { if (other.val) { val = other.engine()->memoryManager->m_persistentValues->allocate(); @@ -267,7 +281,7 @@ PersistentValue::PersistentValue(ExecutionEngine *engine, ReturnedValue value) } PersistentValue::PersistentValue(ExecutionEngine *engine, Object *object) - : val(0) + : val(nullptr) { if (!object) return; @@ -288,6 +302,10 @@ PersistentValue &PersistentValue::operator=(const PersistentValue &other) return *this; val = other.engine()->memoryManager->m_persistentValues->allocate(); } + if (!other.val) { + *val = Encode::undefined(); + return *this; + } Q_ASSERT(engine() == other.engine()); @@ -302,6 +320,10 @@ PersistentValue &PersistentValue::operator=(const WeakValue &other) return *this; val = other.engine()->memoryManager->m_persistentValues->allocate(); } + if (!other.valueRef()) { + *val = Encode::undefined(); + return *this; + } Q_ASSERT(engine() == other.engine()); @@ -344,7 +366,7 @@ void PersistentValue::set(ExecutionEngine *engine, Heap::Base *obj) } WeakValue::WeakValue(const WeakValue &other) - : val(0) + : val(nullptr) { if (other.val) { allocVal(other.engine()); @@ -365,6 +387,10 @@ WeakValue &WeakValue::operator=(const WeakValue &other) return *this; allocVal(other.engine()); } + if (!other.val) { + *val = Encode::undefined(); + return *this; + } Q_ASSERT(engine() == other.engine()); @@ -404,6 +430,6 @@ void WeakValue::free() PersistentValueStorage::free(val); } - val = 0; + val = nullptr; } diff --git a/src/qml/jsruntime/qv4persistent_p.h b/src/qml/jsruntime/qv4persistent_p.h index 1f838f5531..55e8eefcb7 100644 --- a/src/qml/jsruntime/qv4persistent_p.h +++ b/src/qml/jsruntime/qv4persistent_p.h @@ -81,7 +81,7 @@ struct Q_QML_EXPORT PersistentValueStorage Value &operator *(); }; Iterator begin() { return Iterator(firstPage, 0); } - Iterator end() { return Iterator(0, 0); } + Iterator end() { return Iterator(nullptr, 0); } static ExecutionEngine *getEngine(Value *v); @@ -94,7 +94,7 @@ private: class Q_QML_EXPORT PersistentValue { public: - PersistentValue() : val(0) {} + PersistentValue() {} PersistentValue(const PersistentValue &other); PersistentValue &operator=(const PersistentValue &other); PersistentValue &operator=(const WeakValue &other); @@ -117,19 +117,19 @@ public: } Managed *asManaged() const { if (!val) - return 0; + return nullptr; return val->managed(); } template<typename T> T *as() const { if (!val) - return 0; + return nullptr; return val->as<T>(); } ExecutionEngine *engine() const { if (!val) - return 0; + return nullptr; return PersistentValueStorage::getEngine(val); } @@ -137,18 +137,18 @@ public: bool isNullOrUndefined() const { return !val || val->isNullOrUndefined(); } void clear() { PersistentValueStorage::free(val); - val = 0; + val = nullptr; } bool isEmpty() { return !val; } private: - Value *val; + Value *val = nullptr; }; class Q_QML_EXPORT WeakValue { public: - WeakValue() : val(0) {} + WeakValue() {} WeakValue(const WeakValue &other); WeakValue(ExecutionEngine *engine, const Value &value); WeakValue &operator=(const WeakValue &other); @@ -183,19 +183,19 @@ public: } Managed *asManaged() const { if (!val) - return 0; + return nullptr; return val->managed(); } template <typename T> T *as() const { if (!val) - return 0; + return nullptr; return val->as<T>(); } ExecutionEngine *engine() const { if (!val) - return 0; + return nullptr; return PersistentValueStorage::getEngine(val); } @@ -206,7 +206,7 @@ public: void markOnce(MarkStack *markStack); private: - Value *val; + Value *val = nullptr; private: Q_NEVER_INLINE void allocVal(ExecutionEngine *engine); diff --git a/src/qml/jsruntime/qv4profiling.cpp b/src/qml/jsruntime/qv4profiling.cpp index bedcb5b164..b337243204 100644 --- a/src/qml/jsruntime/qv4profiling.cpp +++ b/src/qml/jsruntime/qv4profiling.cpp @@ -78,7 +78,7 @@ Profiler::Profiler(QV4::ExecutionEngine *engine) : featuresEnabled(0), m_engine( void Profiler::stopProfiling() { featuresEnabled = 0; - reportData(true); + reportData(); m_sentLocations.clear(); } @@ -89,7 +89,7 @@ bool operator<(const FunctionCall &call1, const FunctionCall &call2) (call1.m_end == call2.m_end && call1.m_function < call2.m_function))); } -void Profiler::reportData(bool trackLocations) +void Profiler::reportData() { std::sort(m_data.begin(), m_data.end()); QVector<FunctionCallProperties> properties; @@ -100,12 +100,11 @@ void Profiler::reportData(bool trackLocations) properties.append(call.properties()); Function *function = call.function(); SentMarker &marker = m_sentLocations[reinterpret_cast<quintptr>(function)]; - if (!trackLocations || !marker.isValid()) { + if (!marker.isValid()) { FunctionLocation &location = locations[properties.constLast().id]; if (!location.isValid()) location = call.resolveLocation(); - if (trackLocations) - marker.setFunction(function); + marker.setFunction(function); } } @@ -120,7 +119,8 @@ void Profiler::startProfiling(quint64 features) if (features & (1 << FeatureMemoryAllocation)) { qint64 timestamp = m_timer.nsecsElapsed(); MemoryAllocationProperties heap = {timestamp, - (qint64)m_engine->memoryManager->getAllocatedMem(), + (qint64)m_engine->memoryManager->getAllocatedMem() - + (qint64)m_engine->memoryManager->getLargeItemsMem(), HeapPage}; m_memory_data.append(heap); MemoryAllocationProperties small = {timestamp, diff --git a/src/qml/jsruntime/qv4profiling_p.h b/src/qml/jsruntime/qv4profiling_p.h index 9de597ad0e..8461384e9a 100644 --- a/src/qml/jsruntime/qv4profiling_p.h +++ b/src/qml/jsruntime/qv4profiling_p.h @@ -57,17 +57,20 @@ #include <QElapsedTimer> -#ifdef QT_NO_QML_DEBUGGER +#if !QT_CONFIG(qml_debug) #define Q_V4_PROFILE_ALLOC(engine, size, type) (!engine) #define Q_V4_PROFILE_DEALLOC(engine, size, type) (!engine) -#define Q_V4_PROFILE(engine, function) (function->code(engine, function->codeData)) QT_BEGIN_NAMESPACE namespace QV4 { namespace Profiling { class Profiler {}; +class FunctionCallProfiler { +public: + FunctionCallProfiler(ExecutionEngine *, Function *) {} +}; } } @@ -85,12 +88,6 @@ QT_END_NAMESPACE (engine->profiler()->featuresEnabled & (1 << Profiling::FeatureMemoryAllocation)) ?\ engine->profiler()->trackDealloc(size, type) : false) -#define Q_V4_PROFILE(engine, function)\ - (Q_UNLIKELY(engine->profiler()) &&\ - (engine->profiler()->featuresEnabled & (1 << Profiling::FeatureFunctionCall)) ?\ - Profiling::FunctionCallProfiler::profileCall(engine->profiler(), engine, function) :\ - function->code(engine, function->codeData)) - QT_BEGIN_NAMESPACE namespace QV4 { @@ -142,7 +139,7 @@ struct MemoryAllocationProperties { class FunctionCall { public: - FunctionCall() : m_function(0), m_start(0), m_end(0) + FunctionCall() : m_function(nullptr), m_start(0), m_end(0) { Q_ASSERT_X(false, Q_FUNC_INFO, "Cannot construct a function call without function"); } FunctionCall(Function *function, qint64 start, qint64 end) : @@ -230,23 +227,31 @@ public: bool trackAlloc(size_t size, MemoryType type) { - MemoryAllocationProperties allocation = {m_timer.nsecsElapsed(), (qint64)size, type}; - m_memory_data.append(allocation); - return true; + if (size) { + MemoryAllocationProperties allocation = {m_timer.nsecsElapsed(), (qint64)size, type}; + m_memory_data.append(allocation); + return true; + } else { + return false; + } } bool trackDealloc(size_t size, MemoryType type) { - MemoryAllocationProperties allocation = {m_timer.nsecsElapsed(), -(qint64)size, type}; - m_memory_data.append(allocation); - return true; + if (size) { + MemoryAllocationProperties allocation = {m_timer.nsecsElapsed(), -(qint64)size, type}; + m_memory_data.append(allocation); + return true; + } else { + return false; + } } quint64 featuresEnabled; void stopProfiling(); void startProfiling(quint64 features); - void reportData(bool trackLocations); + void reportData(); void setTimer(const QElapsedTimer &timer) { m_timer = timer; } signals: @@ -270,19 +275,21 @@ public: // It's enough to ref() the function in the destructor as it will probably not disappear while // it's executing ... - FunctionCallProfiler(Profiler *profiler, Function *function) : - profiler(profiler), function(function), startTime(profiler->m_timer.nsecsElapsed()) - {} - - ~FunctionCallProfiler() + FunctionCallProfiler(ExecutionEngine *engine, Function *f) + : profiler(nullptr) { - profiler->m_data.append(FunctionCall(function, startTime, profiler->m_timer.nsecsElapsed())); + Profiler *p = engine->profiler(); + if (Q_UNLIKELY(p) && (p->featuresEnabled & (1 << Profiling::FeatureFunctionCall))) { + profiler = p; + function = f; + startTime = profiler->m_timer.nsecsElapsed(); + } } - static ReturnedValue profileCall(Profiler *profiler, ExecutionEngine *engine, Function *function) + ~FunctionCallProfiler() { - FunctionCallProfiler callProfiler(profiler, function); - return function->code(engine, function->codeData); + if (profiler) + profiler->m_data.append(FunctionCall(function, startTime, profiler->m_timer.nsecsElapsed())); } Profiler *profiler; @@ -305,6 +312,6 @@ Q_DECLARE_METATYPE(QV4::Profiling::FunctionLocationHash) Q_DECLARE_METATYPE(QVector<QV4::Profiling::FunctionCallProperties>) Q_DECLARE_METATYPE(QVector<QV4::Profiling::MemoryAllocationProperties>) -#endif // QT_NO_QML_DEBUGGER +#endif // QT_CONFIG(qml_debug) #endif // QV4PROFILING_H diff --git a/src/qml/jsruntime/qv4promiseobject.cpp b/src/qml/jsruntime/qv4promiseobject.cpp new file mode 100644 index 0000000000..b32e227b58 --- /dev/null +++ b/src/qml/jsruntime/qv4promiseobject.cpp @@ -0,0 +1,982 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QCoreApplication> + +#include <private/qv4promiseobject_p.h> +#include <private/qv4symbol_p.h> +#include "qv4jscall_p.h" + +using namespace QV4; +using namespace QV4::Promise; + +DEFINE_OBJECT_VTABLE(PromiseReaction); +DEFINE_OBJECT_VTABLE(PromiseCtor); +DEFINE_OBJECT_VTABLE(PromiseObject); +DEFINE_OBJECT_VTABLE(PromiseCapability); +DEFINE_OBJECT_VTABLE(PromiseExecutionState); + +DEFINE_OBJECT_VTABLE(CapabilitiesExecutorWrapper); +DEFINE_OBJECT_VTABLE(ResolveElementWrapper); +DEFINE_OBJECT_VTABLE(ResolveWrapper); +DEFINE_OBJECT_VTABLE(RejectWrapper); + + +namespace { + +bool isPromise(const Value &object) +{ + return object.as<PromiseObject>() != nullptr; +} + +bool isCallable(const Value &object) +{ + return object.as<FunctionObject>() != nullptr; +} + +void insertIdLengthTag(Scope& scope, Heap::FunctionObject* function) +{ + ScopedFunctionObject scopedFunction(scope, function); + scopedFunction->insertMember(scope.engine->id_length(), Primitive::fromInt32(1), Attr_NotWritable|Attr_NotEnumerable); +} + +void dropException(QV4::ExecutionEngine* e) +{ + e->hasException = false; +} +} + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace Promise { + +const int PROMISE_REACTION_EVENT = QEvent::registerEventType(); + +struct ReactionEvent : public QEvent +{ + ReactionEvent(ExecutionEngine *e, const Value *reaction_, const Value *resolution_) + : QEvent(QEvent::Type(PROMISE_REACTION_EVENT)), + reaction{e, *reaction_}, + resolution{e, *resolution_} + {} + + QV4::PersistentValue reaction; + QV4::PersistentValue resolution; +}; + +} // namespace Promise +} // namespace QV4 +QT_END_NAMESPACE + +ReactionHandler::ReactionHandler(QObject *parent) + : QObject(parent) +{} + +ReactionHandler::~ReactionHandler() +{} + +void ReactionHandler::addReaction(ExecutionEngine *e, const Value *reaction, const Value *value) +{ + QCoreApplication::postEvent(this, new ReactionEvent(e, reaction, value)); +} + +void ReactionHandler::customEvent(QEvent *event) +{ + if (event) + { + const int type = event->type(); + if (type == PROMISE_REACTION_EVENT) + executeReaction(static_cast<ReactionEvent*>(event)); + } +} + +void ReactionHandler::executeReaction(ReactionEvent *event) +{ + Scope scope(event->reaction.engine()); + + Scoped<QV4::PromiseReaction> ro(scope, event->reaction.as<QV4::PromiseReaction>()); + Scoped<QV4::PromiseCapability> capability(scope, ro->d()->capability); + + ScopedValue resolution(scope, event->resolution.value()); + ScopedValue promise(scope, capability->d()->promise); + + if (ro->d()->type == Heap::PromiseReaction::Function) { + ScopedFunctionObject handler(scope, ro->d()->handler.as<QV4::FunctionObject>()); + ScopedValue result(scope, handler->call(promise, resolution, 1)); + + ScopedFunctionObject reaction(scope); + if (scope.hasException()) { + reaction = capability->d()->reject.as<QV4::FunctionObject>(); + } else { + reaction = capability->d()->resolve.as<QV4::FunctionObject>(); + } + + reaction->call(promise, result, 1); + } else { + ScopedFunctionObject reaction(scope); + if (ro->d()->type == Heap::PromiseReaction::Identity) { + reaction = capability->d()->resolve.as<QV4::FunctionObject>(); + } else { + reaction = capability->d()->reject.as<QV4::FunctionObject>(); + } + + reaction->call(promise, resolution, 1); + } +} + +namespace { + +class FunctionBuilder { +public: + static Heap::FunctionObject *makeResolveFunction(ExecutionEngine* e, QV4::Heap::PromiseObject *promise) { + Scope scope(e); + Scoped<QV4::ResolveWrapper> resolveWrapper(scope, e->memoryManager->allocate<QV4::ResolveWrapper>()); + + insertIdLengthTag(scope, resolveWrapper->d()); + resolveWrapper->d()->promise.set(e, promise); + + return resolveWrapper->d(); + } + + static Heap::FunctionObject *makeRejectFunction(ExecutionEngine* e, QV4::Heap::PromiseObject *promise) { + Scope scope(e); + Scoped<QV4::RejectWrapper> rejectWrapper(scope, e->memoryManager->allocate<QV4::RejectWrapper>()); + + insertIdLengthTag(scope, rejectWrapper->d()); + rejectWrapper->d()->promise.set(e, promise); + + return rejectWrapper->d(); + } + + static Heap::FunctionObject *makeResolveElementFunction(ExecutionEngine* e, uint index, Heap::PromiseExecutionState *executionState) + { + Scope scope(e); + Scoped<QV4::ResolveElementWrapper> resolveElementWrapper(scope, e->memoryManager->allocate<QV4::ResolveElementWrapper>()); + + resolveElementWrapper->d()->index = index; + resolveElementWrapper->d()->alreadyResolved = false; + resolveElementWrapper->d()->state.set(e, executionState); + + insertIdLengthTag(scope, resolveElementWrapper->d()); + + return resolveElementWrapper->d(); + } +}; + +} + +void Heap::PromiseObject::setState(PromiseObject::State state) +{ + this->state = state; +} + +bool Heap::PromiseObject::isSettled() const +{ + return (state != Pending); +} + +bool Heap::PromiseObject::isPending() const +{ + return (state == Pending); +} + +bool Heap::PromiseObject::isFulfilled() const +{ + return (state == Fulfilled); +} + +bool Heap::PromiseObject::isRejected() const +{ + return (state == Rejected); +} + +void Heap::PromiseObject::triggerFullfillReactions(ExecutionEngine *e) +{ + Scope scope(e); + ScopedArrayObject a(scope, fulfillReactions); + if (a->arrayData()) { + Scoped<QV4::ArrayData> ad(scope, a->arrayData()); + const uint sz = ad->length(); + ScopedValue value(scope, resolution); + for (uint i = 0; i < sz; i++) { + Scoped<QV4::PromiseReaction> r(scope, ad->get(i)); + r->d()->triggerWithValue(scope.engine, value); + } + } +} + +void Heap::PromiseObject::triggerRejectReactions(ExecutionEngine *e) +{ + Scope scope(e); + ScopedArrayObject a(scope, rejectReactions); + if (a->arrayData()) { + Scoped<QV4::ArrayData> ad(scope, a->arrayData()); + const uint sz = ad->d()->length(); + ScopedValue value(scope, resolution); + for (uint i = 0; i < sz; i++) { + Scoped<QV4::PromiseReaction> r(scope, ad->d()->get(i)); + r->d()->triggerWithValue(scope.engine, value); + } + } +} + +Heap::PromiseReaction *Heap::PromiseReaction::createFulfillReaction(ExecutionEngine* e, + const QV4::PromiseCapability *capability, const QV4::FunctionObject *onFulfilled) +{ + Scope scope(e); + Scoped<QV4::PromiseReaction> fulfillReaction(scope, e->memoryManager->allocate<QV4::PromiseReaction>()); + fulfillReaction->d()->capability.set(e, capability->d()); + + if (onFulfilled) { + QV4::ScopedFunctionObject scopedFullfillReaction(scope, onFulfilled); + if (!scopedFullfillReaction) { + fulfillReaction->d()->type = PromiseReaction::Identity; + } else { + fulfillReaction->d()->type = PromiseReaction::Function; + fulfillReaction->d()->handler.set(e, scopedFullfillReaction); + } + } else { + fulfillReaction->d()->type = PromiseReaction::Identity; + } + + return fulfillReaction->d(); +} + +Heap::PromiseReaction *Heap::PromiseReaction::createRejectReaction(ExecutionEngine* e, + const QV4::PromiseCapability *capability, const QV4::FunctionObject *onRejected) +{ + Scope scope(e); + Scoped<QV4::PromiseReaction> rejectReaction(scope, e->memoryManager->allocate<QV4::PromiseReaction>()); + rejectReaction->d()->capability.set(e, capability->d()); + + if (onRejected) { + ScopedFunctionObject scopedRejectReaction(scope, onRejected); + if (!scopedRejectReaction) { + rejectReaction->d()->type = PromiseReaction::Thrower; + } else { + rejectReaction->d()->type = PromiseReaction::Function; + rejectReaction->d()->handler.set(e, scopedRejectReaction); + } + } else { + rejectReaction->d()->type = PromiseReaction::Thrower; + } + + return rejectReaction->d(); +} + +void Heap::PromiseReaction::triggerWithValue(ExecutionEngine *e, const Value *value) +{ + Scope scope(e); + auto handler = e->getPromiseReactionHandler(); + ScopedValue reaction(scope, Value::fromHeapObject(this)); + handler->addReaction(e, reaction, value); +} + +void Heap::PromiseCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("Promise")); +} + +void Heap::PromiseObject::init(ExecutionEngine *e) +{ + Heap::Object::init(); + + { + Heap::ArrayObject* a = e->newArrayObject(); + fulfillReactions.set(e, a); + } + + { + Heap::ArrayObject* a = e->newArrayObject(); + rejectReactions.set(e, a); + } +} + +void Heap::CapabilitiesExecutorWrapper::init() +{ + Heap::FunctionObject::init(); +} + +void Heap::CapabilitiesExecutorWrapper::destroy() +{ + Heap::FunctionObject::destroy(); +} + +void Heap::PromiseExecutionState::init() +{ + index = 0; + remainingElementCount = 0; +} + +void Heap::ResolveElementWrapper::init() +{ + index = 0; + alreadyResolved = false; + + Heap::FunctionObject::init(); +} + +void Heap::ResolveWrapper::init() +{ + alreadyResolved = false; + Heap::FunctionObject::init(); +} + +void Heap::RejectWrapper::init() +{ + alreadyResolved = false; + Heap::FunctionObject::init(); +} + + +ReturnedValue PromiseCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + Scope scope(f); + + if (argc != 1) + THROW_TYPE_ERROR(); + + ScopedFunctionObject executor(scope, argv[0].as<const FunctionObject>()); + if (!executor) + THROW_TYPE_ERROR(); + + Scoped<PromiseObject> a(scope, scope.engine->newPromiseObject()); + if (scope.engine->hasException) + return Encode::undefined(); + + a->d()->state = Heap::PromiseObject::Pending; + + ScopedFunctionObject resolve(scope, FunctionBuilder::makeResolveFunction(scope.engine, a->d())); + ScopedFunctionObject reject(scope, FunctionBuilder::makeRejectFunction(scope.engine, a->d())); + + JSCallData jsCallData(scope, 2); + jsCallData->args[0] = resolve; + jsCallData->args[1] = reject; + jsCallData->thisObject = a; + + executor->call(jsCallData); + + if (scope.engine->hasException) { + a->d()->state = Heap::PromiseObject::Rejected; + a->d()->resolution.set(scope.engine, Value::fromReturnedValue(scope.engine->catchException())); + } + + if (newTarget) + a->setProtoFromNewTarget(newTarget); + + return a->asReturnedValue(); +} + +ReturnedValue PromiseCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) +{ + Scope scope(f); + THROW_TYPE_ERROR(); +} + +ReturnedValue PromiseCtor::method_resolve(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + ExecutionEngine* e = scope.engine; + if (!thisObject || !thisObject->isObject()) + THROW_TYPE_ERROR(); + + ScopedValue argument(scope); + if (argc < 1) { + argument = Encode::undefined(); + } else { + argument = argv[0]; + } + + if (isPromise(argument) && argument->isObject()) { + ScopedObject so(scope, thisObject); + ScopedObject constructor(scope, argument->objectValue()->get(e->id_constructor())); + if (so->d() == constructor->d()) + return argument->asReturnedValue(); + } + + Scoped<PromiseCapability> capability(scope, e->memoryManager->allocate<QV4::PromiseCapability>()); + + ScopedObject newPromise(scope, e->newPromiseObject(thisObject->as<const FunctionObject>(), capability)); + if (!newPromise || !isCallable(capability->d()->resolve) || !isCallable(capability->d()->reject)) + THROW_TYPE_ERROR(); + + ScopedValue undefined(scope, Value::undefinedValue()); + ScopedFunctionObject resolve(scope, capability->d()->resolve); + resolve->call(undefined, argument, 1); + + return newPromise.asReturnedValue(); +} + +ReturnedValue PromiseCtor::method_reject(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + ExecutionEngine *e = scope.engine; + + if (!thisObject || !thisObject->isObject()) + THROW_TYPE_ERROR(); + + ScopedValue argument(scope); + if (argc < 1) { + argument = Encode::undefined(); + } else { + argument = argv[0]; + } + + Scoped<PromiseCapability> capability(scope, e->memoryManager->allocate<QV4::PromiseCapability>()); + + ScopedObject newPromise(scope, e->newPromiseObject(thisObject->as<const FunctionObject>(), capability)); + if (!newPromise || !isCallable(capability->d()->resolve) || !isCallable(capability->d()->reject)) + THROW_TYPE_ERROR(); + + ScopedValue undefined(scope, Value::undefinedValue()); + ScopedFunctionObject reject(scope, capability->d()->reject.as<const FunctionObject>()); + reject->call(undefined, argument, 1); + + return newPromise.asReturnedValue(); +} + +ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *thisObject, const Value *argv, int) +{ + Scope scope(f); + ExecutionEngine* e = scope.engine; + + if (!thisObject || !thisObject->isObject()) + THROW_TYPE_ERROR(); + + ScopedString resolveName(scope, e->newIdentifier(QStringLiteral("resolve"))); + ScopedString thenName(scope, e->newIdentifier(QStringLiteral("then"))); + + Scoped<PromiseCapability> capability(scope, e->memoryManager->allocate<QV4::PromiseCapability>()); + + ScopedObject newPromise(scope, e->newPromiseObject(thisObject->as<FunctionObject>(), capability)); + if (!newPromise || !isCallable(capability->d()->resolve) || !isCallable(capability->d()->reject)) { + if (scope.hasException()) { + return e->exceptionValue->asReturnedValue(); + } else { + THROW_TYPE_ERROR(); + } + } + capability->d()->promise.set(e, newPromise); + + ScopedFunctionObject reject(scope, capability->d()->reject); + + ScopedObject itemsObject(scope, argv); + ScopedObject iteratorObject(scope, Runtime::GetIterator::call(e, itemsObject, true)); + if (!iteratorObject || scope.hasException()) { + ScopedObject error(scope); + if (scope.hasException()) { + error = e->exceptionValue; + dropException(e); + } else { + error = e->newTypeErrorObject(QStringLiteral("Type error")); + } + reject->call(newPromise, error, 1); + return newPromise.asReturnedValue(); + } + + Scoped<QV4::PromiseExecutionState> executionState(scope, e->memoryManager->allocate<QV4::PromiseExecutionState>()); + executionState->d()->remainingElementCount = 1; + executionState->d()->capability.set(e, capability); + + Scoped<QV4::ArrayObject> results(scope, e->newArrayObject(0)); + executionState->d()->values.set(e, results); + + ScopedValue doneValue(scope); + uint index = 0; + for (;;) { + Scope scope(e); + ScopedValue nextValue(scope); + doneValue = Value::fromReturnedValue(Runtime::IteratorNext::call(e, iteratorObject, nextValue)); + + if (doneValue->toBoolean()) + break; + + ScopedObject nextObject(scope); + if (nextValue->isObject()) { + nextObject = *nextValue; + } else if (nextValue->isBoolean()) { + nextObject = e->newBooleanObject(nextValue->toBoolean()); + } else if (nextValue->isInteger() || nextValue->isDouble()) { + nextObject = e->newNumberObject(nextValue->toInteger()); + } else if (nextValue->isString()) { + ScopedString scopedString(scope, nextValue->toString(scope.engine)); + nextObject = e->newStringObject(scopedString); + } + + ScopedFunctionObject resolve(scope, thisObject->as<Object>()->get(resolveName)); + if (!resolve || scope.hasException()) { + ScopedValue completion(scope); + if (!scope.hasException()) { + completion = e->newTypeErrorObject(QStringLiteral("Type error")); + } else { + completion = e->exceptionValue->asReturnedValue(); + dropException(e); + } + + if (!doneValue->toBoolean()) + completion = Runtime::IteratorClose::call(e, iteratorObject, doneValue); + + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + + ScopedObject nextPromise(scope, Value::fromReturnedValue(resolve->call(thisObject, nextValue, 1))); + if (!nextPromise || scope.hasException()) { + ScopedValue completion(scope, Runtime::IteratorClose::call(e, iteratorObject, doneValue)); + if (scope.hasException()) { + completion = e->exceptionValue->asReturnedValue(); + dropException(e); + } + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + + executionState->d()->remainingElementCount++; + + ScopedFunctionObject then(scope, nextPromise->get(thenName)); + if (!then || scope.hasException()) { + ScopedValue completion(scope); + if (!scope.hasException()) { + completion = e->newTypeErrorObject(QStringLiteral("Type error")); + } else { + completion = e->exceptionValue->asReturnedValue(); + dropException(e); + } + + if (!doneValue->toBoolean()) + completion = Runtime::IteratorClose::call(scope.engine, iteratorObject, doneValue); + + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + + ScopedFunctionObject resolveElement(scope, FunctionBuilder::makeResolveElementFunction(e, index, executionState->d())); + + JSCallData jsCallData(scope, 2); + jsCallData->args[0] = resolveElement; + jsCallData->args[1] = reject; + jsCallData->thisObject = nextPromise; + + then->call(jsCallData); + if (scope.hasException()) { + ScopedValue completion(scope, e->exceptionValue->asReturnedValue()); + dropException(e); + + if (!doneValue->toBoolean()) + completion = Runtime::IteratorClose::call(scope.engine, iteratorObject, doneValue); + + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + + index++; + } + + // empty list + executionState->d()->remainingElementCount--; + if (executionState->d()->remainingElementCount == 0) { + const FunctionObject *resolve = capability->d()->resolve.as<FunctionObject>(); + if (!resolve) + THROW_TYPE_ERROR(); + + ScopedValue values(scope, executionState->d()->values); + resolve->call(newPromise, values, 1); + if (scope.hasException()) { + dropException(e); + reject->call(newPromise, scope.engine->exceptionValue, 1); + } + } + + return newPromise.asReturnedValue(); +} + +ReturnedValue PromiseCtor::method_race(const FunctionObject *f, const Value *thisObject, const Value *argv, int) +{ + Scope scope(f); + ExecutionEngine* e = scope.engine; + + if (!thisObject || !thisObject->isObject()) + THROW_TYPE_ERROR(); + + ScopedString resolveName(scope, e->newIdentifier(QStringLiteral("resolve"))); + ScopedString thenName(scope, e->newIdentifier(QStringLiteral("then"))); + + Scoped<PromiseCapability> capability(scope, scope.engine->memoryManager->allocate<QV4::PromiseCapability>()); + + ScopedObject newPromise(scope, e->newPromiseObject(thisObject->as<FunctionObject>(), capability)); + if (!newPromise || !isCallable(capability->d()->resolve) || !isCallable(capability->d()->reject)) + THROW_TYPE_ERROR(); + capability->d()->promise.set(scope.engine, newPromise); + + ScopedFunctionObject reject(scope, capability->d()->reject); + + ScopedObject itemsObject(scope, argv); + ScopedObject iteratorObject(scope, Runtime::GetIterator::call(e, itemsObject, true)); + if (!iteratorObject) { + ScopedObject error(scope, e->newTypeErrorObject(QStringLiteral("Type error"))); + reject->call(newPromise, error, 1); + return newPromise.asReturnedValue(); + } + + ScopedValue doneValue(scope); + for (;;) { + Scope scope(e); + ScopedValue nextValue(scope); + doneValue = Value::fromReturnedValue(Runtime::IteratorNext::call(e, iteratorObject, nextValue)); + + if (scope.hasException()) { + ScopedValue completion(scope, Runtime::IteratorClose::call(e, iteratorObject, doneValue)); + if (scope.hasException()) { + completion = e->exceptionValue->asReturnedValue(); + dropException(e); + } + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + + if (doneValue->toBoolean()) + break; + + ScopedObject nextObject(scope); + if (nextValue->isObject()) { + nextObject = *nextValue; + } else if (nextValue->isBoolean()) { + nextObject = scope.engine->newBooleanObject(nextValue->toBoolean()); + } else if (nextValue->isInteger() || nextValue->isDouble()) { + nextObject = scope.engine->newNumberObject(nextValue->toInteger()); + } else if (nextValue->isString()) { + ScopedString scopedString(scope, nextValue->toString(scope.engine)); + nextObject = scope.engine->newStringObject(scopedString); + } + + ScopedFunctionObject resolve(scope, thisObject->as<FunctionObject>()->get(resolveName)); + if (!resolve || scope.hasException()) { + ScopedValue completion(scope); + if (!scope.hasException()) { + completion = e->newTypeErrorObject(QStringLiteral("Type error")); + } else { + completion = e->exceptionValue->asReturnedValue(); + dropException(e); + } + + if (!doneValue->toBoolean()) + completion = Runtime::IteratorClose::call(e, iteratorObject, doneValue); + + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + + ScopedObject nextPromise(scope, Value::fromReturnedValue(resolve->call(thisObject, nextValue, 1))); + if (!nextPromise || scope.hasException()) { + ScopedValue completion(scope, Runtime::IteratorClose::call(e, iteratorObject, doneValue)); + if (scope.hasException()) { + completion = e->exceptionValue->asReturnedValue(); + dropException(e); + } + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + + ScopedFunctionObject then(scope, nextPromise->get(thenName)); + if (!then || scope.hasException()) { + ScopedValue completion(scope); + if (!scope.hasException()) { + completion = e->newTypeErrorObject(QStringLiteral("Type error")); + } else { + completion = e->exceptionValue->asReturnedValue(); + dropException(e); + } + + if (!doneValue->toBoolean()) + completion = Runtime::IteratorClose::call(e, iteratorObject, doneValue); + + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + + ScopedFunctionObject resolveOriginalPromise(scope, capability->d()->resolve); + + JSCallData jsCallData(scope, 2); + jsCallData->args[0] = resolveOriginalPromise; + jsCallData->args[1] = reject; + jsCallData->thisObject = nextPromise; + + then->call(jsCallData); + if (scope.hasException()) { + ScopedValue completion(scope, e->exceptionValue->asReturnedValue()); + dropException(e); + + if (!doneValue->toBoolean()) + completion = Runtime::IteratorClose::call(e, iteratorObject, doneValue); + + reject->call(newPromise, completion, 1); + return newPromise.asReturnedValue(); + } + } + + return newPromise.asReturnedValue(); +} + +void PromisePrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedObject o(scope); + + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Primitive::fromInt32(1)); + ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); + + ctor->defineDefaultProperty(QStringLiteral("resolve"), PromiseCtor::method_resolve, 1); + ctor->defineDefaultProperty(QStringLiteral("reject"), PromiseCtor::method_reject, 1); + ctor->defineDefaultProperty(QStringLiteral("all"), PromiseCtor::method_all, 1); + ctor->defineDefaultProperty(QStringLiteral("race"), PromiseCtor::method_race, 1); + ctor->addSymbolSpecies(); + + defineDefaultProperty(engine->id_constructor(), (o = ctor)); + + ScopedString val(scope, engine->newString(QLatin1String("Promise"))); + defineReadonlyConfigurableProperty(engine->symbol_toStringTag(), val); + + defineDefaultProperty(QStringLiteral("then"), method_then, 2); + defineDefaultProperty(QStringLiteral("catch"), method_catch, 1); +} + +ReturnedValue PromisePrototype::method_then(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + ExecutionEngine* e = scope.engine; + + Scoped<QV4::PromiseObject> promise(scope, thisObject); + if (!promise) + THROW_TYPE_ERROR(); + + ScopedFunctionObject onFulfilled(scope); + if (argc >= 1) { + onFulfilled = argv[0]; + } else { + onFulfilled = Encode::undefined(); + } + + ScopedFunctionObject onRejected(scope); + if (argc >= 2) { + onRejected = argv[1]; + } else { + onRejected = Encode::undefined(); + } + + Scoped<PromiseCapability> capability(scope, e->memoryManager->allocate<PromiseCapability>()); + + ScopedFunctionObject constructor(scope, promise->get(e->id_constructor())); + if (!constructor || scope.hasException()) + THROW_TYPE_ERROR(); + + ScopedObject nextPromise(scope, e->newPromiseObject(constructor, capability)); + capability->d()->promise.set(scope.engine, nextPromise); + + Scoped<PromiseReaction> fulfillReaction(scope, Heap::PromiseReaction::createFulfillReaction(scope.engine, capability, onFulfilled)); + Scoped<PromiseReaction> rejectReaction(scope, Heap::PromiseReaction::createRejectReaction(scope.engine, capability, onRejected)); + + ScopedValue resolution(scope, promise->d()->resolution); + if (promise->d()->isPending()) { + { + ScopedArrayObject a(scope, promise->d()->fulfillReactions); + ScopedValue newValue(scope, fulfillReaction->d()); + a->push_back(newValue); + } + + { + ScopedArrayObject a(scope, promise->d()->rejectReactions); + ScopedValue newValue(scope, rejectReaction->d()); + a->push_back(newValue); + } + } else if (promise->d()->isFulfilled()) { + fulfillReaction->as<QV4::PromiseReaction>()->d()->triggerWithValue(e, resolution); + } else if (promise->d()->isRejected()) { + rejectReaction->as<QV4::PromiseReaction>()->d()->triggerWithValue(e, resolution); + } else { + Q_ASSERT(false); + THROW_GENERIC_ERROR("Should never be thrown. Unknown promise state"); + } + + return nextPromise->asReturnedValue(); +} + +ReturnedValue PromisePrototype::method_catch(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + Scoped<Object> promise(scope); + if (thisObject->isObject()) { + promise.setPointer(thisObject->as<Object>()); + } else if (thisObject->isBoolean()) { + promise = scope.engine->newBooleanObject(thisObject->toBoolean()); + } else if (thisObject->isInteger() || thisObject->isDouble()) { + promise = scope.engine->newNumberObject(thisObject->toInteger()); + } else if (thisObject->isString()) { + ScopedString scopedString(scope, thisObject->toString(scope.engine)); + promise = scope.engine->newStringObject(scopedString); + } else { + THROW_TYPE_ERROR(); + } + + ScopedValue onRejected(scope); + if (argc < 1) { + onRejected = Encode::undefined(); + } else { + onRejected = argv[0]; + } + + JSCallData jsCallData(scope, 2); + jsCallData->args[0] = Encode::undefined(); + jsCallData->args[1] = onRejected; + jsCallData->thisObject = promise; + + ScopedString thenName(scope, scope.engine->newIdentifier(QStringLiteral("then"))); + ScopedFunctionObject then(scope, promise->get(thenName)); + if (!then || scope.hasException()) + THROW_TYPE_ERROR(); + + return then->call(jsCallData); +} + +ReturnedValue CapabilitiesExecutorWrapper::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Q_UNUSED(thisObject); + + Scope scope(f); + const CapabilitiesExecutorWrapper* self = static_cast<const CapabilitiesExecutorWrapper*>(f); + Heap::PromiseCapability *capabilities = self->d()->capabilities; + + if (!capabilities->resolve.isUndefined() || !capabilities->reject.isUndefined()) + THROW_TYPE_ERROR(); + + if (argc >= 1 && !argv[0].isUndefined()) + capabilities->resolve.set(scope.engine, argv[0]); + + if (argc >= 2 && !argv[1].isUndefined()) + capabilities->reject.set(scope.engine, argv[1]); + + // TODO: return? + return Encode::undefined(); +} + +ReturnedValue ResolveElementWrapper::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Q_UNUSED(thisObject); + + Scope scope(f); + const ResolveElementWrapper* self = static_cast<const ResolveElementWrapper*>(f); + + if (self->d()->alreadyResolved) + return Encode::undefined(); + + ScopedValue value(scope); + if (argc == 1) { + value = argv[0]; + } else { + value = Encode::undefined(); + } + + Scoped<PromiseExecutionState> so(scope, self->d()->state); + self->d()->alreadyResolved = true; + + ScopedObject values(scope, so->d()->values); + values->arraySet(self->d()->index, value); + + so->d()->remainingElementCount--; + if (so->d()->remainingElementCount == 0) { + Scoped<PromiseCapability> capability(scope, so->d()->capability); + ScopedValue promise(scope, capability->d()->promise); + ScopedFunctionObject resolve(scope, capability->d()->resolve.as<QV4::FunctionObject>()); + resolve->call(promise, values, 1); + } + + return Encode::undefined(); +} + +ReturnedValue ResolveWrapper::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Q_UNUSED(thisObject); + + Scope scope(f); + const ResolveWrapper *self = static_cast<const ResolveWrapper*>(f); + + Scoped<PromiseObject> promise(scope, self->d()->promise); + if (self->d()->alreadyResolved || !promise->d()->isPending()) + return Encode::undefined(); + + ScopedValue value(scope); + if (argc == 1) { + value = argv[0]; + } else { + value = Encode::undefined(); + } + + self->d()->alreadyResolved = true; + promise->d()->setState(Heap::PromiseObject::Fulfilled); + promise->d()->resolution.set(scope.engine, value); + + promise->d()->triggerFullfillReactions(scope.engine); + + return Encode::undefined(); +} + +ReturnedValue RejectWrapper::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Q_UNUSED(thisObject); + + Scope scope(f); + const RejectWrapper *self = static_cast<const RejectWrapper*>(f); + + Scoped<PromiseObject> promise(scope, self->d()->promise); + if (self->d()->alreadyResolved || !promise->d()->isPending()) + return Encode::undefined(); + + ScopedValue value(scope); + if (argc == 1) { + value = argv[0]; + } else { + value = Encode::undefined(); + } + + self->d()->alreadyResolved = true; + promise->d()->setState(Heap::PromiseObject::Rejected); + promise->d()->resolution.set(scope.engine, value); + + promise->d()->triggerRejectReactions(scope.engine); + + return Encode::undefined(); +} diff --git a/src/qml/jsruntime/qv4promiseobject_p.h b/src/qml/jsruntime/qv4promiseobject_p.h new file mode 100644 index 0000000000..bce59b19a7 --- /dev/null +++ b/src/qml/jsruntime/qv4promiseobject_p.h @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4PROMISEOBJECT_H +#define QV4PROMISEOBJECT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4functionobject_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +struct PromiseCapability; + +namespace Promise { + +struct ReactionEvent; + +class ReactionHandler : public QObject +{ + Q_OBJECT + +public: + ReactionHandler(QObject *parent = nullptr); + virtual ~ReactionHandler(); + + void addReaction(ExecutionEngine *e, const Value *reaction, const Value *value); + +protected: + void customEvent(QEvent *event); + void executeReaction(ReactionEvent *event); +}; + +} // Promise + +namespace Heap { + +struct PromiseCtor : FunctionObject { + void init(QV4::ExecutionContext *scope); +}; + +#define PromiseObjectMembers(class, Member) \ + Member(class, HeapValue, HeapValue, resolution) \ + Member(class, HeapValue, HeapValue, fulfillReactions) \ + Member(class, HeapValue, HeapValue, rejectReactions) + +DECLARE_HEAP_OBJECT(PromiseObject, Object) { + DECLARE_MARKOBJECTS(PromiseObject) + void init(ExecutionEngine *e); + + enum State { + Pending, + Fulfilled, + Rejected + }; + + void setState(State); + bool isSettled() const; + bool isPending() const; + bool isFulfilled() const; + bool isRejected() const; + + State state; + + void triggerFullfillReactions(ExecutionEngine *e); + void triggerRejectReactions(ExecutionEngine *e); +}; + +#define PromiseCapabilityMembers(class, Member) \ + Member(class, HeapValue, HeapValue, promise) \ + Member(class, HeapValue, HeapValue, resolve) \ + Member(class, HeapValue, HeapValue, reject) + +DECLARE_HEAP_OBJECT(PromiseCapability, Object) { + DECLARE_MARKOBJECTS(PromiseCapability) +}; + +#define PromiseReactionMembers(class, Member) \ + Member(class, HeapValue, HeapValue, handler) \ + Member(class, Pointer, PromiseCapability*, capability) + +DECLARE_HEAP_OBJECT(PromiseReaction, Object) { + DECLARE_MARKOBJECTS(PromiseReaction) + + static Heap::PromiseReaction *createFulfillReaction(ExecutionEngine* e, const QV4::PromiseCapability *capability, const QV4::FunctionObject *onFulfilled); + static Heap::PromiseReaction *createRejectReaction(ExecutionEngine* e, const QV4::PromiseCapability *capability, const QV4::FunctionObject *onRejected); + + void triggerWithValue(ExecutionEngine *e, const Value *value); + + enum Type { + Function, + Identity, + Thrower + }; + + Type type; + + friend class ReactionHandler; +}; + +#define CapabilitiesExecutorWrapperMembers(class, Member) \ + Member(class, Pointer, PromiseCapability*, capabilities) + +DECLARE_HEAP_OBJECT(CapabilitiesExecutorWrapper, FunctionObject) { + DECLARE_MARKOBJECTS(CapabilitiesExecutorWrapper) + void init(); + void destroy(); +}; + +#define PromiseExecutionStateMembers(class, Member) \ + Member(class, HeapValue, HeapValue, values) \ + Member(class, HeapValue, HeapValue, capability) + +DECLARE_HEAP_OBJECT(PromiseExecutionState, FunctionObject) { + DECLARE_MARKOBJECTS(PromiseExecutionState) + void init(); + + uint index; + uint remainingElementCount; +}; + +#define ResolveElementWrapperMembers(class, Member) \ + Member(class, HeapValue, HeapValue, state) + +DECLARE_HEAP_OBJECT(ResolveElementWrapper, FunctionObject) { + DECLARE_MARKOBJECTS(ResolveElementWrapper) + void init(); + + uint index; + bool alreadyResolved; +}; + +#define ResolveWrapperMembers(class, Member) \ + Member(class, Pointer, PromiseObject*, promise) + +DECLARE_HEAP_OBJECT(ResolveWrapper, FunctionObject) { + DECLARE_MARKOBJECTS(ResolveWrapper) + void init(); + + bool alreadyResolved; +}; + +#define RejectWrapperMembers(class, Member) \ + Member(class, Pointer, PromiseObject*, promise) + +DECLARE_HEAP_OBJECT(RejectWrapper, FunctionObject) { + DECLARE_MARKOBJECTS(RejectWrapper) + void init(); + + bool alreadyResolved; +}; + +} // Heap + +struct PromiseReaction : Object +{ + V4_OBJECT2(PromiseReaction, Object) +}; + +struct PromiseCapability : Object +{ + V4_OBJECT2(PromiseCapability, Object) +}; + +struct PromiseExecutionState : Object +{ + V4_OBJECT2(PromiseExecutionState, Object) +}; + +struct Q_QML_PRIVATE_EXPORT PromiseObject : Object +{ + V4_OBJECT2(PromiseObject, Object) + V4_NEEDS_DESTROY + V4_PROTOTYPE(promisePrototype) +}; + +struct PromiseCtor: FunctionObject +{ + V4_OBJECT2(PromiseCtor, FunctionObject) + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_resolve(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_reject(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_all(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_race(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct PromisePrototype : Object +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_then(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_catch(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct CapabilitiesExecutorWrapper: FunctionObject { + V4_OBJECT2(CapabilitiesExecutorWrapper, FunctionObject) + + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct ResolveElementWrapper : FunctionObject { + V4_OBJECT2(ResolveElementWrapper, FunctionObject) + + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct ResolveWrapper : FunctionObject { + V4_OBJECT2(ResolveWrapper, FunctionObject) + + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct RejectWrapper : FunctionObject { + V4_OBJECT2(RejectWrapper, FunctionObject) + + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +} // QV4 + +QT_END_NAMESPACE + +#endif // QV4PROMISEOBJECT_H diff --git a/src/qml/jsruntime/qv4property_p.h b/src/qml/jsruntime/qv4property_p.h index 2a5b6f7f74..555f323737 100644 --- a/src/qml/jsruntime/qv4property_p.h +++ b/src/qml/jsruntime/qv4property_p.h @@ -66,25 +66,43 @@ struct Property { // Section 8.10 inline void fullyPopulated(PropertyAttributes *attrs) { if (!attrs->hasType()) { - value = Primitive::undefinedValue(); + value = Value::undefinedValue(); } if (attrs->type() == PropertyAttributes::Accessor) { attrs->clearWritable(); if (value.isEmpty()) - value = Primitive::undefinedValue(); + value = Value::undefinedValue(); if (set.isEmpty()) - set = Primitive::undefinedValue(); + set = Value::undefinedValue(); } attrs->resolve(); } + // ES8: 6.2.5.6 + void completed(PropertyAttributes *attrs) { + if (value.isEmpty()) + value = Encode::undefined(); + if (attrs->isGeneric() || attrs->isData()) { + attrs->setType(PropertyAttributes::Data); + if (!attrs->hasWritable()) + attrs->setWritable(false); + } else { + if (set.isEmpty()) + set = Encode::undefined(); + } + if (!attrs->hasEnumerable()) + attrs->setEnumerable(false); + if (!attrs->hasConfigurable()) + attrs->setConfigurable(false); + } + inline bool isSubset(const PropertyAttributes &attrs, const Property *other, PropertyAttributes otherAttrs) const; inline void merge(PropertyAttributes &attrs, const Property *other, PropertyAttributes otherAttrs); inline Heap::FunctionObject *getter() const { return reinterpret_cast<Heap::FunctionObject *>(value.heapObject()); } inline Heap::FunctionObject *setter() const { return reinterpret_cast<Heap::FunctionObject *>(set.heapObject()); } inline void setGetter(FunctionObject *g) { value = reinterpret_cast<Managed *>(g); } - inline void setSetter(FunctionObject *s) { set = (s ? reinterpret_cast<Managed *>(s) : 0); } + inline void setSetter(FunctionObject *s) { set = (s ? reinterpret_cast<Managed *>(s) : nullptr); } void copy(const Property *other, PropertyAttributes attrs) { value = other->value; @@ -92,7 +110,41 @@ struct Property { set = other->set; } - explicit Property() { value = Encode::undefined(); set = Value::fromHeapObject(0); } + // ES8, section 9.1.6.2/9,.1.6.3 + bool isCompatible(PropertyAttributes &attrs, const Property *other, PropertyAttributes otherAttrs) const { + if (otherAttrs.isEmpty()) + return true; + if (!attrs.isConfigurable()) { + if (otherAttrs.hasConfigurable() && otherAttrs.isConfigurable()) + return false; + if (otherAttrs.hasEnumerable() && otherAttrs.isEnumerable() != attrs.isEnumerable()) + return false; + } + if (otherAttrs.isGeneric()) + return true; + if (attrs.isData() != otherAttrs.isData()) { + if (!attrs.isConfigurable()) + return false; + } else if (attrs.isData() && otherAttrs.isData()) { + if (!attrs.isConfigurable() && !attrs.isWritable()) { + if (otherAttrs.hasWritable() && otherAttrs.isWritable()) + return false; + if (!other->value.isEmpty() && !value.sameValue(other->value)) + return false; + } + } else if (attrs.isAccessor() && otherAttrs.isAccessor()) { + if (!attrs.isConfigurable()) { + if (!other->value.isEmpty() && !value.sameValue(other->value)) + return false; + if (!other->set.isEmpty() && !set.sameValue(other->set)) + return false; + } + } + return true; + } + + + explicit Property() { value = Encode::undefined(); set = Value::fromHeapObject(nullptr); } Property(Heap::FunctionObject *getter, Heap::FunctionObject *setter) { value.setM(reinterpret_cast<Heap::Base *>(getter)); set.setM(reinterpret_cast<Heap::Base *>(setter)); @@ -142,6 +194,19 @@ inline void Property::merge(PropertyAttributes &attrs, const Property *other, Pr } } +struct PropertyIndex { + Heap::Base *base; + Value *slot; + + void set(EngineBase *e, Value newVal) { + WriteBarrier::write(e, base, slot->data_ptr(), newVal.asReturnedValue()); + } + const Value *operator->() const { return slot; } + const Value &operator*() const { return *slot; } + bool isNull() const { return !slot; } +}; + + } Q_DECLARE_TYPEINFO(QV4::Property, Q_MOVABLE_TYPE); diff --git a/src/qml/jsruntime/qv4propertykey.cpp b/src/qml/jsruntime/qv4propertykey.cpp new file mode 100644 index 0000000000..064d030b83 --- /dev/null +++ b/src/qml/jsruntime/qv4propertykey.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4propertykey_p.h" + +#include <QtCore/qstring.h> +#include <qv4string_p.h> +#include <qv4engine_p.h> +#include <qv4scopedvalue_p.h> + +QV4::Heap::StringOrSymbol *QV4::PropertyKey::toStringOrSymbol(QV4::ExecutionEngine *e) +{ + if (isArrayIndex()) + return Value::fromUInt32(asArrayIndex()).toString(e); + return static_cast<Heap::StringOrSymbol *>(asStringOrSymbol()); +} + +bool QV4::PropertyKey::isString() const { + Heap::StringOrSymbol *s = asStringOrSymbol(); + return s && s->internalClass->vtable->isString; +} + +bool QV4::PropertyKey::isSymbol() const { + Heap::Base *s = asStringOrSymbol(); + return s && !s->internalClass->vtable->isString && s->internalClass->vtable->isStringOrSymbol; +} + +bool QV4::PropertyKey::isCanonicalNumericIndexString() const +{ + if (isArrayIndex()) + return true; + if (isSymbol()) + return false; + Heap::String *s = static_cast<Heap::String *>(asStringOrSymbol()); + Scope scope(s->internalClass->engine); + ScopedString str(scope, s); + double d = str->toNumber(); + if (d == 0. && std::signbit(d)) + return true; + ScopedString converted(scope, Value::fromDouble(d).toString(scope.engine)); + if (converted->equals(str)) + return true; + return false; +} + +QString QV4::PropertyKey::toQString() const +{ + if (isArrayIndex()) + return QString::number(asArrayIndex()); + Heap::StringOrSymbol *s = asStringOrSymbol(); + Q_ASSERT(s->internalClass->vtable->isStringOrSymbol); + return s->toQString(); +} + +QV4::Heap::String *QV4::PropertyKey::asFunctionName(ExecutionEngine *engine, FunctionNamePrefix prefix) const +{ + QString n; + if (prefix == Getter) + n = QStringLiteral("get "); + else if (prefix == Setter) + n = QStringLiteral("set "); + if (isArrayIndex()) + n += QString::number(asArrayIndex()); + else { + Heap::StringOrSymbol *s = asStringOrSymbol(); + QString str = s->toQString(); + if (s->internalClass->vtable->isString) + n += s->toQString(); + else if (str.length() > 1) + n += QChar::fromLatin1('[') + str.midRef(1) + QChar::fromLatin1(']'); + } + return engine->newString(n); +} diff --git a/src/qml/jsruntime/qv4propertykey_p.h b/src/qml/jsruntime/qv4propertykey_p.h new file mode 100644 index 0000000000..47867765db --- /dev/null +++ b/src/qml/jsruntime/qv4propertykey_p.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4PROPERTYKEY_H +#define QV4PROPERTYKEY_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4global_p.h> + +QT_BEGIN_NAMESPACE + +class QString; + +namespace QV4 { + +struct PropertyKey +{ +private: + // Property keys are Strings, Symbols or unsigned integers. + // For convenience we derive them from Values, allowing us to store them + // on the JS stack + // + // They do however behave somewhat different than a Value: + // * If the key is a String, the pointer to the string is stored in the identifier + // table and thus unique. + // * If the key is a Symbol it simply points to the referenced symbol object + // * if the key is an array index (a uint < UINT_MAX), it's encoded as an + // integer value + quint64 val; + + // Important: Always keep this in sync with the definitions for Integers and heap objects in Value + static const quint64 ArrayIndexMask = 0x3800000000000ull; + enum { + IsManagedOrUndefined_Shift = 64-15, + }; + inline bool isManaged() const { return (val >> IsManagedOrUndefined_Shift) == 0; } + inline quint32 value() const { return val & quint64(~quint32(0)); } + +#if QT_POINTER_SIZE == 8 + QML_NEARLY_ALWAYS_INLINE Heap::StringOrSymbol *m() const + { + Heap::StringOrSymbol *b; + memcpy(&b, &val, 8); + return b; + } + QML_NEARLY_ALWAYS_INLINE void setM(Heap::StringOrSymbol *b) + { + memcpy(&val, &b, 8); + } +#elif QT_POINTER_SIZE == 4 + QML_NEARLY_ALWAYS_INLINE Heap::StringOrSymbol *m() const + { + Q_STATIC_ASSERT(sizeof(Heap::StringOrSymbol*) == sizeof(quint32)); + Heap::StringOrSymbol *b; + quint32 v = value(); + memcpy(&b, &v, 4); + return b; + } + QML_NEARLY_ALWAYS_INLINE void setM(Heap::StringOrSymbol *b) + { + quint32 v; + memcpy(&v, &b, 4); + val = v; + } +#endif + +public: + static PropertyKey invalid() { PropertyKey key; key.val = 0; return key; } + static PropertyKey fromArrayIndex(uint idx) { PropertyKey key; key.val = ArrayIndexMask | static_cast<quint64>(idx); return key; } + bool isStringOrSymbol() const { return isManaged() && val != 0; } + uint asArrayIndex() const { return (isManaged() || val == 0) ? std::numeric_limits<uint>::max() : static_cast<uint>(val & 0xffffffff); } + uint isArrayIndex() const { return !isManaged() && val != 0 && static_cast<uint>(val & 0xffffffff) != std::numeric_limits<uint>::max(); } + bool isValid() const { return val != 0; } + static PropertyKey fromStringOrSymbol(Heap::StringOrSymbol *b) + { PropertyKey key; key.setM(b); return key; } + Heap::StringOrSymbol *asStringOrSymbol() const { + if (!isManaged()) + return nullptr; + return m(); + } + + bool isString() const; + bool isSymbol() const; + bool isCanonicalNumericIndexString() const; + + Q_QML_EXPORT QString toQString() const; + Heap::StringOrSymbol *toStringOrSymbol(ExecutionEngine *e); + quint64 id() const { return val; } + static PropertyKey fromId(quint64 id) { + PropertyKey key; key.val = id; return key; + } + + enum FunctionNamePrefix { + None, + Getter, + Setter + }; + Heap::String *asFunctionName(ExecutionEngine *e, FunctionNamePrefix prefix) const; + + bool operator ==(const PropertyKey &other) const { return val == other.val; } + bool operator !=(const PropertyKey &other) const { return val != other.val; } + bool operator <(const PropertyKey &other) const { return val < other.val; } +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/jsruntime/qv4proxy.cpp b/src/qml/jsruntime/qv4proxy.cpp new file mode 100644 index 0000000000..9325e2e53b --- /dev/null +++ b/src/qml/jsruntime/qv4proxy.cpp @@ -0,0 +1,782 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qv4proxy_p.h" +#include "qv4symbol_p.h" +#include "qv4jscall_p.h" +#include "qv4objectproto_p.h" +#include "qv4persistent_p.h" +#include "qv4objectiterator_p.h" + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(ProxyObject); +DEFINE_OBJECT_VTABLE(ProxyFunctionObject); + +void Heap::ProxyObject::init(const QV4::Object *target, const QV4::Object *handler) +{ + Object::init(); + ExecutionEngine *e = internalClass->engine; + this->target.set(e, target->d()); + this->handler.set(e, handler->d()); +} + +void Heap::ProxyFunctionObject::init(const QV4::FunctionObject *target, const QV4::Object *handler) +{ + ExecutionEngine *e = internalClass->engine; + FunctionObject::init(e->rootContext()); + this->target.set(e, target->d()); + this->handler.set(e, handler->d()); + + if (!target->isConstructor()) + jsConstruct = nullptr; +} + + +ReturnedValue ProxyObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) + return scope.engine->throwTypeError(); + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedValue trap(scope, handler->get(scope.engine->id_get())); + if (scope.hasException()) + return Encode::undefined(); + if (trap->isNullOrUndefined()) + return target->get(id, receiver, hasProperty); + if (!trap->isFunctionObject()) + return scope.engine->throwTypeError(); + if (hasProperty) + *hasProperty = true; + + JSCallData cdata(scope, 3, nullptr, handler); + cdata.args[0] = target; + cdata.args[1] = id.toStringOrSymbol(scope.engine); + cdata.args[2] = *receiver; + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + ScopedProperty targetDesc(scope); + PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); + if (attributes != Attr_Invalid && !attributes.isConfigurable()) { + if (attributes.isData() && !attributes.isWritable()) { + if (!trapResult->sameValue(targetDesc->value)) + return scope.engine->throwTypeError(); + } + if (attributes.isAccessor() && targetDesc->value.isUndefined()) { + if (!trapResult->isUndefined()) + return scope.engine->throwTypeError(); + } + } + return trapResult->asReturnedValue(); +} + +bool ProxyObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) + return scope.engine->throwTypeError(); + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedValue trap(scope, handler->get(scope.engine->id_set())); + if (scope.hasException()) + return Encode::undefined(); + if (trap->isNullOrUndefined()) + return target->put(id, value, receiver); + if (!trap->isFunctionObject()) + return scope.engine->throwTypeError(); + + JSCallData cdata(scope, 4, nullptr, handler); + cdata.args[0] = target; + cdata.args[1] = id.toStringOrSymbol(scope.engine); + cdata.args[2] = value; + cdata.args[3] = *receiver; + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (!trapResult->toBoolean()) + return false; + ScopedProperty targetDesc(scope); + PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); + if (attributes != Attr_Invalid && !attributes.isConfigurable()) { + if (attributes.isData() && !attributes.isWritable()) { + if (!value.sameValue(targetDesc->value)) + return scope.engine->throwTypeError(); + } + if (attributes.isAccessor() && targetDesc->set.isUndefined()) + return scope.engine->throwTypeError(); + } + return true; +} + +bool ProxyObject::virtualDeleteProperty(Managed *m, PropertyKey id) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) + return scope.engine->throwTypeError(); + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString deleteProp(scope, scope.engine->newString(QStringLiteral("deleteProperty"))); + ScopedValue trap(scope, handler->get(deleteProp)); + if (scope.hasException()) + return Encode::undefined(); + if (trap->isNullOrUndefined()) + return target->deleteProperty(id); + if (!trap->isFunctionObject()) + return scope.engine->throwTypeError(); + + JSCallData cdata(scope, 3, nullptr, handler); + cdata.args[0] = target; + cdata.args[1] = id.toStringOrSymbol(scope.engine); + cdata.args[2] = o->d(); // ### fix receiver handling + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (!trapResult->toBoolean()) + return false; + ScopedProperty targetDesc(scope); + PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); + if (attributes == Attr_Invalid) + return true; + if (!attributes.isConfigurable()) + return scope.engine->throwTypeError(); + return true; +} + +bool ProxyObject::virtualHasProperty(const Managed *m, PropertyKey id) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) + return scope.engine->throwTypeError(); + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("has"))); + ScopedValue trap(scope, handler->get(hasProp)); + if (scope.hasException()) + return Encode::undefined(); + if (trap->isNullOrUndefined()) + return target->hasProperty(id); + if (!trap->isFunctionObject()) + return scope.engine->throwTypeError(); + + JSCallData cdata(scope, 2, nullptr, handler); + cdata.args[0] = target; + cdata.args[1] = id.isArrayIndex() ? Value::fromUInt32(id.asArrayIndex()).toString(scope.engine) : id.asStringOrSymbol(); + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + bool result = trapResult->toBoolean(); + if (!result) { + ScopedProperty targetDesc(scope); + PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); + if (attributes != Attr_Invalid) { + if (!attributes.isConfigurable() || !target->isExtensible()) + return scope.engine->throwTypeError(); + } + } + return result; +} + +PropertyAttributes ProxyObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) { + scope.engine->throwTypeError(); + return Attr_Invalid; + } + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString deleteProp(scope, scope.engine->newString(QStringLiteral("getOwnPropertyDescriptor"))); + ScopedValue trap(scope, handler->get(deleteProp)); + if (scope.hasException()) + return Attr_Invalid; + if (trap->isNullOrUndefined()) + return target->getOwnProperty(id, p); + if (!trap->isFunctionObject()) { + scope.engine->throwTypeError(); + return Attr_Invalid; + } + + JSCallData cdata(scope, 2, nullptr, handler); + cdata.args[0] = target; + cdata.args[1] = id.isArrayIndex() ? Value::fromUInt32(id.asArrayIndex()).toString(scope.engine) : id.asStringOrSymbol(); + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (!trapResult->isObject() && !trapResult->isUndefined()) { + scope.engine->throwTypeError(); + return Attr_Invalid; + } + + ScopedProperty targetDesc(scope); + PropertyAttributes targetAttributes = target->getOwnProperty(id, targetDesc); + if (trapResult->isUndefined()) { + p->value = Encode::undefined(); + if (targetAttributes == Attr_Invalid) { + p->value = Encode::undefined(); + return Attr_Invalid; + } + if (!targetAttributes.isConfigurable() || !target->isExtensible()) { + scope.engine->throwTypeError(); + return Attr_Invalid; + } + return Attr_Invalid; + } + + //bool extensibleTarget = target->isExtensible(); + ScopedProperty resultDesc(scope); + PropertyAttributes resultAttributes; + ObjectPrototype::toPropertyDescriptor(scope.engine, trapResult, resultDesc, &resultAttributes); + resultDesc->completed(&resultAttributes); + + if (!targetDesc->isCompatible(targetAttributes, resultDesc, resultAttributes)) { + scope.engine->throwTypeError(); + return Attr_Invalid; + } + + if (!resultAttributes.isConfigurable()) { + if (targetAttributes == Attr_Invalid || targetAttributes.isConfigurable()) { + scope.engine->throwTypeError(); + return Attr_Invalid; + } + } + + p->value = resultDesc->value; + p->set = resultDesc->set; + return resultAttributes; +} + +bool ProxyObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) { + scope.engine->throwTypeError(); + return false; + } + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString prop(scope, scope.engine->newString(QStringLiteral("defineProperty"))); + ScopedValue trap(scope, handler->get(prop)); + if (scope.hasException()) + return false; + if (trap->isNullOrUndefined()) + return target->defineOwnProperty(id, p, attrs); + if (!trap->isFunctionObject()) { + scope.engine->throwTypeError(); + return false; + } + + JSCallData cdata(scope, 3, nullptr, handler); + cdata.args[0] = target; + cdata.args[1] = id.isArrayIndex() ? Value::fromUInt32(id.asArrayIndex()).toString(scope.engine) : id.asStringOrSymbol(); + cdata.args[2] = ObjectPrototype::fromPropertyDescriptor(scope.engine, p, attrs); + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + bool result = trapResult->toBoolean(); + if (!result) + return false; + + ScopedProperty targetDesc(scope); + PropertyAttributes targetAttributes = target->getOwnProperty(id, targetDesc); + bool extensibleTarget = target->isExtensible(); + bool settingConfigFalse = attrs.hasConfigurable() && !attrs.isConfigurable(); + if (targetAttributes == Attr_Invalid) { + if (!extensibleTarget || settingConfigFalse) { + scope.engine->throwTypeError(); + return false; + } + } else { + if (!targetDesc->isCompatible(targetAttributes, p, attrs)) { + scope.engine->throwTypeError(); + return false; + } + if (settingConfigFalse && targetAttributes.isConfigurable()) { + scope.engine->throwTypeError(); + return false; + } + } + + return true; +} + +bool ProxyObject::virtualIsExtensible(const Managed *m) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) + return scope.engine->throwTypeError(); + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("isExtensible"))); + ScopedValue trap(scope, handler->get(hasProp)); + if (scope.hasException()) + return Encode::undefined(); + if (trap->isNullOrUndefined()) + return target->isExtensible(); + if (!trap->isFunctionObject()) + return scope.engine->throwTypeError(); + + JSCallData cdata(scope, 1, nullptr, handler); + cdata.args[0] = target; + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + bool result = trapResult->toBoolean(); + if (result != target->isExtensible()) { + scope.engine->throwTypeError(); + return false; + } + return result; +} + +bool ProxyObject::virtualPreventExtensions(Managed *m) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) + return scope.engine->throwTypeError(); + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("preventExtensions"))); + ScopedValue trap(scope, handler->get(hasProp)); + if (scope.hasException()) + return Encode::undefined(); + if (trap->isNullOrUndefined()) + return target->preventExtensions(); + if (!trap->isFunctionObject()) + return scope.engine->throwTypeError(); + + JSCallData cdata(scope, 1, nullptr, handler); + cdata.args[0] = target; + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + bool result = trapResult->toBoolean(); + if (result && target->isExtensible()) { + scope.engine->throwTypeError(); + return false; + } + return result; +} + +Heap::Object *ProxyObject::virtualGetPrototypeOf(const Managed *m) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) { + scope.engine->throwTypeError(); + return nullptr; + } + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString name(scope, scope.engine->newString(QStringLiteral("getPrototypeOf"))); + ScopedValue trap(scope, handler->get(name)); + if (scope.hasException()) + return nullptr; + if (trap->isNullOrUndefined()) + return target->getPrototypeOf(); + if (!trap->isFunctionObject()) { + scope.engine->throwTypeError(); + return nullptr; + } + + JSCallData cdata(scope, 1, nullptr, handler); + cdata.args[0] = target; + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (!trapResult->isNull() && !trapResult->isObject()) { + scope.engine->throwTypeError(); + return nullptr; + } + Heap::Object *proto = trapResult->isNull() ? nullptr : static_cast<Heap::Object *>(trapResult->heapObject()); + if (!target->isExtensible()) { + Heap::Object *targetProto = target->getPrototypeOf(); + if (proto != targetProto) { + scope.engine->throwTypeError(); + return nullptr; + } + } + return proto; +} + +bool ProxyObject::virtualSetPrototypeOf(Managed *m, const Object *p) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) { + scope.engine->throwTypeError(); + return false; + } + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString name(scope, scope.engine->newString(QStringLiteral("setPrototypeOf"))); + ScopedValue trap(scope, handler->get(name)); + if (scope.hasException()) + return false; + if (trap->isNullOrUndefined()) + return target->setPrototypeOf(p); + if (!trap->isFunctionObject()) { + scope.engine->throwTypeError(); + return false; + } + + JSCallData cdata(scope, 2, nullptr, handler); + cdata.args[0] = target; + cdata.args[1] = p ? p->asReturnedValue() : Encode::null(); + + ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + bool result = trapResult->toBoolean(); + if (!result) + return false; + if (!target->isExtensible()) { + Heap::Object *targetProto = target->getPrototypeOf(); + if (p->d() != targetProto) { + scope.engine->throwTypeError(); + return false; + } + } + return true; +} + +struct ProxyObjectOwnPropertyKeyIterator : OwnPropertyKeyIterator +{ + PersistentValue ownKeys; + uint index = 0; + uint len = 0; + + ProxyObjectOwnPropertyKeyIterator(ArrayObject *keys); + ~ProxyObjectOwnPropertyKeyIterator() override = default; + PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; + +}; + +ProxyObjectOwnPropertyKeyIterator::ProxyObjectOwnPropertyKeyIterator(ArrayObject *keys) +{ + ownKeys = keys; + len = keys->getLength(); +} + +PropertyKey ProxyObjectOwnPropertyKeyIterator::next(const Object *m, Property *pd, PropertyAttributes *attrs) +{ + if (index >= len) + return PropertyKey::invalid(); + + Scope scope(m); + ScopedObject keys(scope, ownKeys.asManaged()); + PropertyKey key = PropertyKey::fromId(keys->get(PropertyKey::fromArrayIndex(index))); + ++index; + + if (pd || attrs) { + ScopedProperty p(scope); + PropertyAttributes a = const_cast<Object *>(m)->getOwnProperty(key, pd ? pd : p); + if (attrs) + *attrs = a; + } + + return key; +} + +static bool removeAllOccurrences(ArrayObject *target, ReturnedValue val) { + uint len = target->getLength(); + bool found = false; + for (uint i = 0; i < len; ++i) { + ReturnedValue v = target->get(i); + if (v == val) { + found = true; + target->put(i, Value::undefinedValue()); + } + } + return found; +} + +OwnPropertyKeyIterator *ProxyObject::virtualOwnPropertyKeys(const Object *m, Value *iteratorTarget) +{ + Scope scope(m); + const ProxyObject *o = static_cast<const ProxyObject *>(m); + if (!o->d()->handler) { + scope.engine->throwTypeError(); + return nullptr; + } + + ScopedObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString name(scope, scope.engine->newString(QStringLiteral("ownKeys"))); + ScopedValue trap(scope, handler->get(name)); + + if (scope.hasException()) + return nullptr; + if (trap->isUndefined()) + return target->ownPropertyKeys(iteratorTarget); + if (!trap->isFunctionObject()) { + scope.engine->throwTypeError(); + return nullptr; + } + + JSCallData cdata(scope, 1, nullptr, handler); + cdata.args[0] = target; + ScopedObject trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (!trapResult) { + scope.engine->throwTypeError(); + return nullptr; + } + + uint len = trapResult->getLength(); + ScopedArrayObject trapKeys(scope, scope.engine->newArrayObject()); + ScopedStringOrSymbol key(scope); + for (uint i = 0; i < len; ++i) { + key = trapResult->get(i); + if (scope.engine->hasException) + return nullptr; + if (!key) { + scope.engine->throwTypeError(); + return nullptr; + } + Value keyAsValue = Value::fromReturnedValue(key->toPropertyKey().id()); + trapKeys->push_back(keyAsValue); + } + + ScopedArrayObject targetConfigurableKeys(scope, scope.engine->newArrayObject()); + ScopedArrayObject targetNonConfigurableKeys(scope, scope.engine->newArrayObject()); + ObjectIterator it(scope, target, ObjectIterator::EnumerableOnly); + ScopedPropertyKey k(scope); + while (1) { + PropertyAttributes attrs; + k = it.next(nullptr, &attrs); + if (!k->isValid()) + break; + Value keyAsValue = Value::fromReturnedValue(k->id()); + if (attrs.isConfigurable()) + targetConfigurableKeys->push_back(keyAsValue); + else + targetNonConfigurableKeys->push_back(keyAsValue); + } + if (target->isExtensible() && targetNonConfigurableKeys->getLength() == 0) + return new ProxyObjectOwnPropertyKeyIterator(trapKeys); + + ScopedArrayObject uncheckedResultKeys(scope, scope.engine->newArrayObject()); + uncheckedResultKeys->copyArrayData(trapKeys); + + len = targetNonConfigurableKeys->getLength(); + for (uint i = 0; i < len; ++i) { + k = PropertyKey::fromId(targetNonConfigurableKeys->get(i)); + if (!removeAllOccurrences(uncheckedResultKeys, k->id())) { + scope.engine->throwTypeError(); + return nullptr; + } + } + + if (target->isExtensible()) + return new ProxyObjectOwnPropertyKeyIterator(trapKeys); + + len = targetConfigurableKeys->getLength(); + for (uint i = 0; i < len; ++i) { + k = PropertyKey::fromId(targetConfigurableKeys->get(i)); + if (!removeAllOccurrences(uncheckedResultKeys, k->id())) { + scope.engine->throwTypeError(); + return nullptr; + } + } + + len = uncheckedResultKeys->getLength(); + for (uint i = 0; i < len; ++i) { + if (uncheckedResultKeys->get(i) != Encode::undefined()) { + scope.engine->throwTypeError(); + return nullptr; + } + } + + *iteratorTarget = *m; + return new ProxyObjectOwnPropertyKeyIterator(trapKeys); +} + + +ReturnedValue ProxyFunctionObject::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + Scope scope(f); + const ProxyObject *o = static_cast<const ProxyObject *>(f); + if (!o->d()->handler) + return scope.engine->throwTypeError(); + + ScopedFunctionObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString name(scope, scope.engine->newString(QStringLiteral("construct"))); + ScopedValue trap(scope, handler->get(name)); + + if (scope.hasException()) + return Encode::undefined(); + if (trap->isNullOrUndefined()) { + Q_ASSERT(target->isConstructor()); + return target->callAsConstructor(argv, argc, newTarget); + } + if (!trap->isFunctionObject()) + return scope.engine->throwTypeError(); + + ScopedFunctionObject trapFunction(scope, trap); + Value *arguments = scope.alloc(3); + arguments[0] = target; + arguments[1] = scope.engine->newArrayObject(argv, argc); + arguments[2] = newTarget ? *newTarget : Value::undefinedValue(); + ScopedObject result(scope, trapFunction->call(handler, arguments, 3)); + + if (!result) + return scope.engine->throwTypeError(); + return result->asReturnedValue(); +} + +ReturnedValue ProxyFunctionObject::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + + const ProxyObject *o = static_cast<const ProxyObject *>(f); + if (!o->d()->handler) + return scope.engine->throwTypeError(); + + ScopedFunctionObject target(scope, o->d()->target); + Q_ASSERT(target); + ScopedObject handler(scope, o->d()->handler); + ScopedString name(scope, scope.engine->newString(QStringLiteral("apply"))); + ScopedValue trap(scope, handler->get(name)); + + if (scope.hasException()) + return Encode::undefined(); + if (trap->isNullOrUndefined()) + return target->call(thisObject, argv, argc); + if (!trap->isFunctionObject()) + return scope.engine->throwTypeError(); + + ScopedFunctionObject trapFunction(scope, trap); + Value *arguments = scope.alloc(3); + arguments[0] = target; + arguments[1] = thisObject ? *thisObject : Value::undefinedValue(); + arguments[2] = scope.engine->newArrayObject(argv, argc); + return trapFunction->call(handler, arguments, 3); +} + +DEFINE_OBJECT_VTABLE(Proxy); + +void Heap::Proxy::init(QV4::ExecutionContext *ctx) +{ + Heap::FunctionObject::init(ctx, QStringLiteral("Proxy")); + + Scope scope(ctx); + Scoped<QV4::Proxy> ctor(scope, this); + ctor->defineDefaultProperty(QStringLiteral("revocable"), QV4::Proxy::method_revocable, 2); + ctor->defineReadonlyConfigurableProperty(scope.engine->id_length(), Value::fromInt32(2)); +} + +ReturnedValue Proxy::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *) +{ + Scope scope(f); + if (argc < 2 || !argv[0].isObject() || !argv[1].isObject()) + return scope.engine->throwTypeError(); + + const Object *target = static_cast<const Object *>(argv); + const Object *handler = static_cast<const Object *>(argv + 1); + if (const ProxyObject *ptarget = target->as<ProxyObject>()) + if (!ptarget->d()->handler) + return scope.engine->throwTypeError(); + if (const ProxyObject *phandler = handler->as<ProxyObject>()) + if (!phandler->d()->handler) + return scope.engine->throwTypeError(); + + const FunctionObject *targetFunction = target->as<FunctionObject>(); + if (targetFunction) + return scope.engine->memoryManager->allocate<ProxyFunctionObject>(targetFunction, handler)->asReturnedValue(); + return scope.engine->memoryManager->allocate<ProxyObject>(target, handler)->asReturnedValue(); +} + +ReturnedValue Proxy::virtualCall(const FunctionObject *f, const Value *, const Value *, int) +{ + return f->engine()->throwTypeError(); +} + +ReturnedValue Proxy::method_revocable(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + ScopedObject proxy(scope, Proxy::virtualCallAsConstructor(f, argv, argc, f)); + if (scope.hasException()) + return Encode::undefined(); + Q_ASSERT(proxy); + + ScopedString revoke(scope, scope.engine->newString(QStringLiteral("revoke"))); + ScopedFunctionObject revoker(scope, scope.engine->memoryManager->allocate<FunctionObject>(scope.engine->rootContext(), nullptr, method_revoke)); + revoker->defineReadonlyConfigurableProperty(scope.engine->id_length(), Value::fromInt32(0)); + revoker->defineDefaultProperty(scope.engine->symbol_revokableProxy(), proxy); + + ScopedObject o(scope, scope.engine->newObject()); + ScopedString p(scope, scope.engine->newString(QStringLiteral("proxy"))); + o->defineDefaultProperty(p, proxy); + o->defineDefaultProperty(revoke, revoker); + return o->asReturnedValue(); +} + +ReturnedValue Proxy::method_revoke(const FunctionObject *f, const Value *, const Value *, int) +{ + Scope scope(f); + ScopedObject o(scope, f->get(scope.engine->symbol_revokableProxy())); + Q_ASSERT(o); + ProxyObject *proxy = o->cast<ProxyObject>(); + + proxy->d()->target.set(scope.engine, nullptr); + proxy->d()->handler.set(scope.engine, nullptr); + return Encode::undefined(); +} diff --git a/src/qml/jsruntime/qv4proxy_p.h b/src/qml/jsruntime/qv4proxy_p.h new file mode 100644 index 0000000000..2ccb50ece6 --- /dev/null +++ b/src/qml/jsruntime/qv4proxy_p.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4PROXY_P_H +#define QV4PROXY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4functionobject_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Heap { + +#define ProxyObjectMembers(class, Member) \ + Member(class, Pointer, Object *, target) \ + Member(class, Pointer, Object *, handler) + +DECLARE_HEAP_OBJECT(ProxyObject, FunctionObject) { + DECLARE_MARKOBJECTS(ProxyObject) + + void init(const QV4::Object *target, const QV4::Object *handler); +}; + +struct ProxyFunctionObject : ProxyObject { + void init(const QV4::FunctionObject *target, const QV4::Object *handler); +}; + +#define ProxyMembers(class, Member) \ + Member(class, Pointer, Symbol *, revokableProxySymbol) \ + +DECLARE_HEAP_OBJECT(Proxy, FunctionObject) { + DECLARE_MARKOBJECTS(Proxy) + + void init(QV4::ExecutionContext *ctx); +}; + +} + +/* + * The inheritance from FunctionObject is a hack. Regular proxy objects are no function objects. + * But this helps implement the proxy for function objects, where we need this and thus gives us + * all the virtual methods from ProxyObject without having to duplicate them. + * + * But it does require a few hacks to make sure we don't recognize regular proxy objects as function + * objects in the runtime. + */ +struct ProxyObject : FunctionObject { + V4_OBJECT2(ProxyObject, Object) + Q_MANAGED_TYPE(ProxyObject) + V4_INTERNALCLASS(ProxyObject) + enum { + IsFunctionObject = false + }; + + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); + static bool virtualDeleteProperty(Managed *m, PropertyKey id); + static bool virtualHasProperty(const Managed *m, PropertyKey id); + static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); + static bool virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs); + static bool virtualIsExtensible(const Managed *m); + static bool virtualPreventExtensions(Managed *); + static Heap::Object *virtualGetPrototypeOf(const Managed *); + static bool virtualSetPrototypeOf(Managed *, const Object *); + static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *iteratorTarget); +}; + +struct ProxyFunctionObject : ProxyObject { + V4_OBJECT2(ProxyFunctionObject, FunctionObject) + Q_MANAGED_TYPE(ProxyObject) + V4_INTERNALCLASS(ProxyFunctionObject) + enum { + IsFunctionObject = true + }; + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct Proxy : FunctionObject +{ + V4_OBJECT2(Proxy, FunctionObject) + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_revocable(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_revoke(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + +} + +QT_END_NAMESPACE + +#endif // QV4ECMAOBJECTS_P_H diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index 61d785066f..88b0822f42 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -54,6 +54,7 @@ #include <private/qqmljavascriptexpression_p.h> #include <private/qjsvalue_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qv4module_p.h> QT_BEGIN_NAMESPACE @@ -62,57 +63,56 @@ using namespace QV4; DEFINE_OBJECT_VTABLE(QQmlContextWrapper); DEFINE_MANAGED_VTABLE(QmlContext); -void Heap::QQmlContextWrapper::init(QQmlContextData *context, QObject *scopeObject, bool ownsContext) +void Heap::QQmlContextWrapper::init(QQmlContextData *context, QObject *scopeObject) { Object::init(); - readOnly = true; - this->ownsContext = ownsContext; - isNullWrapper = false; - this->context = new QQmlGuardedContextData(context); + this->context = new QQmlContextDataRef(context); this->scopeObject.init(scopeObject); } void Heap::QQmlContextWrapper::destroy() { - if (*context && ownsContext) - (*context)->destroy(); delete context; scopeObject.destroy(); Object::destroy(); } -ReturnedValue QQmlContextWrapper::get(const Managed *m, String *name, bool *hasProperty) +ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { Q_ASSERT(m->as<QQmlContextWrapper>()); + + if (!id.isString()) + return Object::virtualGet(m, id, receiver, hasProperty); + const QQmlContextWrapper *resource = static_cast<const QQmlContextWrapper *>(m); QV4::ExecutionEngine *v4 = resource->engine(); QV4::Scope scope(v4); - // In V8 the JS global object would come _before_ the QML global object, - // so simulate that here. - bool hasProp; - QV4::ScopedValue result(scope, v4->globalObject->get(name, &hasProp)); - if (hasProp) { - if (hasProperty) - *hasProperty = hasProp; - return result->asReturnedValue(); - } - - if (resource->d()->isNullWrapper) - return Object::get(m, name, hasProperty); + if (v4->callingQmlContext() != *resource->d()->context) { + if (resource->d()->module) { + Scoped<Module> module(scope, resource->d()->module); + bool hasProp = false; + ScopedValue value(scope, module->get(id, receiver, &hasProp)); + if (hasProp) { + if (hasProperty) + *hasProperty = hasProp; + return value->asReturnedValue(); + } + } - if (v4->callingQmlContext() != *resource->d()->context) - return Object::get(m, name, hasProperty); + return Object::virtualGet(m, id, receiver, hasProperty); + } - result = Object::get(m, name, &hasProp); + bool hasProp = false; + ScopedValue result(scope, Object::virtualGet(m, id, receiver, &hasProp)); if (hasProp) { if (hasProperty) *hasProperty = hasProp; return result->asReturnedValue(); } - // Its possible we could delay the calculation of the "actual" context (in the case - // of sub contexts) until it is definately needed. + // It's possible we could delay the calculation of the "actual" context (in the case + // of sub contexts) until it is definitely needed. QQmlContextData *context = resource->getContext(); QQmlContextData *expressionContext = context; @@ -132,17 +132,51 @@ ReturnedValue QQmlContextWrapper::get(const Managed *m, String *name, bool *hasP QObject *scopeObject = resource->getScopeObject(); + ScopedString name(scope, id.asStringOrSymbol()); + + const auto performGobalLookUp = [&result, v4, &name, hasProperty]() { + bool hasProp = false; + result = v4->globalObject->get(name, &hasProp); + if (hasProp) { + if (hasProperty) + *hasProperty = hasProp; + return true; + } + return false; + }; + + // If the scope object is a QAbstractDynamicMetaObject, then QMetaObject::indexOfProperty + // will call createProperty() on the QADMO and implicitly create the property. While that + // is questionable behavior, there are two use-cases that we support in the light of this: + // + // (1) The implicit creation of properties is necessary because it will also result in + // a recorded capture, which will allow a re-evaluation of bindings when the value + // is populated later. See QTBUG-35233 and the test-case in tst_qqmlpropertymap. + // + // (1) Looking up "console" in order to place a console.log() call for example must + // find the console instead of creating a new property. Therefore we prioritize the + // lookup in the global object here. + // + // Note: The scope object is only a QADMO for example when somebody registers a QQmlPropertyMap + // sub-class as QML type and then instantiates it in .qml. + if (scopeObject && QQmlPropertyCache::isDynamicMetaObject(scopeObject->metaObject())) { + if (performGobalLookUp()) + return result->asReturnedValue(); + } + if (context->imports && name->startsWithUpper()) { // Search for attached properties, enums and imported scripts - QQmlTypeNameCache::Result r = context->imports->query(name); + QQmlTypeNameCache::Result r = context->imports->query(name, QQmlImport::AllowRecursion); if (r.isValid()) { if (hasProperty) *hasProperty = true; if (r.scriptIndex != -1) { QV4::ScopedObject scripts(scope, context->importedScripts.valueRef()); - return scripts->getIndexed(r.scriptIndex); - } else if (r.type) { + if (scripts) + return scripts->get(r.scriptIndex); + return QV4::Encode::null(); + } else if (r.type.isValid()) { return QQmlTypeWrapper::create(v4, scopeObject, r.type); } else if (r.importNamespace) { return QQmlTypeWrapper::create(v4, scopeObject, context->imports, r.importNamespace); @@ -157,7 +191,7 @@ ReturnedValue QQmlContextWrapper::get(const Managed *m, String *name, bool *hasP while (context) { // Search context properties - const QV4::IdentifierHash<int> &properties = context->propertyNames(); + const QV4::IdentifierHash &properties = context->propertyNames(); if (properties.count()) { int propertyIdx = properties.value(name); @@ -203,7 +237,7 @@ ReturnedValue QQmlContextWrapper::get(const Managed *m, String *name, bool *hasP return result->asReturnedValue(); } } - scopeObject = 0; + scopeObject = nullptr; // Search context object @@ -220,14 +254,23 @@ ReturnedValue QQmlContextWrapper::get(const Managed *m, String *name, bool *hasP context = context->parent; } + // Do a lookup in the global object here to avoid expressionContext->unresolvedNames becoming + // true if we access properties of the global object. + if (performGobalLookUp()) + return result->asReturnedValue(); + expressionContext->unresolvedNames = true; return Encode::undefined(); } -bool QQmlContextWrapper::put(Managed *m, String *name, const Value &value) +bool QQmlContextWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { Q_ASSERT(m->as<QQmlContextWrapper>()); + + if (id.isSymbol() || id.isArrayIndex()) + return Object::virtualPut(m, id, value, receiver); + QQmlContextWrapper *resource = static_cast<QQmlContextWrapper *>(m); ExecutionEngine *v4 = resource->engine(); QV4::Scope scope(v4); @@ -235,24 +278,12 @@ bool QQmlContextWrapper::put(Managed *m, String *name, const Value &value) return false; QV4::Scoped<QQmlContextWrapper> wrapper(scope, resource); - uint member = wrapper->internalClass()->find(name); - if (member < UINT_MAX) - return wrapper->putValue(member, value); - - if (wrapper->d()->isNullWrapper) { - if (wrapper && wrapper->d()->readOnly) { - QString error = QLatin1String("Invalid write to global property \"") + name->toQString() + - QLatin1Char('"'); - ScopedString e(scope, v4->newString(error)); - v4->throwError(e); - return false; - } + auto member = wrapper->internalClass()->findValueOrSetter(id); + if (member.index < UINT_MAX) + return wrapper->putValue(member.index, member.attrs, value); - return Object::put(m, name, value); - } - - // Its possible we could delay the calculation of the "actual" context (in the case - // of sub contexts) until it is definately needed. + // It's possible we could delay the calculation of the "actual" context (in the case + // of sub contexts) until it is definitely needed. QQmlContextData *context = wrapper->getContext(); QQmlContextData *expressionContext = context; @@ -262,9 +293,10 @@ bool QQmlContextWrapper::put(Managed *m, String *name, const Value &value) // See QV8ContextWrapper::Getter for resolution order QObject *scopeObject = wrapper->getScopeObject(); + ScopedString name(scope, id.asStringOrSymbol()); while (context) { - const QV4::IdentifierHash<int> &properties = context->propertyNames(); + const QV4::IdentifierHash &properties = context->propertyNames(); // Search context properties if (properties.count() && properties.value(name) != -1) return false; @@ -273,7 +305,7 @@ bool QQmlContextWrapper::put(Managed *m, String *name, const Value &value) if (scopeObject && QV4::QObjectWrapper::setQmlProperty(v4, context, scopeObject, name, QV4::QObjectWrapper::CheckRevision, value)) return true; - scopeObject = 0; + scopeObject = nullptr; // Search context object if (context->contextObject && @@ -285,58 +317,25 @@ bool QQmlContextWrapper::put(Managed *m, String *name, const Value &value) expressionContext->unresolvedNames = true; - if (wrapper->d()->readOnly) { - QString error = QLatin1String("Invalid write to global property \"") + name->toQString() + - QLatin1Char('"'); - v4->throwError(error); - return false; - } - - return Object::put(m, name, value); + QString error = QLatin1String("Invalid write to global property \"") + name->toQString() + + QLatin1Char('"'); + v4->throwError(error); + return false; } void Heap::QmlContext::init(QV4::ExecutionContext *outerContext, QV4::QQmlContextWrapper *qml) { Heap::ExecutionContext::init(Heap::ExecutionContext::Type_QmlContext); outer.set(internalClass->engine, outerContext->d()); - strictMode = false; - callData = outer->callData; - lookups = outer->lookups; - constantTable = outer->constantTable; - compilationUnit = outer->compilationUnit; - this->qml.set(internalClass->engine, qml->d()); -} - -Heap::QmlContext *QmlContext::createWorkerContext(ExecutionContext *parent, const QUrl &source, Value *sendFunction) -{ - Scope scope(parent); - - QQmlContextData *context = new QQmlContextData; - context->baseUrl = source; - context->baseUrlString = source.toString(); - context->isInternal = true; - context->isJSContext = true; - - Scoped<QQmlContextWrapper> qml(scope, scope.engine->memoryManager->allocObject<QQmlContextWrapper>(context, (QObject*)0, true)); - qml->d()->isNullWrapper = true; - - qml->setReadOnly(false); - QV4::ScopedObject api(scope, scope.engine->newObject()); - api->put(QV4::ScopedString(scope, scope.engine->newString(QStringLiteral("sendMessage"))), *sendFunction); - qml->QV4::Object::put(QV4::ScopedString(scope, scope.engine->newString(QStringLiteral("WorkerScript"))), api); - qml->setReadOnly(true); - - Heap::QmlContext *c = scope.engine->memoryManager->alloc<QmlContext>(parent, qml); - Q_ASSERT(c->vtable() == staticVTable()); - return c; + this->activation.set(internalClass->engine, qml->d()); } Heap::QmlContext *QmlContext::create(ExecutionContext *parent, QQmlContextData *context, QObject *scopeObject) { Scope scope(parent); - Scoped<QQmlContextWrapper> qml(scope, scope.engine->memoryManager->allocObject<QQmlContextWrapper>(context, scopeObject)); + Scoped<QQmlContextWrapper> qml(scope, scope.engine->memoryManager->allocate<QQmlContextWrapper>(context, scopeObject)); Heap::QmlContext *c = scope.engine->memoryManager->alloc<QmlContext>(parent, qml); Q_ASSERT(c->vtable() == staticVTable()); return c; diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index 2f1dbabaf2..dd6de3323d 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -66,23 +66,25 @@ struct QQmlContextWrapper; namespace Heap { -struct QQmlContextWrapper : Object { - void init(QQmlContextData *context, QObject *scopeObject, bool ownsContext = false); +#define QQmlContextWrapperMembers(class, Member) \ + Member(class, Pointer, Module *, module) + +DECLARE_HEAP_OBJECT(QQmlContextWrapper, Object) { + DECLARE_MARKOBJECTS(QQmlContextWrapper); + + void init(QQmlContextData *context, QObject *scopeObject); void destroy(); - bool readOnly; - bool ownsContext; - bool isNullWrapper; - QQmlGuardedContextData *context; + QQmlContextDataRef *context; QQmlQPointer<QObject> scopeObject; }; -#define QmlContextMembers(class, Member) \ - Member(class, Pointer, QQmlContextWrapper *, qml) +#define QmlContextMembers(class, Member) DECLARE_HEAP_OBJECT(QmlContext, ExecutionContext) { - DECLARE_MARK_TABLE(QmlContext); + DECLARE_MARKOBJECTS(QmlContext); + QQmlContextWrapper *qml() { return static_cast<QQmlContextWrapper *>(activation.get()); } void init(QV4::ExecutionContext *outerContext, QV4::QQmlContextWrapper *qml); }; @@ -92,36 +94,27 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object { V4_OBJECT2(QQmlContextWrapper, Object) V4_NEEDS_DESTROY - - void takeContextOwnership() { - d()->ownsContext = true; - } + V4_INTERNALCLASS(QmlContextWrapper) inline QObject *getScopeObject() const { return d()->scopeObject; } inline QQmlContextData *getContext() const { return *d()->context; } - void setReadOnly(bool b) { d()->readOnly = b; } - - static ReturnedValue get(const Managed *m, String *name, bool *hasProperty); - static bool put(Managed *m, String *name, const Value &value); + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); }; struct Q_QML_EXPORT QmlContext : public ExecutionContext { V4_MANAGED(QmlContext, ExecutionContext) + V4_INTERNALCLASS(QmlContext) - static Heap::QmlContext *createWorkerContext(QV4::ExecutionContext *parent, const QUrl &source, Value *sendFunction); static Heap::QmlContext *create(QV4::ExecutionContext *parent, QQmlContextData *context, QObject *scopeObject); QObject *qmlScope() const { - return d()->qml->scopeObject; + return d()->qml()->scopeObject; } QQmlContextData *qmlContext() const { - return *d()->qml->context; - } - - void takeContextOwnership() { - d()->qml->ownsContext = true; + return *d()->qml()->context; } }; diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 91650f16e2..15f064ba7a 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -39,7 +39,7 @@ #include "qv4qobjectwrapper_p.h" -#include <private/qqmlpropertycache_p.h> +#include <private/qqmlstaticmetaobject_p.h> #include <private/qqmlengine_p.h> #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlbinding_p.h> @@ -56,12 +56,17 @@ #include <private/qv4functionobject_p.h> #include <private/qv4runtime_p.h> #include <private/qv4variantobject_p.h> + +#if QT_CONFIG(qml_sequence_object) #include <private/qv4sequenceobject_p.h> +#endif + #include <private/qv4objectproto_p.h> #include <private/qv4jsonobject_p.h> #include <private/qv4regexpobject_p.h> #include <private/qv4dateobject_p.h> #include <private/qv4scopedvalue_p.h> +#include <private/qv4jscall_p.h> #include <private/qv4mm_p.h> #include <private/qqmlscriptstring_p.h> #include <private/qv4compileddata_p.h> @@ -98,13 +103,13 @@ QPair<QObject *, int> QObjectMethod::extractQtMethod(const QV4::FunctionObject * return qMakePair(method->object(), method->methodIndex()); } - return qMakePair((QObject *)0, -1); + return qMakePair((QObject *)nullptr, -1); } -static QPair<QObject *, int> extractQtSignal(const Value &value) +static QPair<QObject *, int> extractQtSignal(const QV4::Value &value) { if (value.isObject()) { - QV4::ExecutionEngine *v4 = value.as<Object>()->engine(); + QV4::ExecutionEngine *v4 = value.as<QV4::Object>()->engine(); QV4::Scope scope(v4); QV4::ScopedFunctionObject function(scope, value); if (function) @@ -115,7 +120,7 @@ static QPair<QObject *, int> extractQtSignal(const Value &value) return qMakePair(handler->object(), handler->signalIndex()); } - return qMakePair((QObject *)0, -1); + return qMakePair((QObject *)nullptr, -1); } static QV4::ReturnedValue loadProperty(QV4::ExecutionEngine *v4, QObject *object, @@ -125,7 +130,7 @@ static QV4::ReturnedValue loadProperty(QV4::ExecutionEngine *v4, QObject *object QV4::Scope scope(v4); if (property.isQObject()) { - QObject *rv = 0; + QObject *rv = nullptr; property.readProperty(object, &rv); return QV4::QObjectWrapper::wrap(v4, rv); } else if (property.isQList()) { @@ -180,11 +185,13 @@ static QV4::ReturnedValue loadProperty(QV4::ExecutionEngine *v4, QObject *object if (const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(property.propType())) return QV4::QQmlValueTypeWrapper::create(v4, object, property.coreIndex(), valueTypeMetaObject, property.propType()); } else { +#if QT_CONFIG(qml_sequence_object) // see if it's a sequence type bool succeeded = false; - QV4::ScopedValue retn(scope, QV4::SequencePrototype::newSequence(v4, property.propType(), object, property.coreIndex(), &succeeded)); + QV4::ScopedValue retn(scope, QV4::SequencePrototype::newSequence(v4, property.propType(), object, property.coreIndex(), !property.isWritable(), &succeeded)); if (succeeded) return retn->asReturnedValue(); +#endif } if (property.propType() == QMetaType::UnknownType) { @@ -193,7 +200,7 @@ static QV4::ReturnedValue loadProperty(QV4::ExecutionEngine *v4, QObject *object "'%s::%s'", p.typeName(), object->metaObject()->className(), p.name()); return QV4::Encode::undefined(); } else { - QVariant v(property.propType(), (void *)0); + QVariant v(property.propType(), (void *)nullptr); property.readProperty(object, v.data()); return scope.engine->fromVariant(v); } @@ -216,7 +223,7 @@ QQmlPropertyData *QObjectWrapper::findProperty(ExecutionEngine *engine, QObject Q_UNUSED(revisionMode); QQmlData *ddata = QQmlData::get(o, false); - QQmlPropertyData *result = 0; + QQmlPropertyData *result = nullptr; if (ddata && ddata->propertyCache) result = ddata->propertyCache->property(name, o, qmlContext); else @@ -241,14 +248,14 @@ ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *obje return QV4::QObjectMethod::create(global, object, property->coreIndex()); } else if (property->isSignalHandler()) { QmlSignalHandler::initProto(engine); - return engine->memoryManager->allocObject<QV4::QmlSignalHandler>(object, property->coreIndex())->asReturnedValue(); + return engine->memoryManager->allocate<QV4::QmlSignalHandler>(object, property->coreIndex())->asReturnedValue(); } else { ExecutionContext *global = engine->rootContext(); return QV4::QObjectMethod::create(global, object, property->coreIndex()); } } - QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : 0; + QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : nullptr; if (captureRequired && ep && ep->propertyCapture && !property->isConstant()) ep->propertyCapture->captureProperty(object, property->coreIndex(), property->notifyIndex()); @@ -296,7 +303,7 @@ ReturnedValue QObjectWrapper::getQmlProperty(QQmlContextData *qmlContext, String if (r.isValid()) { if (r.scriptIndex != -1) { return QV4::Encode::undefined(); - } else if (r.type) { + } else if (r.type.isValid()) { return QQmlTypeWrapper::create(v4, d()->object(), r.type, Heap::QQmlTypeWrapper::ExcludeEnums); } else if (r.importNamespace) { @@ -307,7 +314,7 @@ ReturnedValue QObjectWrapper::getQmlProperty(QQmlContextData *qmlContext, String } } } - return QV4::Object::get(this, name, hasProperty); + return QV4::Object::virtualGet(this, name->propertyKey(), this, hasProperty); } QQmlData *ddata = QQmlData::get(d()->object(), false); @@ -334,6 +341,11 @@ ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *obje if (!ddata) return QV4::Encode::undefined(); + if (Q_UNLIKELY(!ddata->propertyCache)) { + ddata->propertyCache = QQmlEnginePrivate::get(engine)->cache(object->metaObject()); + ddata->propertyCache->addref(); + } + QQmlPropertyCache *cache = ddata->propertyCache; Q_ASSERT(cache); QQmlPropertyData *property = cache->property(propertyIndex); @@ -433,7 +445,7 @@ void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, QQmlP return; } - QQmlBinding *newBinding = 0; + QQmlBinding *newBinding = nullptr; QV4::Scope scope(engine); QV4::ScopedFunctionObject f(scope, value); if (f) { @@ -454,9 +466,12 @@ void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, QQmlP QV4::Scoped<QQmlBindingFunction> bindingFunction(scope, (const Value &)f); + QV4::ScopedFunctionObject f(scope, bindingFunction->bindingFunction()); QV4::ScopedContext ctx(scope, bindingFunction->scope()); - newBinding = QQmlBinding::create(property, bindingFunction->function(), object, callingQmlContext, ctx); + newBinding = QQmlBinding::create(property, f->function(), object, callingQmlContext, ctx); newBinding->setSourceLocation(bindingFunction->currentLocation()); + if (f->isBoundFunction()) + newBinding->setBoundFunction(static_cast<QV4::BoundFunction *>(f.getPointer())); newBinding->setTarget(object, *property, nullptr); } } @@ -468,11 +483,11 @@ void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, QQmlP if (auto binding = QQmlPropertyPrivate::binding(object, QQmlPropertyIndex(property->coreIndex()))) { Q_ASSERT(!binding->isValueTypeProxy()); const auto qmlBinding = static_cast<const QQmlBinding*>(binding); - const auto stackFrame = engine->currentStackFrame(); + const auto stackFrame = engine->currentStackFrame; qCInfo(lcBindingRemoval, "Overwriting binding on %s::%s at %s:%d that was initially bound at %s", object->metaObject()->className(), qPrintable(property->name(object)), - qPrintable(stackFrame.source), stackFrame.line, + qPrintable(stackFrame->source()), stackFrame->lineNumber(), qPrintable(qmlBinding->expressionIdentifier())); } } @@ -495,9 +510,9 @@ void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, QQmlP QMetaObject::metacall(object, QMetaObject::WriteProperty, property->coreIndex(), argv); if (value.isNull() && property->isQObject()) { - PROPERTY_STORE(QObject*, 0); + PROPERTY_STORE(QObject*, nullptr); } else if (value.isUndefined() && property->isResettable()) { - void *a[] = { 0 }; + void *a[] = { nullptr }; QMetaObject::metacall(object, QMetaObject::ResetProperty, property->coreIndex(), a); } else if (value.isUndefined() && property->propType() == qMetaTypeId<QVariant>()) { PROPERTY_STORE(QVariant, QVariant()); @@ -530,7 +545,7 @@ void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, QQmlP Q_ASSERT(vmemo); vmemo->setVMEProperty(property->coreIndex(), value); } else if (property->propType() == qMetaTypeId<QQmlScriptString>() && (value.isUndefined() || value.isPrimitive())) { - QQmlScriptString ss(value.toQStringNoThrow(), 0 /* context */, object); + QQmlScriptString ss(value.toQStringNoThrow(), nullptr /* context */, object); if (value.isNumber()) { ss.d->numberValue = value.toNumber(); ss.d->isNumberLiteral = true; @@ -548,7 +563,7 @@ void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, QQmlP QQmlContextData *callingQmlContext = scope.engine->callingQmlContext(); if (!QQmlPropertyPrivate::write(object, *property, v, callingQmlContext)) { - const char *valueType = 0; + const char *valueType = nullptr; if (v.userType() == QVariant::Invalid) valueType = "null"; else valueType = QMetaType::typeName(v.userType()); @@ -589,7 +604,7 @@ ReturnedValue QObjectWrapper::wrap_slowPath(ExecutionEngine *engine, QObject *ob } else { // If this object is tainted, we have to check to see if it is in our // tainted object list - ScopedObject alternateWrapper(scope, (Object *)0); + ScopedObject alternateWrapper(scope, (Object *)nullptr); if (engine->m_multiplyWrappedQObjects && ddata->hasTaintedV4Object) alternateWrapper = engine->m_multiplyWrappedQObjects->value(object); @@ -652,7 +667,7 @@ void QObjectWrapper::setProperty(ExecutionEngine *engine, QObject *object, int p return setProperty(engine, object, property, value); } -bool QObjectWrapper::isEqualTo(Managed *a, Managed *b) +bool QObjectWrapper::virtualIsEqualTo(Managed *a, Managed *b) { Q_ASSERT(a->as<QV4::QObjectWrapper>()); QV4::QObjectWrapper *qobjectWrapper = static_cast<QV4::QObjectWrapper *>(a); @@ -675,66 +690,92 @@ ReturnedValue QObjectWrapper::create(ExecutionEngine *engine, QObject *object) return result; } } - return (engine->memoryManager->allocObject<QV4::QObjectWrapper>(object))->asReturnedValue(); + return (engine->memoryManager->allocate<QV4::QObjectWrapper>(object))->asReturnedValue(); } -QV4::ReturnedValue QObjectWrapper::get(const Managed *m, String *name, bool *hasProperty) +QV4::ReturnedValue QObjectWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { + if (!id.isString()) + return Object::virtualGet(m, id, receiver, hasProperty); + const QObjectWrapper *that = static_cast<const QObjectWrapper*>(m); + Scope scope(that); + ScopedString n(scope, id.asStringOrSymbol()); QQmlContextData *qmlContext = that->engine()->callingQmlContext(); - return that->getQmlProperty(qmlContext, name, IgnoreRevision, hasProperty, /*includeImports*/ true); + return that->getQmlProperty(qmlContext, n, IgnoreRevision, hasProperty, /*includeImports*/ true); } -bool QObjectWrapper::put(Managed *m, String *name, const Value &value) +bool QObjectWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { + if (!id.isString()) + return Object::virtualPut(m, id, value, receiver); + + Scope scope(m); QObjectWrapper *that = static_cast<QObjectWrapper*>(m); - ExecutionEngine *v4 = that->engine(); + ScopedString name(scope, id.asStringOrSymbol()); - if (v4->hasException || QQmlData::wasDeleted(that->d()->object())) + if (scope.engine->hasException || QQmlData::wasDeleted(that->d()->object())) return false; - QQmlContextData *qmlContext = v4->callingQmlContext(); - if (!setQmlProperty(v4, qmlContext, that->d()->object(), name, QV4::QObjectWrapper::IgnoreRevision, value)) { + QQmlContextData *qmlContext = scope.engine->callingQmlContext(); + if (!setQmlProperty(scope.engine, qmlContext, that->d()->object(), name, QV4::QObjectWrapper::IgnoreRevision, value)) { QQmlData *ddata = QQmlData::get(that->d()->object()); // Types created by QML are not extensible at run-time, but for other QObjects we can store them // as regular JavaScript properties, like on JavaScript objects. if (ddata && ddata->context) { QString error = QLatin1String("Cannot assign to non-existent property \"") + name->toQString() + QLatin1Char('\"'); - v4->throwError(error); + scope.engine->throwError(error); return false; } else { - return QV4::Object::put(m, name, value); + return QV4::Object::virtualPut(m, id, value, receiver); } } return true; } -PropertyAttributes QObjectWrapper::query(const Managed *m, String *name) +PropertyAttributes QObjectWrapper::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { - const QObjectWrapper *that = static_cast<const QObjectWrapper*>(m); - ExecutionEngine *engine = that->engine(); - QQmlContextData *qmlContext = engine->callingQmlContext(); - QQmlPropertyData local; - if (that->findProperty(engine, qmlContext, name, IgnoreRevision, &local) - || name->equals(engine->id_destroy()) || name->equals(engine->id_toString())) - return QV4::Attr_Data; - else - return QV4::Object::query(m, name); + if (id.isString()) { + const QObjectWrapper *that = static_cast<const QObjectWrapper*>(m); + const QObject *thatObject = that->d()->object(); + if (!QQmlData::wasDeleted(thatObject)) { + Scope scope(m); + ScopedString n(scope, id.asStringOrSymbol()); + QQmlContextData *qmlContext = scope.engine->callingQmlContext(); + QQmlPropertyData local; + if (that->findProperty(scope.engine, qmlContext, n, IgnoreRevision, &local) + || n->equals(scope.engine->id_destroy()) || n->equals(scope.engine->id_toString())) { + if (p) { + // ### probably not the fastest implementation + bool hasProperty; + p->value = that->getQmlProperty(qmlContext, n, IgnoreRevision, &hasProperty, /*includeImports*/ true); + } + return QV4::Attr_Data; + } + } + } + + return QV4::Object::virtualGetOwnProperty(m, id, p); } -void QObjectWrapper::advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attributes) +struct QObjectWrapperOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator +{ + int propertyIndex = 0; + ~QObjectWrapperOwnPropertyKeyIterator() override = default; + PropertyKey next(const QV4::Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; + +}; + +PropertyKey QObjectWrapperOwnPropertyKeyIterator::next(const QV4::Object *o, Property *pd, PropertyAttributes *attrs) { // Used to block access to QObject::destroyed() and QObject::deleteLater() from QML static const int destroyedIdx1 = QObject::staticMetaObject.indexOfSignal("destroyed(QObject*)"); static const int destroyedIdx2 = QObject::staticMetaObject.indexOfSignal("destroyed()"); static const int deleteLaterIdx = QObject::staticMetaObject.indexOfSlot("deleteLater()"); - name->setM(0); - *index = UINT_MAX; - - QObjectWrapper *that = static_cast<QObjectWrapper*>(m); + const QObjectWrapper *that = static_cast<const QObjectWrapper*>(o); QObject *thatObject = that->d()->object(); if (thatObject && !QQmlData::wasDeleted(thatObject)) { @@ -742,40 +783,50 @@ void QObjectWrapper::advanceIterator(Managed *m, ObjectIterator *it, Value *name // These indices don't apply to gadgets, so don't block them. const bool preventDestruction = mo->superClass() || mo == &QObject::staticMetaObject; const int propertyCount = mo->propertyCount(); - if (it->arrayIndex < static_cast<uint>(propertyCount)) { + if (propertyIndex < propertyCount) { ExecutionEngine *thatEngine = that->engine(); Scope scope(thatEngine); - const QMetaProperty property = mo->property(it->arrayIndex); + const QMetaProperty property = mo->property(propertyIndex); ScopedString propName(scope, thatEngine->newString(QString::fromUtf8(property.name()))); - name->setM(propName->d()); - ++it->arrayIndex; - *attributes = QV4::Attr_Data; - - QQmlPropertyData local; - local.load(property); - p->value = that->getProperty(thatEngine, thatObject, &local); - return; + ++propertyIndex; + if (attrs) + *attrs= QV4::Attr_Data; + if (pd) { + QQmlPropertyData local; + local.load(property); + pd->value = that->getProperty(thatEngine, thatObject, &local); + } + return propName->toPropertyKey(); } const int methodCount = mo->methodCount(); - while (it->arrayIndex < static_cast<uint>(propertyCount + methodCount)) { - const int index = it->arrayIndex - propertyCount; + while (propertyIndex < propertyCount + methodCount) { + Q_ASSERT(propertyIndex >= propertyCount); + int index = propertyIndex - propertyCount; const QMetaMethod method = mo->method(index); - ++it->arrayIndex; + ++propertyIndex; if (method.access() == QMetaMethod::Private || (preventDestruction && (index == deleteLaterIdx || index == destroyedIdx1 || index == destroyedIdx2))) continue; ExecutionEngine *thatEngine = that->engine(); Scope scope(thatEngine); ScopedString methodName(scope, thatEngine->newString(QString::fromUtf8(method.name()))); - name->setM(methodName->d()); - *attributes = QV4::Attr_Data; - - QQmlPropertyData local; - local.load(method); - p->value = that->getProperty(thatEngine, thatObject, &local); - return; + if (attrs) + *attrs = QV4::Attr_Data; + if (pd) { + QQmlPropertyData local; + local.load(method); + pd->value = that->getProperty(thatEngine, thatObject, &local); + } + return methodName->toPropertyKey(); } } - QV4::Object::advanceIterator(m, it, name, index, p, attributes); + + return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); +} + +OwnPropertyKeyIterator *QObjectWrapper::virtualOwnPropertyKeys(const Object *m, Value *target) +{ + *target = *m; + return new QObjectWrapperOwnPropertyKeyIterator; } namespace QV4 { @@ -808,25 +859,25 @@ struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase break; QQmlMetaObject::ArgTypeStorage storage; - int *argsTypes = QQmlMetaObject(r).methodParameterTypes(This->signalIndex, &storage, 0); + int *argsTypes = QQmlMetaObject(r).methodParameterTypes(This->signalIndex, &storage, nullptr); int argCount = argsTypes ? argsTypes[0]:0; QV4::Scope scope(v4); QV4::ScopedFunctionObject f(scope, This->function.value()); - QV4::ScopedCallData callData(scope, argCount); - callData->thisObject = This->thisObject.isUndefined() ? v4->globalObject->asReturnedValue() : This->thisObject.value(); + QV4::JSCallData jsCallData(scope, argCount); + *jsCallData->thisObject = This->thisObject.isUndefined() ? v4->globalObject->asReturnedValue() : This->thisObject.value(); for (int ii = 0; ii < argCount; ++ii) { int type = argsTypes[ii + 1]; if (type == qMetaTypeId<QVariant>()) { - callData->args[ii] = v4->fromVariant(*((QVariant *)metaArgs[ii + 1])); + jsCallData->args[ii] = v4->fromVariant(*((QVariant *)metaArgs[ii + 1])); } else { - callData->args[ii] = v4->fromVariant(QVariant(type, metaArgs[ii + 1])); + jsCallData->args[ii] = v4->fromVariant(QVariant(type, metaArgs[ii + 1])); } } - f->call(scope, callData); + f->call(jsCallData); if (scope.hasException()) { QQmlError error = v4->catchExceptionAsQmlError(); if (error.description().isEmpty()) { @@ -837,7 +888,7 @@ struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase QQmlEnginePrivate::get(qmlEngine)->warning(error); } else { QMessageLogger(error.url().toString().toLatin1().constData(), - error.line(), 0).warning().noquote() + error.line(), nullptr).warning().noquote() << error.toString(); } } @@ -899,12 +950,14 @@ struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase } // namespace QV4 -void QObjectWrapper::method_connect(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue QObjectWrapper::method_connect(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - if (callData->argc == 0) + QV4::Scope scope(b); + + if (argc == 0) THROW_GENERIC_ERROR("Function.prototype.connect: no arguments given"); - QPair<QObject *, int> signalInfo = extractQtSignal(callData->thisObject); + QPair<QObject *, int> signalInfo = extractQtSignal(*thisObject); QObject *signalObject = signalInfo.first; int signalIndex = signalInfo.second; // in method range, not signal range! @@ -918,25 +971,25 @@ void QObjectWrapper::method_connect(const BuiltinFunction *, Scope &scope, CallD THROW_GENERIC_ERROR("Function.prototype.connect: this object is not a signal"); QV4::ScopedFunctionObject f(scope); - QV4::ScopedValue thisObject (scope, QV4::Encode::undefined()); + QV4::ScopedValue object (scope, QV4::Encode::undefined()); - if (callData->argc == 1) { - f = callData->args[0]; - } else if (callData->argc >= 2) { - thisObject = callData->args[0]; - f = callData->args[1]; + if (argc == 1) { + f = argv[0]; + } else if (argc >= 2) { + object = argv[0]; + f = argv[1]; } if (!f) THROW_GENERIC_ERROR("Function.prototype.connect: target is not a function"); - if (!thisObject->isUndefined() && !thisObject->isObject()) + if (!object->isUndefined() && !object->isObject()) THROW_GENERIC_ERROR("Function.prototype.connect: target this is not an object"); QV4::QObjectSlotDispatcher *slot = new QV4::QObjectSlotDispatcher; slot->signalIndex = signalIndex; - slot->thisObject.set(scope.engine, thisObject); + slot->thisObject.set(scope.engine, object); slot->function.set(scope.engine, f); if (QQmlData *ddata = QQmlData::get(signalObject)) { @@ -949,12 +1002,14 @@ void QObjectWrapper::method_connect(const BuiltinFunction *, Scope &scope, CallD RETURN_UNDEFINED(); } -void QObjectWrapper::method_disconnect(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue QObjectWrapper::method_disconnect(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - if (callData->argc == 0) + QV4::Scope scope(b); + + if (argc == 0) THROW_GENERIC_ERROR("Function.prototype.disconnect: no arguments given"); - QPair<QObject *, int> signalInfo = extractQtSignal(callData->thisObject); + QPair<QObject *, int> signalInfo = extractQtSignal(*thisObject); QObject *signalObject = signalInfo.first; int signalIndex = signalInfo.second; @@ -970,11 +1025,11 @@ void QObjectWrapper::method_disconnect(const BuiltinFunction *, Scope &scope, Ca QV4::ScopedFunctionObject functionValue(scope); QV4::ScopedValue functionThisValue(scope, QV4::Encode::undefined()); - if (callData->argc == 1) { - functionValue = callData->args[0]; - } else if (callData->argc >= 2) { - functionThisValue = callData->args[0]; - functionValue = callData->args[1]; + if (argc == 1) { + functionValue = argv[0]; + } else if (argc >= 2) { + functionThisValue = argv[0]; + functionValue = argv[1]; } if (!functionValue) @@ -1010,9 +1065,9 @@ static void markChildQObjectsRecursively(QObject *parent, QV4::MarkStack *markSt } } -void QObjectWrapper::markObjects(Heap::Base *that, QV4::MarkStack *markStack) +void Heap::QObjectWrapper::markObjects(Heap::Base *that, QV4::MarkStack *markStack) { - QObjectWrapper::Data *This = static_cast<QObjectWrapper::Data *>(that); + QObjectWrapper *This = static_cast<QObjectWrapper *>(that); if (QObject *o = This->object()) { QQmlVMEMetaObject *vme = QQmlVMEMetaObject::get(o); @@ -1027,7 +1082,7 @@ void QObjectWrapper::markObjects(Heap::Base *that, QV4::MarkStack *markStack) markChildQObjectsRecursively(o, markStack); } - QV4::Object::markObjects(that, markStack); + Object::markObjects(that, markStack); } void QObjectWrapper::destroyObject(bool lastCall) @@ -1040,8 +1095,12 @@ void QObjectWrapper::destroyObject(bool lastCall) QQmlData *ddata = QQmlData::get(h->object(), false); if (ddata) { if (!h->object()->parent() && !ddata->indestructible) { - if (ddata && ddata->ownContext && ddata->context) - ddata->context->emitDestruction(); + if (ddata && ddata->ownContext) { + Q_ASSERT(ddata->ownContext == ddata->context); + ddata->ownContext->emitDestruction(); + ddata->ownContext = nullptr; + ddata->context = nullptr; + } // This object is notionally destroyed now ddata->isQueuedForDeletion = true; if (lastCall) @@ -1085,7 +1144,7 @@ struct CallArgument { inline void *dataPtr(); inline void initAsType(int type); - inline void fromValue(int type, ExecutionEngine *, const Value &); + inline bool fromValue(int type, ExecutionEngine *, const QV4::Value &); inline ReturnedValue toValue(ExecutionEngine *); private: @@ -1145,8 +1204,24 @@ static QV4::ReturnedValue CallMethod(const QQmlObjectOrGadget &object, int index // Convert all arguments. QVarLengthArray<CallArgument, 9> args(argCount + 1); args[0].initAsType(returnType); - for (int ii = 0; ii < argCount; ++ii) - args[ii + 1].fromValue(argTypes[ii], engine, callArgs->args[ii]); + for (int ii = 0; ii < argCount; ++ii) { + if (!args[ii + 1].fromValue(argTypes[ii], engine, callArgs->args[ii])) { + qWarning() << QString::fromLatin1("Could not convert argument %1 at").arg(ii); + const StackTrace stack = engine->stackTrace(); + for (const StackFrame &frame : stack) { + qWarning() << "\t" << frame.function + QLatin1Char('@') + frame.source + + (frame.line > 0 + ? (QLatin1Char(':') + QString::number(frame.line)) + : QString()); + + } + qWarning() << QLatin1String("Passing incompatible arguments to C++ functions from " + "JavaScript is dangerous and deprecated."); + qWarning() << QLatin1String("This will throw a JavaScript TypeError in future " + "releases of Qt!"); + + } + } QVarLengthArray<void *, 9> argData(args.count()); for (int ii = 0; ii < args.count(); ++ii) argData[ii] = args[ii].dataPtr(); @@ -1168,14 +1243,14 @@ static QV4::ReturnedValue CallMethod(const QQmlObjectOrGadget &object, int index } else { - void *args[] = { 0 }; + void *args[] = { nullptr }; object.metacall(callType, index, args); return Encode::undefined(); } } -/*! +/* Returns the match score for converting \a actual to be of type \a conversionType. A zero score means "perfect match" whereas a higher score is worse. @@ -1242,6 +1317,9 @@ static int MatchScore(const QV4::Value &actual, int conversionType) } else if (actual.as<QV4::RegExpObject>()) { switch (conversionType) { case QMetaType::QRegExp: +#if QT_CONFIG(regularexpression) + case QMetaType::QRegularExpression: +#endif return 0; default: return 10; @@ -1283,7 +1361,7 @@ static int MatchScore(const QV4::Value &actual, int conversionType) return 10; } } - } else if (const Object *obj = actual.as<Object>()) { + } else if (const QV4::Object *obj = actual.as<QV4::Object>()) { if (obj->as<QV4::VariantObject>()) { if (conversionType == qMetaTypeId<QVariant>()) return 0; @@ -1332,7 +1410,7 @@ static inline int QMetaObject_methods(const QMetaObject *metaObject) return reinterpret_cast<const Private *>(metaObject->d.data)->methodCount; } -/*! +/* Returns the next related method, if one, or 0. */ static const QQmlPropertyData * RelatedMethod(const QQmlObjectOrGadget &object, @@ -1341,7 +1419,7 @@ static const QQmlPropertyData * RelatedMethod(const QQmlObjectOrGadget &object, const QQmlPropertyCache *propertyCache) { if (!current->isOverload()) - return 0; + return nullptr; Q_ASSERT(!current->overrideIndexIsProperty()); @@ -1359,7 +1437,7 @@ static const QQmlPropertyData * RelatedMethod(const QQmlObjectOrGadget &object, // If we've been called before with the same override index, then // we can't go any further... if (&dummy == current && dummy.coreIndex() == current->overrideIndex()) - return 0; + return nullptr; QMetaMethod method = mo->method(current->overrideIndex()); dummy.load(method); @@ -1394,7 +1472,7 @@ static QV4::ReturnedValue CallPrecise(const QQmlObjectOrGadget &object, const QQ if (data.hasArguments()) { - int *args = 0; + int *args = nullptr; QQmlMetaObject::ArgTypeStorage storage; if (data.isConstructor()) @@ -1408,7 +1486,7 @@ static QV4::ReturnedValue CallPrecise(const QQmlObjectOrGadget &object, const QQ + QLatin1String(unknownTypeError)); } - if (args[0] > callArgs->argc) { + if (args[0] > callArgs->argc()) { QString error = QLatin1String("Insufficient arguments"); return engine->throwError(error); } @@ -1417,12 +1495,12 @@ static QV4::ReturnedValue CallPrecise(const QQmlObjectOrGadget &object, const QQ } else { - return CallMethod(object, data.coreIndex(), returnType, 0, 0, engine, callArgs, callType); + return CallMethod(object, data.coreIndex(), returnType, 0, nullptr, engine, callArgs, callType); } } -/*! +/* Resolve the overloaded method to call. The algorithm works conceptually like this: 1. Resolve the set of overloads it is *possible* to call. Impossible overloads include those that have too many parameters or have parameters @@ -1439,7 +1517,7 @@ static QV4::ReturnedValue CallOverloaded(const QQmlObjectOrGadget &object, const QV4::ExecutionEngine *engine, QV4::CallData *callArgs, const QQmlPropertyCache *propertyCache, QMetaObject::Call callType = QMetaObject::InvokeMetaMethod) { - int argumentCount = callArgs->argc; + int argumentCount = callArgs->argc(); QQmlPropertyData best; int bestParameterScore = INT_MAX; @@ -1454,9 +1532,9 @@ static QV4::ReturnedValue CallOverloaded(const QQmlObjectOrGadget &object, const do { QQmlMetaObject::ArgTypeStorage storage; int methodArgumentCount = 0; - int *methodArgTypes = 0; + int *methodArgTypes = nullptr; if (attempt->hasArguments()) { - int *args = object.methodParameterTypes(attempt->coreIndex(), &storage, 0); + int *args = object.methodParameterTypes(attempt->coreIndex(), &storage, nullptr); if (!args) // Must be an unknown argument continue; @@ -1484,7 +1562,7 @@ static QV4::ReturnedValue CallOverloaded(const QQmlObjectOrGadget &object, const if (bestParameterScore == 0 && bestMatchScore == 0) break; // We can't get better than that - } while ((attempt = RelatedMethod(object, attempt, dummy, propertyCache)) != 0); + } while ((attempt = RelatedMethod(object, attempt, dummy, propertyCache)) != nullptr); if (best.isValid()) { return CallPrecise(object, best, engine, callArgs, callType); @@ -1551,7 +1629,7 @@ void *CallArgument::dataPtr() return stdVectorQModelIndexPtr; else if (type != 0) return (void *)&allocData; - return 0; + return nullptr; } void CallArgument::initAsType(int callType) @@ -1569,7 +1647,7 @@ void CallArgument::initAsType(int callType) callType == QMetaType::Float) { type = callType; } else if (callType == QMetaType::QObjectStar) { - qobjectPtr = 0; + qobjectPtr = nullptr; type = callType; } else if (callType == QMetaType::QString) { qstringPtr = new (&allocData) QString(); @@ -1594,10 +1672,11 @@ void CallArgument::initAsType(int callType) jsonValuePtr = new (&allocData) QJsonValue(); } else { type = -1; - qvariantPtr = new (&allocData) QVariant(callType, (void *)0); + qvariantPtr = new (&allocData) QVariant(callType, (void *)nullptr); } } +#if QT_CONFIG(qml_sequence_object) template <class T, class M> void CallArgument::fromContainerValue(const QV4::Object *object, int callType, M CallArgument::*member, bool &queryEngine) { @@ -1610,8 +1689,9 @@ void CallArgument::fromContainerValue(const QV4::Object *object, int callType, M } } } +#endif -void CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const QV4::Value &value) +bool CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const QV4::Value &value) { if (type != 0) { cleanup(); @@ -1646,36 +1726,41 @@ void CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const Q qstringPtr = new (&allocData) QString(value.toQStringNoThrow()); type = callType; } else if (callType == QMetaType::QObjectStar) { - qobjectPtr = 0; + qobjectPtr = nullptr; + type = callType; if (const QV4::QObjectWrapper *qobjectWrapper = value.as<QV4::QObjectWrapper>()) qobjectPtr = qobjectWrapper->object(); else if (const QV4::QQmlTypeWrapper *qmlTypeWrapper = value.as<QV4::QQmlTypeWrapper>()) queryEngine = qmlTypeWrapper->isSingleton(); - type = callType; + else if (!value.isNull() && !value.isUndefined()) // null and undefined are nullptr + return false; } else if (callType == qMetaTypeId<QVariant>()) { qvariantPtr = new (&allocData) QVariant(scope.engine->toVariant(value, -1)); type = callType; } else if (callType == qMetaTypeId<QList<QObject*> >()) { qlistPtr = new (&allocData) QList<QObject *>(); + type = callType; QV4::ScopedArrayObject array(scope, value); if (array) { Scoped<QV4::QObjectWrapper> qobjectWrapper(scope); uint length = array->getLength(); for (uint ii = 0; ii < length; ++ii) { - QObject *o = 0; - qobjectWrapper = array->getIndexed(ii); + QObject *o = nullptr; + qobjectWrapper = array->get(ii); if (!!qobjectWrapper) o = qobjectWrapper->object(); qlistPtr->append(o); } } else { - QObject *o = 0; - if (const QV4::QObjectWrapper *qobjectWrapper = value.as<QV4::QObjectWrapper>()) - o = qobjectWrapper->object(); - qlistPtr->append(o); + if (const QV4::QObjectWrapper *qobjectWrapper = value.as<QV4::QObjectWrapper>()) { + qlistPtr->append(qobjectWrapper->object()); + } else { + qlistPtr->append(nullptr); + if (!value.isNull() && !value.isUndefined()) + return false; + } } - type = callType; } else if (callType == qMetaTypeId<QQmlV4Handle>()) { handlePtr = new (&allocData) QQmlV4Handle(value.asReturnedValue()); type = callType; @@ -1692,6 +1777,7 @@ void CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const Q type = callType; } else if (callType == QMetaType::Void) { *qvariantPtr = QVariant(); +#if QT_CONFIG(qml_sequence_object) } else if (callType == qMetaTypeId<std::vector<int>>() || callType == qMetaTypeId<std::vector<qreal>>() || callType == qMetaTypeId<std::vector<bool>>() @@ -1699,7 +1785,7 @@ void CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const Q || callType == qMetaTypeId<std::vector<QUrl>>() || callType == qMetaTypeId<std::vector<QModelIndex>>()) { queryEngine = true; - const QV4::Object* object = value.as<Object>(); + const QV4::Object* object = value.as<QV4::Object>(); if (callType == qMetaTypeId<std::vector<int>>()) { stdVectorIntPtr = nullptr; fromContainerValue<std::vector<int>>(object, callType, &CallArgument::stdVectorIntPtr, queryEngine); @@ -1719,6 +1805,16 @@ void CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const Q stdVectorQModelIndexPtr = nullptr; fromContainerValue<std::vector<QModelIndex>>(object, callType, &CallArgument::stdVectorQModelIndexPtr, queryEngine); } +#endif + } else if (QMetaType::typeFlags(callType) + & (QMetaType::PointerToQObject | QMetaType::PointerToGadget)) { + // You can assign null or undefined to any pointer. The result is a nullptr. + if (value.isNull() || value.isUndefined()) { + qvariantPtr = new (&allocData) QVariant(callType, nullptr); + type = callType; + } else { + queryEngine = true; + } } else { queryEngine = true; } @@ -1727,7 +1823,7 @@ void CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const Q qvariantPtr = new (&allocData) QVariant(); type = -1; - QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : 0; + QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : nullptr; QVariant v = scope.engine->toVariant(value, callType); if (v.userType() == callType) { @@ -1740,15 +1836,20 @@ void CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const Q if (!mo.isNull()) { QObject *obj = ep->toQObject(v); - if (obj != 0 && !QQmlMetaObject::canConvert(obj, mo)) - obj = 0; + if (obj != nullptr && !QQmlMetaObject::canConvert(obj, mo)) { + *qvariantPtr = QVariant(callType, nullptr); + return false; + } *qvariantPtr = QVariant(callType, &obj); - } else { - *qvariantPtr = QVariant(callType, (void *)0); + return true; } + + *qvariantPtr = QVariant(callType, (void *)nullptr); + return false; } } + return true; } QV4::ReturnedValue CallArgument::toValue(QV4::ExecutionEngine *engine) @@ -1812,7 +1913,7 @@ QV4::ReturnedValue CallArgument::toValue(QV4::ExecutionEngine *engine) ReturnedValue QObjectMethod::create(ExecutionContext *scope, QObject *object, int index) { Scope valueScope(scope); - Scoped<QObjectMethod> method(valueScope, valueScope.engine->memoryManager->allocObject<QObjectMethod>(scope)); + Scoped<QObjectMethod> method(valueScope, valueScope.engine->memoryManager->allocate<QObjectMethod>(scope)); method->d()->setObject(object); if (QQmlData *ddata = QQmlData::get(object)) @@ -1825,7 +1926,7 @@ ReturnedValue QObjectMethod::create(ExecutionContext *scope, QObject *object, in ReturnedValue QObjectMethod::create(ExecutionContext *scope, const QQmlValueTypeWrapper *valueType, int index) { Scope valueScope(scope); - Scoped<QObjectMethod> method(valueScope, valueScope.engine->memoryManager->allocObject<QObjectMethod>(scope)); + Scoped<QObjectMethod> method(valueScope, valueScope.engine->memoryManager->allocate<QObjectMethod>(scope)); method->d()->setPropertyCache(valueType->d()->propertyCache()); method->d()->index = index; method->d()->valueTypeWrapper.set(valueScope.engine, valueType->d()); @@ -1844,7 +1945,7 @@ const QMetaObject *Heap::QObjectMethod::metaObject() return object()->metaObject(); } -QV4::ReturnedValue QObjectMethod::method_toString(QV4::ExecutionContext *ctx) const +QV4::ReturnedValue QObjectMethod::method_toString(QV4::ExecutionEngine *engine) const { QString result; if (const QMetaObject *metaObject = d()->metaObject()) { @@ -1863,15 +1964,15 @@ QV4::ReturnedValue QObjectMethod::method_toString(QV4::ExecutionContext *ctx) co result = QLatin1String("null"); } - return ctx->engine()->newString(result)->asReturnedValue(); + return engine->newString(result)->asReturnedValue(); } -QV4::ReturnedValue QObjectMethod::method_destroy(QV4::ExecutionContext *ctx, const Value *args, int argc) const +QV4::ReturnedValue QObjectMethod::method_destroy(QV4::ExecutionEngine *engine, const Value *args, int argc) const { if (!d()->object()) return Encode::undefined(); if (QQmlData::keepAliveDuringGarbageCollection(d()->object())) - return ctx->engine()->throwError(QStringLiteral("Invalid attempt to destroy() an indestructible object")); + return engine->throwError(QStringLiteral("Invalid attempt to destroy() an indestructible object")); int delay = 0; if (argc > 0) @@ -1885,32 +1986,24 @@ QV4::ReturnedValue QObjectMethod::method_destroy(QV4::ExecutionContext *ctx, con return Encode::undefined(); } -void QObjectMethod::call(const Managed *m, Scope &scope, CallData *callData) +ReturnedValue QObjectMethod::virtualCall(const FunctionObject *m, const Value *thisObject, const Value *argv, int argc) { const QObjectMethod *This = static_cast<const QObjectMethod*>(m); - This->callInternal(callData, scope); + return This->callInternal(thisObject, argv, argc); } -void QObjectMethod::callInternal(CallData *callData, Scope &scope) const +ReturnedValue QObjectMethod::callInternal(const Value *thisObject, const Value *argv, int argc) const { ExecutionEngine *v4 = engine(); - ExecutionContext *context = v4->currentContext; - if (d()->index == DestroyMethod) { - scope.result = method_destroy(context, callData->args, callData->argc); - return; - } - - else if (d()->index == ToStringMethod) { - scope.result = method_toString(context); - return; - } + if (d()->index == DestroyMethod) + return method_destroy(v4, argv, argc); + else if (d()->index == ToStringMethod) + return method_toString(v4); QQmlObjectOrGadget object(d()->object()); if (!d()->object()) { - if (!d()->valueTypeWrapper) { - scope.result = Encode::undefined(); - return; - } + if (!d()->valueTypeWrapper) + return Encode::undefined(); object = QQmlObjectOrGadget(d()->propertyCache(), d()->valueTypeWrapper->gadgetPtr); } @@ -1919,20 +2012,16 @@ void QObjectMethod::callInternal(CallData *callData, Scope &scope) const if (d()->propertyCache()) { QQmlPropertyData *data = d()->propertyCache()->method(d()->index); - if (!data) { - scope.result = QV4::Encode::undefined(); - return; - } + if (!data) + return QV4::Encode::undefined(); method = *data; } else { const QMetaObject *mo = d()->object()->metaObject(); const QMetaMethod moMethod = mo->method(d()->index); method.load(moMethod); - if (method.coreIndex() == -1) { - scope.result = QV4::Encode::undefined(); - return; - } + if (method.coreIndex() == -1) + return QV4::Encode::undefined(); // Look for overloaded methods QByteArray methodName = moMethod.name(); @@ -1947,21 +2036,25 @@ void QObjectMethod::callInternal(CallData *callData, Scope &scope) const } } + Scope scope(v4); + JSCallData cData(scope, argc, argv, thisObject); + CallData *callData = cData.callData(); + if (method.isV4Function()) { - scope.result = QV4::Encode::undefined(); - QQmlV4Function func(callData, &scope.result, v4); + QV4::ScopedValue rv(scope, QV4::Value::undefinedValue()); + QQmlV4Function func(callData, rv, v4); QQmlV4Function *funcptr = &func; - void *args[] = { 0, &funcptr }; + void *args[] = { nullptr, &funcptr }; object.metacall(QMetaObject::InvokeMetaMethod, method.coreIndex(), args); - return; + return rv->asReturnedValue(); } if (!method.isOverload()) { - scope.result = CallPrecise(object, method, v4, callData); + return CallPrecise(object, method, v4, callData); } else { - scope.result = CallOverloaded(object, method, v4, callData, d()->propertyCache()); + return CallOverloaded(object, method, v4, callData, d()->propertyCache()); } } @@ -2006,7 +2099,7 @@ void Heap::QMetaObjectWrapper::ensureConstructorsCache() { ReturnedValue QMetaObjectWrapper::create(ExecutionEngine *engine, const QMetaObject* metaObject) { QV4::Scope scope(engine); - Scoped<QMetaObjectWrapper> mo(scope, engine->memoryManager->allocObject<QV4::QMetaObjectWrapper>(metaObject)->asReturnedValue()); + Scoped<QMetaObjectWrapper> mo(scope, engine->memoryManager->allocate<QV4::QMetaObjectWrapper>(metaObject)->asReturnedValue()); mo->init(engine); return mo->asReturnedValue(); } @@ -2019,18 +2112,19 @@ void QMetaObjectWrapper::init(ExecutionEngine *) { for (int k = 0; k < Enum.keyCount(); k++) { const char* key = Enum.key(k); const int value = Enum.value(k); - defineReadonlyProperty(QLatin1String(key), Primitive::fromInt32(value)); + defineReadonlyProperty(QLatin1String(key), Value::fromInt32(value)); } } } -void QMetaObjectWrapper::construct(const Managed *m, Scope &scope, CallData *callData) +ReturnedValue QMetaObjectWrapper::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *) { - const QMetaObjectWrapper *This = static_cast<const QMetaObjectWrapper*>(m); - scope.result = This->constructInternal(callData); + const QMetaObjectWrapper *This = static_cast<const QMetaObjectWrapper*>(f); + return This->constructInternal(argv, argc); } -ReturnedValue QMetaObjectWrapper::constructInternal(CallData * callData) const { +ReturnedValue QMetaObjectWrapper::constructInternal(const Value *argv, int argc) const +{ d()->ensureConstructorsCache(); @@ -2043,6 +2137,8 @@ ReturnedValue QMetaObjectWrapper::constructInternal(CallData * callData) const { Scope scope(v4); Scoped<QObjectWrapper> object(scope); + JSCallData cData(scope, argc, argv); + CallData *callData = cData.callData(); if (d()->constructorCount == 1) { object = callConstructor(d()->constructors[0], v4, callData); @@ -2052,7 +2148,7 @@ ReturnedValue QMetaObjectWrapper::constructInternal(CallData * callData) const { } Scoped<QMetaObjectWrapper> metaObject(scope, this); object->defineDefaultProperty(v4->id_constructor(), metaObject); - object->setPrototype(const_cast<QMetaObjectWrapper*>(this)); + object->setPrototypeOf(const_cast<QMetaObjectWrapper*>(this)); return object.asReturnedValue(); } @@ -2067,7 +2163,7 @@ ReturnedValue QMetaObjectWrapper::callConstructor(const QQmlPropertyData &data, ReturnedValue QMetaObjectWrapper::callOverloadedConstructor(QV4::ExecutionEngine *engine, QV4::CallData *callArgs) const { const int numberOfConstructors = d()->constructorCount; - const int argumentCount = callArgs->argc; + const int argumentCount = callArgs->argc(); const QQmlStaticMetaObject object(d()->metaObject); QQmlPropertyData best; @@ -2079,11 +2175,11 @@ ReturnedValue QMetaObjectWrapper::callOverloadedConstructor(QV4::ExecutionEngine for (int i = 0; i < numberOfConstructors; i++) { const QQmlPropertyData & attempt = d()->constructors[i]; + QQmlMetaObject::ArgTypeStorage storage; int methodArgumentCount = 0; - int *methodArgTypes = 0; + int *methodArgTypes = nullptr; if (attempt.hasArguments()) { - QQmlMetaObject::ArgTypeStorage storage; - int *args = object.constructorParameterTypes(attempt.coreIndex(), &storage, 0); + int *args = object.constructorParameterTypes(attempt.coreIndex(), &storage, nullptr); if (!args) // Must be an unknown argument continue; @@ -2127,7 +2223,7 @@ ReturnedValue QMetaObjectWrapper::callOverloadedConstructor(QV4::ExecutionEngine } } -bool QMetaObjectWrapper::isEqualTo(Managed *a, Managed *b) +bool QMetaObjectWrapper::virtualIsEqualTo(Managed *a, Managed *b) { Q_ASSERT(a->as<QMetaObjectWrapper>()); QMetaObjectWrapper *aMetaObject = a->as<QMetaObjectWrapper>(); @@ -2168,9 +2264,7 @@ void QmlSignalHandler::initProto(ExecutionEngine *engine) void MultiplyWrappedQObjectMap::insert(QObject *key, Heap::Object *value) { - QV4::WeakValue v; - v.set(value->internalClass->engine, value); - QHash<QObject*, QV4::WeakValue>::insert(key, v); + QHash<QObject*, QV4::WeakValue>::operator[](key).set(value->internalClass->engine, value); connect(key, SIGNAL(destroyed(QObject*)), this, SLOT(removeDestroyedObject(QObject*))); } diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index 018e444f7c..43a53ac673 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -55,9 +55,7 @@ #include <QtCore/qmetatype.h> #include <QtCore/qpair.h> #include <QtCore/qhash.h> -#include <private/qhashedstring_p.h> #include <private/qqmldata_p.h> -#include <private/qqmlpropertycache_p.h> #include <private/qintrusivelist_p.h> #include <private/qv4value_p.h> @@ -90,6 +88,7 @@ struct Q_QML_EXPORT QObjectWrapper : Object { } QObject *object() const { return qObj.data(); } + static void markObjects(Heap::Base *that, MarkStack *markStack); private: QQmlQPointer<QObject> qObj; @@ -102,7 +101,7 @@ private: Member(class, NoMark, int, index) DECLARE_HEAP_OBJECT(QObjectMethod, FunctionObject) { - DECLARE_MARK_TABLE(QObjectMethod); + DECLARE_MARKOBJECTS(QObjectMethod); void init(QV4::ExecutionContext *scope); void destroy() @@ -165,8 +164,8 @@ struct Q_QML_EXPORT QObjectWrapper : public Object QObject *object() const { return d()->object(); } - ReturnedValue getQmlProperty(QQmlContextData *qmlContext, String *name, RevisionMode revisionMode, bool *hasProperty = 0, bool includeImports = false) const; - static ReturnedValue getQmlProperty(ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, RevisionMode revisionMode, bool *hasProperty = 0); + ReturnedValue getQmlProperty(QQmlContextData *qmlContext, String *name, RevisionMode revisionMode, bool *hasProperty = nullptr, bool includeImports = false) const; + static ReturnedValue getQmlProperty(ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, RevisionMode revisionMode, bool *hasProperty = nullptr); static bool setQmlProperty(ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, RevisionMode revisionMode, const Value &value); @@ -181,25 +180,23 @@ struct Q_QML_EXPORT QObjectWrapper : public Object void destroyObject(bool lastCall); -protected: - static bool isEqualTo(Managed *that, Managed *o); - static ReturnedValue getProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property, bool captureRequired = true); +protected: static void setProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property, const Value &value); + static bool virtualIsEqualTo(Managed *that, Managed *o); static ReturnedValue create(ExecutionEngine *engine, QObject *object); static QQmlPropertyData *findProperty(ExecutionEngine *engine, QObject *o, QQmlContextData *qmlContext, String *name, RevisionMode revisionMode, QQmlPropertyData *local); QQmlPropertyData *findProperty(ExecutionEngine *engine, QQmlContextData *qmlContext, String *name, RevisionMode revisionMode, QQmlPropertyData *local) const; - static ReturnedValue get(const Managed *m, String *name, bool *hasProperty); - static bool put(Managed *m, String *name, const Value &value); - static PropertyAttributes query(const Managed *, String *name); - static void advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attributes); - static void markObjects(Heap::Base *that, QV4::MarkStack *markStack); + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); + static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); + static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); - static void method_connect(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_disconnect(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_connect(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_disconnect(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); private: Q_NEVER_INLINE static ReturnedValue wrap_slowPath(ExecutionEngine *engine, QObject *object); @@ -234,12 +231,12 @@ struct Q_QML_EXPORT QObjectMethod : public QV4::FunctionObject int methodIndex() const { return d()->index; } QObject *object() const { return d()->object(); } - QV4::ReturnedValue method_toString(QV4::ExecutionContext *ctx) const; - QV4::ReturnedValue method_destroy(QV4::ExecutionContext *ctx, const Value *args, int argc) const; + QV4::ReturnedValue method_toString(QV4::ExecutionEngine *engine) const; + QV4::ReturnedValue method_destroy(QV4::ExecutionEngine *ctx, const Value *args, int argc) const; - static void call(const Managed *, Scope &scope, CallData *callData); + static ReturnedValue virtualCall(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); - void callInternal(CallData *callData, Scope &scope) const; + ReturnedValue callInternal(const Value *thisObject, const Value *argv, int argc) const; static QPair<QObject *, int> extractQtMethod(const QV4::FunctionObject *function); }; @@ -251,14 +248,15 @@ struct Q_QML_EXPORT QMetaObjectWrapper : public QV4::FunctionObject V4_NEEDS_DESTROY static ReturnedValue create(ExecutionEngine *engine, const QMetaObject* metaObject); - static void construct(const Managed *, Scope &scope, CallData *callData); - static bool isEqualTo(Managed *a, Managed *b); - const QMetaObject *metaObject() const { return d()->metaObject; } +protected: + static ReturnedValue virtualCallAsConstructor(const FunctionObject *, const Value *argv, int argc, const Value *); + static bool virtualIsEqualTo(Managed *a, Managed *b); + private: void init(ExecutionEngine *engine); - ReturnedValue constructInternal(CallData *callData) const; + ReturnedValue constructInternal(const Value *argv, int argc) const; ReturnedValue callConstructor(const QQmlPropertyData &data, QV4::ExecutionEngine *engine, QV4::CallData *callArgs) const; ReturnedValue callOverloadedConstructor(QV4::ExecutionEngine *engine, QV4::CallData *callArgs) const; @@ -290,7 +288,14 @@ public: Iterator end() { return QHash<QObject*, QV4::WeakValue>::end(); } void insert(QObject *key, Heap::Object *value); - ReturnedValue value(QObject *key) const { return QHash<QObject*, QV4::WeakValue>::value(key).value(); } + ReturnedValue value(QObject *key) const + { + ConstIterator it = find(key); + return it == end() + ? QV4::WeakValue().value() + : it->value(); + } + Iterator erase(Iterator it); void remove(QObject *key); void mark(QObject *key, MarkStack *markStack); diff --git a/src/qml/jsruntime/qv4reflect.cpp b/src/qml/jsruntime/qv4reflect.cpp new file mode 100644 index 0000000000..0772770d63 --- /dev/null +++ b/src/qml/jsruntime/qv4reflect.cpp @@ -0,0 +1,285 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4reflect_p.h" +#include "qv4symbol_p.h" +#include "qv4runtimeapi_p.h" +#include "qv4objectproto_p.h" +#include "qv4propertykey_p.h" +#include "qv4objectiterator_p.h" + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(Reflect); + +void Heap::Reflect::init() +{ + Object::init(); + Scope scope(internalClass->engine); + ScopedObject r(scope, this); + + r->defineDefaultProperty(QStringLiteral("apply"), QV4::Reflect::method_apply, 3); + r->defineDefaultProperty(QStringLiteral("construct"), QV4::Reflect::method_construct, 2); + r->defineDefaultProperty(QStringLiteral("defineProperty"), QV4::Reflect::method_defineProperty, 3); + r->defineDefaultProperty(QStringLiteral("deleteProperty"), QV4::Reflect::method_deleteProperty, 2); + r->defineDefaultProperty(QStringLiteral("get"), QV4::Reflect::method_get, 2); + r->defineDefaultProperty(QStringLiteral("getOwnPropertyDescriptor"), QV4::Reflect::method_getOwnPropertyDescriptor, 2); + r->defineDefaultProperty(QStringLiteral("getPrototypeOf"), QV4::Reflect::method_getPrototypeOf, 1); + r->defineDefaultProperty(QStringLiteral("has"), QV4::Reflect::method_has, 2); + r->defineDefaultProperty(QStringLiteral("isExtensible"), QV4::Reflect::method_isExtensible, 1); + r->defineDefaultProperty(QStringLiteral("ownKeys"), QV4::Reflect::method_ownKeys, 1); + r->defineDefaultProperty(QStringLiteral("preventExtensions"), QV4::Reflect::method_preventExtensions, 1); + r->defineDefaultProperty(QStringLiteral("set"), QV4::Reflect::method_set, 3); + r->defineDefaultProperty(QStringLiteral("setPrototypeOf"), QV4::Reflect::method_setPrototypeOf, 2); +} + +struct CallArgs { + Value *argv; + int argc; +}; + +static CallArgs createListFromArrayLike(Scope &scope, const Object *o) +{ + int len = o->getLength(); + Value *arguments = scope.alloc(len); + + for (int i = 0; i < len; ++i) { + arguments[i] = o->get(i); + if (scope.hasException()) + return { nullptr, 0 }; + } + return { arguments, len }; +} + +ReturnedValue Reflect::method_apply(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (argc < 3 || !argv[0].isFunctionObject() || !argv[2].isObject()) + return scope.engine->throwTypeError(); + + const Object *o = static_cast<const Object *>(argv + 2); + CallArgs arguments = createListFromArrayLike(scope, o); + if (scope.hasException()) + return Encode::undefined(); + + return static_cast<const FunctionObject &>(argv[0]).call(&argv[1], arguments.argv, arguments.argc); +} + +ReturnedValue Reflect::method_construct(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (argc < 2 || !argv[1].isObject()) + return scope.engine->throwTypeError(); + const FunctionObject *target = argv[0].as<FunctionObject>(); + const FunctionObject *newTarget = argc == 3 ? argv[2].as<FunctionObject>() : target; + if (!target || !target->isConstructor() || !newTarget || !newTarget->isConstructor()) + return scope.engine->throwTypeError(); + + const Object *o = static_cast<const Object *>(argv + 1); + CallArgs arguments = createListFromArrayLike(scope, o); + if (scope.hasException()) + return Encode::undefined(); + + return target->callAsConstructor(arguments.argv, arguments.argc, newTarget); +} + +ReturnedValue Reflect::method_defineProperty(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc || !argv[0].isObject()) + return scope.engine->throwTypeError(); + + ScopedObject O(scope, argv[0]); + ScopedPropertyKey name(scope, (argc > 1 ? argv[1] : Value::undefinedValue()).toPropertyKey(scope.engine)); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + + ScopedValue attributes(scope, argc > 2 ? argv[2] : Value::undefinedValue()); + ScopedProperty pd(scope); + PropertyAttributes attrs; + ObjectPrototype::toPropertyDescriptor(scope.engine, attributes, pd, &attrs); + if (scope.engine->hasException) + return QV4::Encode::undefined(); + + bool result = O->defineOwnProperty(name, pd, attrs); + + return Encode(result); +} + +ReturnedValue Reflect::method_deleteProperty(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + ExecutionEngine *e = f->engine(); + if (!argc || !argv[0].isObject()) + return e->throwTypeError(); + + bool result = Runtime::DeleteProperty_NoThrow::call(e, argv[0], argc > 1 ? argv[1] : Value::undefinedValue()); + return Encode(result); +} + +ReturnedValue Reflect::method_get(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc || !argv[0].isObject()) + return scope.engine->throwTypeError(); + + ScopedObject o(scope, static_cast<const Object *>(argv)); + Value undef = Value::undefinedValue(); + const Value *index = argc > 1 ? &argv[1] : &undef; + ScopedPropertyKey name(scope, index->toPropertyKey(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + ScopedValue receiver(scope, argc > 2 ? argv[2] : *o); + + return Encode(o->get(name, receiver)); +} + +ReturnedValue Reflect::method_getOwnPropertyDescriptor(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + if (!argc || !argv[0].isObject()) + return f->engine()->throwTypeError(); + + return ObjectPrototype::method_getOwnPropertyDescriptor(f, thisObject, argv, argc); +} + +ReturnedValue Reflect::method_getPrototypeOf(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + if (!argc || !argv[0].isObject()) + return f->engine()->throwTypeError(); + + const Object *o = static_cast<const Object *>(argv); + Heap::Object *p = o->getPrototypeOf(); + return (p ? p->asReturnedValue() : Encode::null()); +} + +ReturnedValue Reflect::method_has(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc || !argv[0].isObject()) + return scope.engine->throwTypeError(); + + ScopedObject o(scope, static_cast<const Object *>(argv)); + Value undef = Value::undefinedValue(); + const Value *index = argc > 1 ? &argv[1] : &undef; + + ScopedPropertyKey name(scope, index->toPropertyKey(scope.engine)); + if (scope.engine->hasException) + return false; + + return Encode(o->hasProperty(name)); +} + +ReturnedValue Reflect::method_isExtensible(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + if (!argc || !argv[0].isObject()) + return f->engine()->throwTypeError(); + + const Object *o = static_cast<const Object *>(argv); + return Encode(o->isExtensible()); +} + + +ReturnedValue Reflect::method_ownKeys(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + if (!argc || !argv[0].isObject()) + return f->engine()->throwTypeError(); + + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + ScopedObject O(scope, argv[0].toObject(scope.engine)); + if (!O) + return Encode::undefined(); + + ScopedArrayObject keys(scope, scope.engine->newArrayObject()); + + ObjectIterator it(scope, O, ObjectIterator::WithSymbols); + ScopedPropertyKey key(scope); + ScopedValue v(scope); + while (1) { + key = it.next(); + if (!key->isValid()) + break; + v = key->toStringOrSymbol(scope.engine); + keys->push_back(v); + } + + return keys->asReturnedValue(); + +} + +ReturnedValue Reflect::method_preventExtensions(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc || !argv[0].isObject()) + return scope.engine->throwTypeError(); + + ScopedObject o(scope, static_cast<const Object *>(argv)); + return Encode(o->preventExtensions()); +} + +ReturnedValue Reflect::method_set(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc || !argv[0].isObject()) + return scope.engine->throwTypeError(); + + ScopedObject o(scope, static_cast<const Object *>(argv)); + Value undef = Value::undefinedValue(); + const Value *index = argc > 1 ? &argv[1] : &undef; + const Value &val = argc > 2 ? argv[2] : undef; + ScopedValue receiver(scope, argc >3 ? argv[3] : argv[0]); + + ScopedPropertyKey propertyKey(scope, index->toPropertyKey(scope.engine)); + if (scope.engine->hasException) + return false; + bool result = o->put(propertyKey, val, receiver); + return Encode(result); +} + +ReturnedValue Reflect::method_setPrototypeOf(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + if (argc < 2 || !argv[0].isObject() || (!argv[1].isNull() && !argv[1].isObject())) + return f->engine()->throwTypeError(); + + Scope scope(f); + ScopedObject o(scope, static_cast<const Object *>(argv)); + const Object *proto = argv[1].isNull() ? nullptr : static_cast<const Object *>(argv + 1); + return Encode(o->setPrototypeOf(proto)); +} diff --git a/src/qml/jsruntime/qv4reflect_p.h b/src/qml/jsruntime/qv4reflect_p.h new file mode 100644 index 0000000000..d480e1d914 --- /dev/null +++ b/src/qml/jsruntime/qv4reflect_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4REFLECT_H +#define QV4REFLECT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Heap { + +struct Reflect : Object { + void init(); +}; + +} + +struct Reflect : Object { + V4_OBJECT2(Reflect, Object) + + static ReturnedValue method_apply(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_construct(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_defineProperty(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_deleteProperty(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getOwnPropertyDescriptor(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_getPrototypeOf(const FunctionObject *, const Value *, const Value *argv, int argc); + static ReturnedValue method_has(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_isExtensible(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_ownKeys(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_preventExtensions(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_set(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_setPrototypeOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/jsruntime/qv4regexp.cpp b/src/qml/jsruntime/qv4regexp.cpp index 6778145ff1..4ed1dbd5aa 100644 --- a/src/qml/jsruntime/qv4regexp.cpp +++ b/src/qml/jsruntime/qv4regexp.cpp @@ -41,14 +41,31 @@ #include "qv4engine_p.h" #include "qv4scopedvalue_p.h" #include <private/qv4mm_p.h> +#include <runtime/VM.h> using namespace QV4; +static JSC::RegExpFlags jscFlags(uint flags) +{ + JSC::RegExpFlags jscFlags = JSC::NoFlags; + if (flags & CompiledData::RegExp::RegExp_Global) + jscFlags = static_cast<JSC::RegExpFlags>(flags | JSC::FlagGlobal); + if (flags & CompiledData::RegExp::RegExp_IgnoreCase) + jscFlags = static_cast<JSC::RegExpFlags>(flags | JSC::FlagIgnoreCase); + if (flags & CompiledData::RegExp::RegExp_Multiline) + jscFlags = static_cast<JSC::RegExpFlags>(flags | JSC::FlagMultiline); + if (flags & CompiledData::RegExp::RegExp_Unicode) + jscFlags = static_cast<JSC::RegExpFlags>(flags | JSC::FlagUnicode); + if (flags & CompiledData::RegExp::RegExp_Sticky) + jscFlags = static_cast<JSC::RegExpFlags>(flags | JSC::FlagSticky); + return jscFlags; +} + RegExpCache::~RegExpCache() { for (RegExpCache::Iterator it = begin(), e = end(); it != e; ++it) { if (RegExp *re = it.value().as<RegExp>()) - re->d()->cache = 0; + re->d()->cache = nullptr; } } @@ -56,22 +73,120 @@ DEFINE_MANAGED_VTABLE(RegExp); uint RegExp::match(const QString &string, int start, uint *matchOffsets) { + static const uint offsetJITFail = std::numeric_limits<unsigned>::max() - 1; + if (!isValid()) return JSC::Yarr::offsetNoMatch; WTF::String s(string); #if ENABLE(YARR_JIT) - if (!jitCode()->isFallBack() && jitCode()->has16BitCode()) - return uint(jitCode()->execute(s.characters16(), start, s.length(), (int*)matchOffsets).start); + auto *priv = d(); + if (priv->hasValidJITCode()) { + uint ret = JSC::Yarr::offsetNoMatch; +#if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS) + char buffer[8192]; + ret = uint(priv->jitCode->execute(s.characters16(), start, s.length(), + (int*)matchOffsets, buffer, 8192).start); +#else + ret = uint(priv->jitCode->execute(s.characters16(), start, s.length(), + (int*)matchOffsets).start); #endif + if (ret != offsetJITFail) + return ret; + + // JIT failed. We need byteCode to run the interpreter. + if (!priv->byteCode) { + JSC::Yarr::ErrorCode error = JSC::Yarr::ErrorCode::NoError; + JSC::Yarr::YarrPattern yarrPattern(WTF::String(*priv->pattern), jscFlags(priv->flags), + error); + + // As we successfully parsed the pattern before, we should still be able to. + Q_ASSERT(error == JSC::Yarr::ErrorCode::NoError); + + priv->byteCode = JSC::Yarr::byteCompile( + yarrPattern, + priv->internalClass->engine->bumperPointerAllocator).release(); + } + } +#endif // ENABLE(YARR_JIT) return JSC::Yarr::interpret(byteCode(), s.characters16(), string.length(), start, matchOffsets); } -Heap::RegExp *RegExp::create(ExecutionEngine* engine, const QString& pattern, bool ignoreCase, bool multiline) +QString RegExp::getSubstitution(const QString &matched, const QString &str, int position, const Value *captures, int nCaptures, const QString &replacement) { - RegExpCacheKey key(pattern, ignoreCase, multiline); + QString result; + + int matchedLength = matched.length(); + Q_ASSERT(position >= 0 && position <= str.length()); + int tailPos = position + matchedLength; + int seenDollar = -1; + for (int i = 0; i < replacement.length(); ++i) { + QChar ch = replacement.at(i); + if (seenDollar >= 0) { + if (ch.unicode() == '$') { + result += QLatin1Char('$'); + } else if (ch.unicode() == '&') { + result += matched; + } else if (ch.unicode() == '`') { + result += str.left(position); + } else if (ch.unicode() == '\'') { + result += str.mid(tailPos); + } else if (ch.unicode() >= '0' && ch.unicode() <= '9') { + int n = ch.unicode() - '0'; + if (i + 1 < replacement.length()) { + ch = replacement.at(i + 1); + if (ch.unicode() >= '0' && ch.unicode() <= '9') { + n = n*10 + (ch.unicode() - '0'); + ++i; + } + } + if (n > 0 && n <= nCaptures) { + String *s = captures[n].stringValue(); + if (s) + result += s->toQString(); + } else { + for (int j = seenDollar; j <= i; ++j) + result += replacement.at(j); + } + } else { + result += QLatin1Char('$'); + result += ch; + } + seenDollar = -1; + } else { + if (ch == QLatin1Char('$')) { + seenDollar = i; + continue; + } + result += ch; + } + } + if (seenDollar >= 0) + result += QLatin1Char('$'); + return result; +} + +QString Heap::RegExp::flagsAsString() const +{ + QString result; + if (flags & CompiledData::RegExp::RegExp_Global) + result += QLatin1Char('g'); + if (flags & CompiledData::RegExp::RegExp_IgnoreCase) + result += QLatin1Char('i'); + if (flags & CompiledData::RegExp::RegExp_Multiline) + result += QLatin1Char('m'); + if (flags & CompiledData::RegExp::RegExp_Unicode) + result += QLatin1Char('u'); + if (flags & CompiledData::RegExp::RegExp_Sticky) + result += QLatin1Char('y'); + return result; +} + +Heap::RegExp *RegExp::create(ExecutionEngine* engine, const QString& pattern, uint flags) +{ + RegExpCacheKey key(pattern, flags); RegExpCache *cache = engine->regExpCache; if (!cache) @@ -82,7 +197,7 @@ Heap::RegExp *RegExp::create(ExecutionEngine* engine, const QString& pattern, bo return result->d(); Scope scope(engine); - Scoped<RegExp> result(scope, engine->memoryManager->alloc<RegExp>(engine, pattern, ignoreCase, multiline)); + Scoped<RegExp> result(scope, engine->memoryManager->alloc<RegExp>(engine, pattern, flags)); result->d()->cache = cache; cachedValue.set(engine, result); @@ -90,27 +205,35 @@ Heap::RegExp *RegExp::create(ExecutionEngine* engine, const QString& pattern, bo return result->d(); } -void Heap::RegExp::init(ExecutionEngine* engine, const QString &pattern, bool ignoreCase, bool multiline) +void Heap::RegExp::init(ExecutionEngine *engine, const QString &pattern, uint flags) { Base::init(); this->pattern = new QString(pattern); - this->ignoreCase = ignoreCase; - this->multiLine = multiline; + this->flags = flags; - const char* error = 0; - JSC::Yarr::YarrPattern yarrPattern(WTF::String(pattern), ignoreCase, multiline, &error); - if (error) + valid = false; + + JSC::Yarr::ErrorCode error = JSC::Yarr::ErrorCode::NoError; + JSC::Yarr::YarrPattern yarrPattern(WTF::String(pattern), jscFlags(flags), error); + if (error != JSC::Yarr::ErrorCode::NoError) return; subPatternCount = yarrPattern.m_numSubpatterns; - OwnPtr<JSC::Yarr::BytecodePattern> p = JSC::Yarr::byteCompile(yarrPattern, engine->bumperPointerAllocator); - byteCode = p.take(); #if ENABLE(YARR_JIT) - jitCode = new JSC::Yarr::YarrCodeBlock; - if (!yarrPattern.m_containsBackreferences && engine->iselFactory->jitCompileRegexps()) { - JSC::JSGlobalData dummy(engine->regExpAllocator); - JSC::Yarr::jitCompile(yarrPattern, JSC::Yarr::Char16, &dummy, *jitCode); + if (!yarrPattern.m_containsBackreferences && engine->canJIT()) { + jitCode = new JSC::Yarr::YarrCodeBlock; + JSC::VM *vm = static_cast<JSC::VM *>(engine); + JSC::Yarr::jitCompile(yarrPattern, JSC::Yarr::Char16, vm, *jitCode); } +#else + Q_UNUSED(engine) #endif + if (hasValidJITCode()) { + valid = true; + return; + } + byteCode = JSC::Yarr::byteCompile(yarrPattern, internalClass->engine->bumperPointerAllocator).release(); + if (byteCode) + valid = true; } void Heap::RegExp::destroy() diff --git a/src/qml/jsruntime/qv4regexp_p.h b/src/qml/jsruntime/qv4regexp_p.h index 7ab12fe245..6afb10ea95 100644 --- a/src/qml/jsruntime/qv4regexp_p.h +++ b/src/qml/jsruntime/qv4regexp_p.h @@ -76,7 +76,7 @@ struct RegExpCacheKey; namespace Heap { struct RegExp : Base { - void init(ExecutionEngine* engine, const QString& pattern, bool ignoreCase, bool multiline); + void init(ExecutionEngine *engine, const QString& pattern, uint flags); void destroy(); QString *pattern; @@ -84,14 +84,29 @@ struct RegExp : Base { #if ENABLE(YARR_JIT) JSC::Yarr::YarrCodeBlock *jitCode; #endif + bool hasValidJITCode() const { +#if ENABLE(YARR_JIT) + return jitCode && !jitCode->failureReason().has_value() && jitCode->has16BitCode(); +#else + return false; +#endif + } + + bool ignoreCase() const { return flags & CompiledData::RegExp::RegExp_IgnoreCase; } + bool multiLine() const { return flags & CompiledData::RegExp::RegExp_Multiline; } + bool global() const { return flags & CompiledData::RegExp::RegExp_Global; } + bool unicode() const { return flags & CompiledData::RegExp::RegExp_Unicode; } + bool sticky() const { return flags & CompiledData::RegExp::RegExp_Sticky; } + RegExpCache *cache; int subPatternCount; - bool ignoreCase; - bool multiLine; + uint flags; + bool valid; + QString flagsAsString() const; int captureCount() const { return subPatternCount + 1; } }; -V4_ASSERT_IS_TRIVIAL(RegExp) +Q_STATIC_ASSERT(std::is_trivial< RegExp >::value); } @@ -109,43 +124,44 @@ struct RegExp : public Managed #endif RegExpCache *cache() const { return d()->cache; } int subPatternCount() const { return d()->subPatternCount; } - bool ignoreCase() const { return d()->ignoreCase; } - bool multiLine() const { return d()->multiLine; } + bool ignoreCase() const { return d()->ignoreCase(); } + bool multiLine() const { return d()->multiLine(); } + bool global() const { return d()->global(); } + bool unicode() const { return d()->unicode(); } + bool sticky() const { return d()->sticky(); } - static Heap::RegExp *create(ExecutionEngine* engine, const QString& pattern, bool ignoreCase = false, bool multiline = false); + static Heap::RegExp *create(ExecutionEngine* engine, const QString& pattern, uint flags = CompiledData::RegExp::RegExp_NoFlags); - bool isValid() const { return d()->byteCode; } + bool isValid() const { return d()->valid; } uint match(const QString& string, int start, uint *matchOffsets); int captureCount() const { return subPatternCount() + 1; } + static QString getSubstitution(const QString &matched, const QString &str, int position, const Value *captures, int nCaptures, const QString &replacement); + friend class RegExpCache; }; struct RegExpCacheKey { - RegExpCacheKey(const QString &pattern, bool ignoreCase, bool multiLine) - : pattern(pattern) - , ignoreCase(ignoreCase) - , multiLine(multiLine) + RegExpCacheKey(const QString &pattern, uint flags) + : pattern(pattern), flags(flags) { } explicit inline RegExpCacheKey(const RegExp::Data *re); bool operator==(const RegExpCacheKey &other) const - { return pattern == other.pattern && ignoreCase == other.ignoreCase && multiLine == other.multiLine; } + { return pattern == other.pattern && flags == other.flags;; } bool operator!=(const RegExpCacheKey &other) const { return !operator==(other); } QString pattern; - uint ignoreCase : 1; - uint multiLine : 1; + uint flags; }; inline RegExpCacheKey::RegExpCacheKey(const RegExp::Data *re) : pattern(*re->pattern) - , ignoreCase(re->ignoreCase) - , multiLine(re->multiLine) + , flags(re->flags) {} inline uint qHash(const RegExpCacheKey& key, uint seed = 0) Q_DECL_NOTHROW diff --git a/src/qml/jsruntime/qv4regexpobject.cpp b/src/qml/jsruntime/qv4regexpobject.cpp index 735951e085..5bd25dcbec 100644 --- a/src/qml/jsruntime/qv4regexpobject.cpp +++ b/src/qml/jsruntime/qv4regexpobject.cpp @@ -38,24 +38,21 @@ ****************************************************************************/ #include "qv4regexpobject_p.h" -#include "qv4jsir_p.h" -#include "qv4isel_p.h" #include "qv4objectproto_p.h" #include "qv4regexp_p.h" #include "qv4stringobject_p.h" #include <private/qv4mm_p.h> #include "qv4scopedvalue_p.h" +#include "qv4jscall_p.h" +#include "qv4symbol_p.h" -#include <private/qqmljsengine_p.h> -#include <private/qqmljslexer_p.h> -#include <private/qqmljsparser_p.h> -#include <private/qqmljsast_p.h> -#include <qv4jsir_p.h> -#include <qv4codegen_p.h> #include "private/qlocale_tools_p.h" #include <QtCore/QDebug> #include <QtCore/qregexp.h> +#if QT_CONFIG(regularexpression) +#include <QtCore/qregularexpression.h> +#endif #include <cassert> #include <typeinfo> #include <iostream> @@ -74,16 +71,14 @@ void Heap::RegExpObject::init() Object::init(); Scope scope(internalClass->engine); Scoped<QV4::RegExpObject> o(scope, this); - value.set(scope.engine, QV4::RegExp::create(scope.engine, QString(), false, false)); - global = false; + value.set(scope.engine, QV4::RegExp::create(scope.engine, QString(), CompiledData::RegExp::RegExp_NoFlags)); o->initProperties(); } -void Heap::RegExpObject::init(QV4::RegExp *value, bool global) +void Heap::RegExpObject::init(QV4::RegExp *value) { Object::init(); Scope scope(internalClass->engine); - this->global = global; this->value.set(scope.engine, value->d()); Scoped<QV4::RegExpObject> o(scope, this); o->initProperties(); @@ -95,7 +90,6 @@ void Heap::RegExpObject::init(QV4::RegExp *value, bool global) void Heap::RegExpObject::init(const QRegExp &re) { Object::init(); - global = false; // Convert the pattern to a ECMAScript pattern. QString pattern = QT_PREPEND_NAMESPACE(qt_regexp_toCanonical)(re.pattern(), re.patternSyntax()); @@ -137,30 +131,36 @@ void Heap::RegExpObject::init(const QRegExp &re) Scope scope(internalClass->engine); Scoped<QV4::RegExpObject> o(scope, this); - o->d()->value.set(scope.engine, - QV4::RegExp::create(scope.engine, pattern, re.caseSensitivity() == Qt::CaseInsensitive, false)); + uint flags = (re.caseSensitivity() == Qt::CaseInsensitive ? CompiledData::RegExp::RegExp_IgnoreCase : CompiledData::RegExp::RegExp_NoFlags); + o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, pattern, flags)); o->initProperties(); } -void RegExpObject::initProperties() +#if QT_CONFIG(regularexpression) +// Converts a QRegularExpression to a JS RegExp. +// The conversion is not 100% exact since ECMA regexp and QRegularExpression +// have different semantics/flags, but we try to do our best. +void Heap::RegExpObject::init(const QRegularExpression &re) { - setProperty(Index_LastIndex, Primitive::fromInt32(0)); + Object::init(); - Q_ASSERT(value()); + Scope scope(internalClass->engine); + Scoped<QV4::RegExpObject> o(scope, this); - QString p = *value()->pattern; - if (p.isEmpty()) { - p = QStringLiteral("(?:)"); - } else { - // escape certain parts, see ch. 15.10.4 - p.replace('/', QLatin1String("\\/")); - } + const uint flags = (re.patternOptions() & QRegularExpression::CaseInsensitiveOption) + ? CompiledData::RegExp::RegExp_IgnoreCase + : CompiledData::RegExp::RegExp_NoFlags; + o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, re.pattern(), flags)); + o->initProperties(); +} +#endif - setProperty(Index_Source, engine()->newString(p)); - setProperty(Index_Global, Primitive::fromBoolean(global())); - setProperty(Index_IgnoreCase, Primitive::fromBoolean(value()->ignoreCase)); - setProperty(Index_Multiline, Primitive::fromBoolean(value()->multiLine)); +void RegExpObject::initProperties() +{ + setProperty(Index_LastIndex, Value::fromInt32(0)); + + Q_ASSERT(value()); } // Converts a JS RegExp to a QRegExp. @@ -168,40 +168,96 @@ void RegExpObject::initProperties() // have different semantics/flags, but we try to do our best. QRegExp RegExpObject::toQRegExp() const { - Qt::CaseSensitivity caseSensitivity = value()->ignoreCase ? Qt::CaseInsensitive : Qt::CaseSensitive; + Qt::CaseSensitivity caseSensitivity = (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase) ? Qt::CaseInsensitive : Qt::CaseSensitive; return QRegExp(*value()->pattern, caseSensitivity, QRegExp::RegExp2); } +#if QT_CONFIG(regularexpression) +// Converts a JS RegExp to a QRegularExpression. +// The conversion is not 100% exact since ECMA regexp and QRegularExpression +// have different semantics/flags, but we try to do our best. +QRegularExpression RegExpObject::toQRegularExpression() const +{ + QRegularExpression::PatternOptions caseSensitivity + = (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase) + ? QRegularExpression::CaseInsensitiveOption + : QRegularExpression::NoPatternOption; + return QRegularExpression(*value()->pattern, caseSensitivity); +} +#endif + QString RegExpObject::toString() const { - QString result = QLatin1Char('/') + source() + QLatin1Char('/'); - if (global()) - result += QLatin1Char('g'); - if (value()->ignoreCase) - result += QLatin1Char('i'); - if (value()->multiLine) - result += QLatin1Char('m'); - return result; + QString p = *value()->pattern; + if (p.isEmpty()) { + p = QStringLiteral("(?:)"); + } else { + // escape certain parts, see ch. 15.10.4 + p.replace('/', QLatin1String("\\/")); + } + return p; } QString RegExpObject::source() const { Scope scope(engine()); - ScopedString source(scope, scope.engine->newIdentifier(QStringLiteral("source"))); - ScopedValue s(scope, const_cast<RegExpObject *>(this)->get(source)); + ScopedValue s(scope, get(scope.engine->id_source())); return s->toQString(); } -uint RegExpObject::flags() const +ReturnedValue RegExpObject::builtinExec(ExecutionEngine *engine, const String *str) { - uint f = 0; - if (global()) - f |= QV4::RegExpObject::RegExp_Global; - if (value()->ignoreCase) - f |= QV4::RegExpObject::RegExp_IgnoreCase; - if (value()->multiLine) - f |= QV4::RegExpObject::RegExp_Multiline; - return f; + QString s = str->toQString(); + + Scope scope(engine); + int offset = (global() || sticky()) ? lastIndex() : 0; + if (offset < 0 || offset > s.length()) { + setLastIndex(0); + RETURN_RESULT(Encode::null()); + } + + Q_ALLOCA_VAR(uint, matchOffsets, value()->captureCount() * 2 * sizeof(uint)); + const uint result = Scoped<RegExp>(scope, value())->match(s, offset, matchOffsets); + + RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor()); + regExpCtor->d()->clearLastMatch(); + + if (result == JSC::Yarr::offsetNoMatch) { + if (global() || sticky()) + setLastIndex(0); + RETURN_RESULT(Encode::null()); + } + + Q_ASSERT(result <= uint(std::numeric_limits<int>::max())); + + // fill in result data + ScopedArrayObject array(scope, scope.engine->newArrayObject(scope.engine->internalClasses(EngineBase::Class_RegExpExecArray))); + int len = value()->captureCount(); + array->arrayReserve(len); + ScopedValue v(scope); + int strlen = s.length(); + for (int i = 0; i < len; ++i) { + int start = matchOffsets[i * 2]; + int end = matchOffsets[i * 2 + 1]; + if (end > strlen) + end = strlen; + v = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(str->d(), start, end - start)->asReturnedValue() : Encode::undefined(); + array->arrayPut(i, v); + } + array->setArrayLengthUnchecked(len); + array->setProperty(Index_ArrayIndex, Value::fromInt32(int(result))); + array->setProperty(Index_ArrayInput, *str); + + RegExpCtor::Data *dd = regExpCtor->d(); + dd->lastMatch.set(scope.engine, array); + dd->lastInput.set(scope.engine, str->d()); + dd->lastMatchStart = matchOffsets[0]; + dd->lastMatchEnd = matchOffsets[1]; + + if (global() || sticky()) + setLastIndex(matchOffsets[1]); + + return array.asReturnedValue(); } DEFINE_OBJECT_VTABLE(RegExpCtor); @@ -214,79 +270,118 @@ void Heap::RegExpCtor::init(QV4::ExecutionContext *scope) void Heap::RegExpCtor::clearLastMatch() { - lastMatch.set(internalClass->engine, Primitive::nullValue()); + lastMatch.set(internalClass->engine, Value::nullValue()); lastInput.set(internalClass->engine, internalClass->engine->id_empty()->d()); lastMatchStart = 0; lastMatchEnd = 0; } -void RegExpCtor::construct(const Managed *, Scope &scope, CallData *callData) +static bool isRegExp(ExecutionEngine *e, const QV4::Value *arg) { - ScopedValue r(scope, callData->argument(0)); - ScopedValue f(scope, callData->argument(1)); - Scoped<RegExpObject> re(scope, r); - if (re) { - if (!f->isUndefined()) { - scope.result = scope.engine->throwTypeError(); - return; - } - - Scoped<RegExp> regexp(scope, re->value()); - scope.result = Encode(scope.engine->newRegExpObject(regexp, re->global())); - return; - } - - QString pattern; - if (!r->isUndefined()) - pattern = r->toQString(); - if (scope.hasException()) { - scope.result = Encode::undefined(); - return; - } + const QV4::Object *o = arg->objectValue(); + if (!o) + return false; + + QV4::Value isRegExp = QV4::Value::fromReturnedValue(o->get(e->symbol_match())); + if (!isRegExp.isUndefined()) + return isRegExp.toBoolean(); + const RegExpObject *re = o->as<RegExpObject>(); + return re ? true : false; +} - bool global = false; - bool ignoreCase = false; - bool multiLine = false; +uint parseFlags(Scope &scope, const QV4::Value *f) +{ + uint flags = CompiledData::RegExp::RegExp_NoFlags; if (!f->isUndefined()) { ScopedString s(scope, f->toString(scope.engine)); - if (scope.hasException()) { - scope.result = Encode::undefined(); - return; - } + if (scope.hasException()) + return flags; QString str = s->toQString(); for (int i = 0; i < str.length(); ++i) { - if (str.at(i) == QLatin1Char('g') && !global) { - global = true; - } else if (str.at(i) == QLatin1Char('i') && !ignoreCase) { - ignoreCase = true; - } else if (str.at(i) == QLatin1Char('m') && !multiLine) { - multiLine = true; + if (str.at(i) == QLatin1Char('g') && !(flags & CompiledData::RegExp::RegExp_Global)) { + flags |= CompiledData::RegExp::RegExp_Global; + } else if (str.at(i) == QLatin1Char('i') && !(flags & CompiledData::RegExp::RegExp_IgnoreCase)) { + flags |= CompiledData::RegExp::RegExp_IgnoreCase; + } else if (str.at(i) == QLatin1Char('m') && !(flags & CompiledData::RegExp::RegExp_Multiline)) { + flags |= CompiledData::RegExp::RegExp_Multiline; + } else if (str.at(i) == QLatin1Char('u') && !(flags & CompiledData::RegExp::RegExp_Unicode)) { + flags |= CompiledData::RegExp::RegExp_Unicode; + } else if (str.at(i) == QLatin1Char('y') && !(flags & CompiledData::RegExp::RegExp_Sticky)) { + flags |= CompiledData::RegExp::RegExp_Sticky; } else { - scope.result = scope.engine->throwSyntaxError(QStringLiteral("Invalid flags supplied to RegExp constructor")); - return; + scope.engine->throwSyntaxError(QStringLiteral("Invalid flags supplied to RegExp constructor")); + return flags; } } } + return flags; +} - Scoped<RegExp> regexp(scope, RegExp::create(scope.engine, pattern, ignoreCase, multiLine)); - if (!regexp->isValid()) { - scope.result = scope.engine->throwSyntaxError(QStringLiteral("Invalid regular expression")); - return; +ReturnedValue RegExpCtor::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *newTarget) +{ + Scope scope(fo); + + bool patternIsRegExp = argc ? ::isRegExp(scope.engine, argv) : false; + + if (newTarget == fo) { + if (patternIsRegExp && (argc < 2 || argv[1].isUndefined())) { + const Object *pattern = static_cast<const Object *>(argv); + ScopedValue patternConstructor(scope, pattern->get(scope.engine->id_constructor())); + if (patternConstructor->sameValue(*newTarget)) + return pattern->asReturnedValue(); + } } - scope.result = Encode(scope.engine->newRegExpObject(regexp, global)); -} + ScopedValue p(scope, argc ? argv[0] : Value::undefinedValue()); + ScopedValue f(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Scoped<RegExpObject> re(scope, p); + QString pattern; + uint flags = CompiledData::RegExp::RegExp_NoFlags; -void RegExpCtor::call(const Managed *that, Scope &scope, CallData *callData) -{ - if (callData->argc > 0 && callData->args[0].as<RegExpObject>()) { - if (callData->argc == 1 || callData->args[1].isUndefined()) { - scope.result = callData->args[0]; - return; + if (re) { + if (f->isUndefined()) { + Scoped<RegExp> regexp(scope, re->value()); + return Encode(scope.engine->newRegExpObject(regexp)); } + pattern = *re->value()->pattern; + flags = parseFlags(scope, f); + } else if (patternIsRegExp) { + const Object *po = static_cast<const Object *>(argv); + p = po->get(scope.engine->id_source()); + if (!p->isUndefined()) + pattern = p->toQString(); + if (scope.hasException()) + return Encode::undefined(); + if (f->isUndefined()) + f = po->get(scope.engine->id_flags()); + flags = parseFlags(scope, f); + } else { + if (!p->isUndefined()) + pattern = p->toQString(); + if (scope.hasException()) + return Encode::undefined(); + flags = parseFlags(scope, f); + } + if (scope.hasException()) + return Encode::undefined(); + + Scoped<RegExp> regexp(scope, RegExp::create(scope.engine, pattern, flags)); + if (!regexp->isValid()) { + return scope.engine->throwSyntaxError(QStringLiteral("Invalid regular expression")); } - construct(that, scope, callData); + ReturnedValue o = Encode(scope.engine->newRegExpObject(regexp)); + + if (!newTarget) + return o; + ScopedObject obj(scope, o); + obj->setProtoFromNewTarget(newTarget); + return obj->asReturnedValue(); +} + +ReturnedValue RegExpCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + return virtualCallAsConstructor(f, argv, argc, f); } void RegExpPrototype::init(ExecutionEngine *engine, Object *constructor) @@ -296,49 +391,62 @@ void RegExpPrototype::init(ExecutionEngine *engine, Object *constructor) ScopedObject ctor(scope, constructor); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(2)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(2)); + ctor->addSymbolSpecies(); // Properties deprecated in the spec but required by "the web" :( - ctor->defineAccessorProperty(QStringLiteral("lastMatch"), method_get_lastMatch_n<0>, 0); - ctor->defineAccessorProperty(QStringLiteral("$&"), method_get_lastMatch_n<0>, 0); - ctor->defineAccessorProperty(QStringLiteral("$1"), method_get_lastMatch_n<1>, 0); - ctor->defineAccessorProperty(QStringLiteral("$2"), method_get_lastMatch_n<2>, 0); - ctor->defineAccessorProperty(QStringLiteral("$3"), method_get_lastMatch_n<3>, 0); - ctor->defineAccessorProperty(QStringLiteral("$4"), method_get_lastMatch_n<4>, 0); - ctor->defineAccessorProperty(QStringLiteral("$5"), method_get_lastMatch_n<5>, 0); - ctor->defineAccessorProperty(QStringLiteral("$6"), method_get_lastMatch_n<6>, 0); - ctor->defineAccessorProperty(QStringLiteral("$7"), method_get_lastMatch_n<7>, 0); - ctor->defineAccessorProperty(QStringLiteral("$8"), method_get_lastMatch_n<8>, 0); - ctor->defineAccessorProperty(QStringLiteral("$9"), method_get_lastMatch_n<9>, 0); - ctor->defineAccessorProperty(QStringLiteral("lastParen"), method_get_lastParen, 0); - ctor->defineAccessorProperty(QStringLiteral("$+"), method_get_lastParen, 0); - ctor->defineAccessorProperty(QStringLiteral("input"), method_get_input, 0); - ctor->defineAccessorProperty(QStringLiteral("$_"), method_get_input, 0); - ctor->defineAccessorProperty(QStringLiteral("leftContext"), method_get_leftContext, 0); - ctor->defineAccessorProperty(QStringLiteral("$`"), method_get_leftContext, 0); - ctor->defineAccessorProperty(QStringLiteral("rightContext"), method_get_rightContext, 0); - ctor->defineAccessorProperty(QStringLiteral("$'"), method_get_rightContext, 0); + ctor->defineAccessorProperty(QStringLiteral("lastMatch"), method_get_lastMatch_n<0>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$&"), method_get_lastMatch_n<0>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$1"), method_get_lastMatch_n<1>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$2"), method_get_lastMatch_n<2>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$3"), method_get_lastMatch_n<3>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$4"), method_get_lastMatch_n<4>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$5"), method_get_lastMatch_n<5>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$6"), method_get_lastMatch_n<6>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$7"), method_get_lastMatch_n<7>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$8"), method_get_lastMatch_n<8>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$9"), method_get_lastMatch_n<9>, nullptr); + ctor->defineAccessorProperty(QStringLiteral("lastParen"), method_get_lastParen, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$+"), method_get_lastParen, nullptr); + ctor->defineAccessorProperty(QStringLiteral("input"), method_get_input, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$_"), method_get_input, nullptr); + ctor->defineAccessorProperty(QStringLiteral("leftContext"), method_get_leftContext, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$`"), method_get_leftContext, nullptr); + ctor->defineAccessorProperty(QStringLiteral("rightContext"), method_get_rightContext, nullptr); + ctor->defineAccessorProperty(QStringLiteral("$'"), method_get_rightContext, nullptr); defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); + defineAccessorProperty(scope.engine->id_flags(), method_get_flags, nullptr); + defineAccessorProperty(scope.engine->id_global(), method_get_global, nullptr); + defineAccessorProperty(scope.engine->id_ignoreCase(), method_get_ignoreCase, nullptr); defineDefaultProperty(QStringLiteral("exec"), method_exec, 1); + defineDefaultProperty(engine->symbol_match(), method_match, 1); + defineAccessorProperty(scope.engine->id_multiline(), method_get_multiline, nullptr); + defineDefaultProperty(engine->symbol_replace(), method_replace, 2); + defineDefaultProperty(engine->symbol_search(), method_search, 1); + defineAccessorProperty(scope.engine->id_source(), method_get_source, nullptr); + defineDefaultProperty(engine->symbol_split(), method_split, 2); + defineAccessorProperty(scope.engine->id_sticky(), method_get_sticky, nullptr); defineDefaultProperty(QStringLiteral("test"), method_test, 1); defineDefaultProperty(engine->id_toString(), method_toString, 0); + defineAccessorProperty(scope.engine->id_unicode(), method_get_unicode, nullptr); + + // another web extension defineDefaultProperty(QStringLiteral("compile"), method_compile, 2); } -void RegExpPrototype::method_exec(const BuiltinFunction *, Scope &scope, CallData *callData) +/* used by String.match */ +ReturnedValue RegExpPrototype::execFirstMatch(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<RegExpObject> r(scope, callData->thisObject.as<RegExpObject>()); - if (!r) - THROW_TYPE_ERROR(); + Scope scope(b); + Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>()); + Q_ASSERT(r && r->global()); - ScopedValue arg(scope, callData->argument(0)); - ScopedString str(scope, arg->toString(scope.engine)); - if (scope.hasException()) - RETURN_UNDEFINED(); + ScopedString str(scope, argc ? argv[0] : Value::undefinedValue()); + Q_ASSERT(str); QString s = str->toQString(); - int offset = r->global() ? r->lastIndex() : 0; + int offset = r->lastIndex(); if (offset < 0 || offset > s.length()) { r->setLastIndex(0); RETURN_RESULT(Encode::null()); @@ -347,7 +455,7 @@ void RegExpPrototype::method_exec(const BuiltinFunction *, Scope &scope, CallDat Q_ALLOCA_VAR(uint, matchOffsets, r->value()->captureCount() * 2 * sizeof(uint)); const int result = Scoped<RegExp>(scope, r->value())->match(s, offset, matchOffsets); - Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor()); + RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor()); regExpCtor->d()->clearLastMatch(); if (result == -1) { @@ -355,99 +463,556 @@ void RegExpPrototype::method_exec(const BuiltinFunction *, Scope &scope, CallDat RETURN_RESULT(Encode::null()); } - // fill in result data - ScopedArrayObject array(scope, scope.engine->newArrayObject(scope.engine->internalClasses[EngineBase::Class_RegExpExecArray], scope.engine->arrayPrototype())); - int len = r->value()->captureCount(); - array->arrayReserve(len); - ScopedValue v(scope); - for (int i = 0; i < len; ++i) { - int start = matchOffsets[i * 2]; - int end = matchOffsets[i * 2 + 1]; - v = (start != -1) ? scope.engine->newString(s.mid(start, end - start))->asReturnedValue() : Encode::undefined(); - array->arrayPut(i, v); + ReturnedValue retVal = Encode::undefined(); + // return first match + if (r->value()->captureCount()) { + int start = matchOffsets[0]; + int end = matchOffsets[1]; + retVal = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(str->d(), start, end - start)->asReturnedValue() : Encode::undefined(); } - array->setArrayLengthUnchecked(len); - array->setProperty(Index_ArrayIndex, Primitive::fromInt32(result)); - array->setProperty(Index_ArrayInput, str); RegExpCtor::Data *dd = regExpCtor->d(); - dd->lastMatch.set(scope.engine, array); dd->lastInput.set(scope.engine, str->d()); dd->lastMatchStart = matchOffsets[0]; dd->lastMatchEnd = matchOffsets[1]; - if (r->global()) - r->setLastIndex(matchOffsets[1]); + r->setLastIndex(matchOffsets[1]); - scope.result = array; + return retVal; } -void RegExpPrototype::method_test(const BuiltinFunction *b, Scope &scope, CallData *callData) +ReturnedValue RegExpPrototype::exec(ExecutionEngine *engine, const Object *o, const String *s) { - method_exec(b, scope, callData); - scope.result = Encode(!scope.result.isNull()); + Scope scope(engine); + ScopedString key(scope, scope.engine->newString(QStringLiteral("exec"))); + ScopedFunctionObject exec(scope, o->get(key)); + if (exec) { + ScopedValue result(scope, exec->call(o, s, 1)); + if (!result->isNull() && !result->isObject()) + return scope.engine->throwTypeError(); + return result->asReturnedValue(); + } + Scoped<RegExpObject> re(scope, o); + if (!re) + return scope.engine->throwTypeError(); + return re->builtinExec(engine, s); } -void RegExpPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue RegExpPrototype::method_exec(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<RegExpObject> r(scope, callData->thisObject.as<RegExpObject>()); + Scope scope(b); + Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>()); if (!r) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); + + ScopedValue arg(scope, argc ? argv[0]: Value::undefinedValue()); + ScopedString str(scope, arg->toString(scope.engine)); + if (scope.hasException()) + RETURN_UNDEFINED(); + + return r->builtinExec(scope.engine, str); +} + +ReturnedValue RegExpPrototype::method_get_flags(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + ScopedObject o(scope, thisObject); + if (!o) + return scope.engine->throwTypeError(); + + QString result; + ScopedValue v(scope); + v = o->get(scope.engine->id_global()); + if (scope.hasException()) + return Encode::undefined(); + if (v->toBoolean()) + result += QLatin1Char('g'); + v = o->get(scope.engine->id_ignoreCase()); + if (scope.hasException()) + return Encode::undefined(); + if (v->toBoolean()) + result += QLatin1Char('i'); + v = o->get(scope.engine->id_multiline()); + if (scope.hasException()) + return Encode::undefined(); + if (v->toBoolean()) + result += QLatin1Char('m'); + v = o->get(scope.engine->id_unicode()); + if (scope.hasException()) + return Encode::undefined(); + if (v->toBoolean()) + result += QLatin1Char('u'); + v = o->get(scope.engine->id_sticky()); + if (scope.hasException()) + return Encode::undefined(); + if (v->toBoolean()) + result += QLatin1Char('y'); + return scope.engine->newString(result)->asReturnedValue(); +} + +ReturnedValue RegExpPrototype::method_get_global(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + Scoped<RegExpObject> re(scope, thisObject); + if (!re) { + if (thisObject->sameValue(*scope.engine->regExpPrototype())) + return Encode::undefined(); + return scope.engine->throwTypeError(); + } - scope.result = scope.engine->newString(r->toString()); + bool b = re->value()->flags & CompiledData::RegExp::RegExp_Global; + return Encode(b); } -void RegExpPrototype::method_compile(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue RegExpPrototype::method_get_ignoreCase(const FunctionObject *f, const Value *thisObject, const Value *, int) { - Scoped<RegExpObject> r(scope, callData->thisObject.as<RegExpObject>()); + Scope scope(f); + Scoped<RegExpObject> re(scope, thisObject); + if (!re) { + if (thisObject->sameValue(*scope.engine->regExpPrototype())) + return Encode::undefined(); + return scope.engine->throwTypeError(); + } + + bool b = re->value()->flags & CompiledData::RegExp::RegExp_IgnoreCase; + return Encode(b); +} + +static int advanceStringIndex(int index, const QString &str, bool unicode) +{ + if (unicode) { + if (index < str.length() - 1 && + str.at(index).isHighSurrogate() && + str.at(index + 1).isLowSurrogate()) + ++index; + } + ++index; + return index; +} + +static void advanceLastIndexOnEmptyMatch(ExecutionEngine *e, bool unicode, QV4::Object *rx, const String *matchString, const QString &str) +{ + Scope scope(e); + if (matchString->d()->length() == 0) { + QV4::ScopedValue v(scope, rx->get(scope.engine->id_lastIndex())); + int lastIndex = advanceStringIndex(v->toLength(), str, unicode); + if (!rx->put(scope.engine->id_lastIndex(), QV4::Value::fromInt32(lastIndex))) + scope.engine->throwTypeError(); + } +} + +ReturnedValue RegExpPrototype::method_match(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + ScopedObject rx(scope, thisObject); + if (!rx) + return scope.engine->throwTypeError(); + ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + bool global = ScopedValue(scope, rx->get(scope.engine->id_global()))->toBoolean(); + + if (!global) + return exec(scope.engine, rx, s); + + bool unicode = ScopedValue(scope, rx->get(scope.engine->id_unicode()))->toBoolean(); + + rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0)); + ScopedArrayObject a(scope, scope.engine->newArrayObject()); + uint n = 0; + + ScopedValue result(scope); + ScopedValue match(scope); + ScopedString matchString(scope); + ScopedValue v(scope); + while (1) { + result = exec(scope.engine, rx, s); + if (scope.hasException()) + return Encode::undefined(); + if (result->isNull()) { + if (!n) + return Encode::null(); + return a->asReturnedValue(); + } + Q_ASSERT(result->isObject()); + match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0)); + matchString = match->toString(scope.engine); + if (scope.hasException()) + return Encode::undefined(); + a->push_back(matchString); + advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString()); + ++n; + } +} + +ReturnedValue RegExpPrototype::method_get_multiline(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + Scoped<RegExpObject> re(scope, thisObject); + if (!re) { + if (thisObject->sameValue(*scope.engine->regExpPrototype())) + return Encode::undefined(); + return scope.engine->throwTypeError(); + } + + bool b = re->value()->flags & CompiledData::RegExp::RegExp_Multiline; + return Encode(b); +} + +ReturnedValue RegExpPrototype::method_replace(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + ScopedObject rx(scope, thisObject); + if (!rx) + return scope.engine->throwTypeError(); + + ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + + int lengthS = s->toQString().length(); + + ScopedString replaceValue(scope); + ScopedFunctionObject replaceFunction(scope, (argc > 1 ? argv[1] : Value::undefinedValue())); + bool functionalReplace = !!replaceFunction; + if (!functionalReplace) + replaceValue = (argc > 1 ? argv[1] : Value::undefinedValue()).toString(scope.engine); + + ScopedValue v(scope); + bool global = (v = rx->get(scope.engine->id_global()))->toBoolean(); + bool unicode = false; + if (global) { + unicode = (v = rx->get(scope.engine->id_unicode()))->toBoolean(); + if (!rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0))) + return scope.engine->throwTypeError(); + } + + ScopedArrayObject results(scope, scope.engine->newArrayObject()); + ScopedValue result(scope); + ScopedValue match(scope); + ScopedString matchString(scope); + while (1) { + result = exec(scope.engine, rx, s); + if (scope.hasException()) + return Encode::undefined(); + if (result->isNull()) + break; + results->push_back(result); + if (!global) + break; + match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0)); + matchString = match->toString(scope.engine); + if (scope.hasException()) + return Encode::undefined(); + advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString()); + } + QString accumulatedResult; + int nextSourcePosition = 0; + int resultsLength = results->getLength(); + ScopedObject resultObject(scope); + for (int i = 0; i < resultsLength; ++i) { + resultObject = results->get(PropertyKey::fromArrayIndex(i)); + if (scope.hasException()) + return Encode::undefined(); + + int nCaptures = resultObject->getLength(); + nCaptures = qMax(nCaptures - 1, 0); + match = resultObject->get(PropertyKey::fromArrayIndex(0)); + matchString = match->toString(scope.engine); + if (scope.hasException()) + return Encode::undefined(); + QString m = matchString->toQString(); + int matchLength = m.length(); + v = resultObject->get(scope.engine->id_index()); + int position = v->toInt32(); + position = qMax(qMin(position, lengthS), 0); + if (scope.hasException()) + return Encode::undefined(); + + int n = 1; + Scope innerScope(scope.engine); + JSCallData cData(scope, nCaptures + 3); + while (n <= nCaptures) { + v = resultObject->get(PropertyKey::fromArrayIndex(n)); + if (!v->isUndefined()) + cData->args[n] = v->toString(scope.engine); + ++n; + } + QString replacement; + if (functionalReplace) { + cData->args[0] = matchString; + cData->args[nCaptures + 1] = Encode(position); + cData->args[nCaptures + 2] = s; + ScopedValue replValue(scope, replaceFunction->call(cData)); + replacement = replValue->toQString(); + } else { + replacement = RegExp::getSubstitution(matchString->toQString(), s->toQString(), position, cData.args, nCaptures, replaceValue->toQString()); + } + if (scope.hasException()) + return Encode::undefined(); + if (position >= nextSourcePosition) { + accumulatedResult += s->toQString().midRef(nextSourcePosition, position - nextSourcePosition) + replacement; + nextSourcePosition = position + matchLength; + } + } + if (nextSourcePosition < lengthS) { + accumulatedResult += s->toQString().midRef(nextSourcePosition); + } + return scope.engine->newString(accumulatedResult)->asReturnedValue(); +} + +ReturnedValue RegExpPrototype::method_search(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + ScopedObject rx(scope, thisObject); + if (!rx) + return scope.engine->throwTypeError(); + + ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + + ScopedValue previousLastIndex(scope, rx->get(scope.engine->id_lastIndex())); + if (previousLastIndex->toNumber() != 0) { + if (!rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0))) + return scope.engine->throwTypeError(); + } + + ScopedValue result(scope, exec(scope.engine, rx, s)); + if (scope.hasException()) + return Encode::undefined(); + + ScopedValue currentLastIndex(scope, rx->get(scope.engine->id_lastIndex())); + if (!currentLastIndex->sameValue(previousLastIndex)) { + if (!rx->put(scope.engine->id_lastIndex(), previousLastIndex)) + return scope.engine->throwTypeError(); + } + + if (result->isNull()) + return Encode(-1); + ScopedObject o(scope, result); + Q_ASSERT(o); + return o->get(scope.engine->id_index()); +} + + +ReturnedValue RegExpPrototype::method_get_source(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + Scoped<RegExpObject> re(scope, thisObject); + if (!re) { + if (thisObject->sameValue(*scope.engine->regExpPrototype())) + return scope.engine->newString(QStringLiteral("(?:)"))->asReturnedValue(); + return scope.engine->throwTypeError(); + } + + return scope.engine->newString(re->toString())->asReturnedValue(); +} + +ReturnedValue RegExpPrototype::method_split(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + ScopedObject rx(scope, thisObject); + if (!rx) + return scope.engine->throwTypeError(); + + ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + + ScopedValue flagsValue(scope, rx->get(scope.engine->id_flags())); + ScopedString flags(scope, flagsValue->toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + QString flagsString = flags->toQString(); + if (!flagsString.contains(QLatin1Char('y'))) + flags = scope.engine->newString(flagsString + QLatin1Char('y')); + bool unicodeMatching = flagsString.contains(QLatin1Char('u')); + + const FunctionObject *C = rx->speciesConstructor(scope, scope.engine->regExpCtor()); + if (!C) + return Encode::undefined(); + + Value *args = scope.alloc(2); + args[0] = rx; + args[1] = flags; + ScopedObject splitter(scope, C->callAsConstructor(args, 2, f)); + if (scope.hasException()) + return Encode::undefined(); + + ScopedArrayObject A(scope, scope.engine->newArrayObject()); + uint lengthA = 0; + uint limit = argc < 2 ? UINT_MAX : argv[1].toUInt32(); + if (limit == 0) + return A->asReturnedValue(); + + QString S = s->toQString(); + int size = S.length(); + if (size == 0) { + ScopedValue z(scope, exec(scope.engine, splitter, s)); + if (z->isNull()) + A->push_back(s); + return A->asReturnedValue(); + } + + int p = 0; + int q = 0; + ScopedValue v(scope); + ScopedValue z(scope); + ScopedObject zz(scope); + ScopedString t(scope); + while (q < size) { + Value qq = Value::fromInt32(q); + if (!splitter->put(scope.engine->id_lastIndex(), qq)) + return scope.engine->throwTypeError(); + z = exec(scope.engine, splitter, s); + if (scope.hasException()) + return Encode::undefined(); + + if (z->isNull()) { + q = advanceStringIndex(q, S, unicodeMatching); + continue; + } + + v = splitter->get(scope.engine->id_lastIndex()); + int e = qMin(v->toInt32(), size); + if (e == p) { + q = advanceStringIndex(q, S, unicodeMatching); + continue; + } + QString T = S.mid(p, q - p); + t = scope.engine->newString(T); + A->push_back(t); + ++lengthA; + if (lengthA == limit) + return A->asReturnedValue(); + p = e; + zz = *z; + uint numberOfCaptures = qMax(zz->getLength() - 1, 0ll); + for (uint i = 1; i <= numberOfCaptures; ++i) { + v = zz->get(PropertyKey::fromArrayIndex(i)); + A->push_back(v); + ++lengthA; + if (lengthA == limit) + return A->asReturnedValue(); + } + q = p; + } + + QString T = S.mid(p); + t = scope.engine->newString(T); + A->push_back(t); + return A->asReturnedValue(); +} + +ReturnedValue RegExpPrototype::method_get_sticky(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + Scoped<RegExpObject> re(scope, thisObject); + if (!re) { + if (thisObject->sameValue(*scope.engine->regExpPrototype())) + return Encode::undefined(); + return scope.engine->throwTypeError(); + } + + bool b = re->value()->flags & CompiledData::RegExp::RegExp_Sticky; + return Encode(b); +} + +ReturnedValue RegExpPrototype::method_test(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Value res = Value::fromReturnedValue(method_exec(b, thisObject, argv, argc)); + return Encode(!res.isNull()); +} + +ReturnedValue RegExpPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + const Object *r = thisObject->as<Object>(); if (!r) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); + + ScopedValue v(scope); + v = r->get(scope.engine->id_source()); + ScopedString source(scope, v->toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + v = r->get(scope.engine->id_flags()); + ScopedString flags(scope, v->toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + + QString result = QLatin1Char('/') + source->toQString() + QLatin1Char('/') + flags->toQString(); + return Encode(scope.engine->newString(result)); +} - ScopedCallData cData(scope, callData->argc); - memcpy(cData->args, callData->args, callData->argc*sizeof(Value)); +ReturnedValue RegExpPrototype::method_get_unicode(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + Scoped<RegExpObject> re(scope, thisObject); + if (!re) { + if (thisObject->sameValue(*scope.engine->regExpPrototype())) + return Encode::undefined(); + return scope.engine->throwTypeError(); + } + + bool b = re->value()->flags & CompiledData::RegExp::RegExp_Unicode; + return Encode(b); +} + +ReturnedValue RegExpPrototype::method_compile(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>()); + if (!r) + return scope.engine->throwTypeError(); - scope.engine->regExpCtor()->as<FunctionObject>()->construct(scope, cData); - Scoped<RegExpObject> re(scope, scope.result.asReturnedValue()); + Scoped<RegExpObject> re(scope, scope.engine->regExpCtor()->callAsConstructor(argv, argc)); r->d()->value.set(scope.engine, re->value()); - r->d()->global = re->global(); - RETURN_UNDEFINED(); + return Encode::undefined(); } -template <int index> -void RegExpPrototype::method_get_lastMatch_n(const BuiltinFunction *, Scope &scope, CallData *) +template <uint index> +ReturnedValue RegExpPrototype::method_get_lastMatch_n(const FunctionObject *b, const Value *, const Value *, int) { + Scope scope(b); ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch()); - scope.result = lastMatch ? lastMatch->getIndexed(index) : Encode::undefined(); - if (scope.result.isUndefined()) - scope.result = scope.engine->newString(); + ScopedValue res(scope, lastMatch ? lastMatch->get(index) : Encode::undefined()); + if (res->isUndefined()) + res = scope.engine->newString(); + return res->asReturnedValue(); } -void RegExpPrototype::method_get_lastParen(const BuiltinFunction *, Scope &scope, CallData *) +ReturnedValue RegExpPrototype::method_get_lastParen(const FunctionObject *b, const Value *, const Value *, int) { + Scope scope(b); ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch()); - scope.result = lastMatch ? lastMatch->getIndexed(lastMatch->getLength() - 1) : Encode::undefined(); - if (scope.result.isUndefined()) - scope.result = scope.engine->newString(); + ScopedValue res(scope, lastMatch ? lastMatch->get(lastMatch->getLength() - 1) : Encode::undefined()); + if (res->isUndefined()) + res = scope.engine->newString(); + return res->asReturnedValue(); } -void RegExpPrototype::method_get_input(const BuiltinFunction *, Scope &scope, CallData *) +ReturnedValue RegExpPrototype::method_get_input(const FunctionObject *b, const Value *, const Value *, int) { - scope.result = static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastInput(); + return static_cast<RegExpCtor*>(b->engine()->regExpCtor())->lastInput()->asReturnedValue(); } -void RegExpPrototype::method_get_leftContext(const BuiltinFunction *, Scope &scope, CallData *) +ReturnedValue RegExpPrototype::method_get_leftContext(const FunctionObject *b, const Value *, const Value *, int) { + Scope scope(b); Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor()); QString lastInput = regExpCtor->lastInput()->toQString(); - scope.result = scope.engine->newString(lastInput.left(regExpCtor->lastMatchStart())); + return Encode(scope.engine->newString(lastInput.left(regExpCtor->lastMatchStart()))); } -void RegExpPrototype::method_get_rightContext(const BuiltinFunction *, Scope &scope, CallData *) +ReturnedValue RegExpPrototype::method_get_rightContext(const FunctionObject *b, const Value *, const Value *, int) { + Scope scope(b); Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor()); QString lastInput = regExpCtor->lastInput()->toQString(); - scope.result = scope.engine->newString(lastInput.mid(regExpCtor->lastMatchEnd())); + return Encode(scope.engine->newString(lastInput.mid(regExpCtor->lastMatchEnd()))); } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4regexpobject_p.h b/src/qml/jsruntime/qv4regexpobject_p.h index 6f54a4ab92..04b533e49d 100644 --- a/src/qml/jsruntime/qv4regexpobject_p.h +++ b/src/qml/jsruntime/qv4regexpobject_p.h @@ -55,11 +55,10 @@ #include "qv4context_p.h" #include "qv4functionobject_p.h" #include "qv4string_p.h" -#include "qv4codegen_p.h" -#include "qv4isel_p.h" #include "qv4managed_p.h" #include "qv4property_p.h" #include "qv4objectiterator_p.h" +#include "qv4regexp_p.h" #include <QtCore/QString> #include <QtCore/QHash> @@ -74,15 +73,17 @@ namespace QV4 { namespace Heap { #define RegExpObjectMembers(class, Member) \ - Member(class, Pointer, RegExp *, value) \ - Member(class, NoMark, bool, global) + Member(class, Pointer, RegExp *, value) DECLARE_HEAP_OBJECT(RegExpObject, Object) { - DECLARE_MARK_TABLE(RegExpObject); + DECLARE_MARKOBJECTS(RegExpObject) void init(); - void init(QV4::RegExp *value, bool global); + void init(QV4::RegExp *value); void init(const QRegExp &re); +#if QT_CONFIG(regularexpression) + void init(const QRegularExpression &re); +#endif }; #define RegExpCtorMembers(class, Member) \ @@ -92,7 +93,7 @@ DECLARE_HEAP_OBJECT(RegExpObject, Object) { Member(class, NoMark, int, lastMatchEnd) DECLARE_HEAP_OBJECT(RegExpCtor, FunctionObject) { - DECLARE_MARK_TABLE(RegExpCtor); + DECLARE_MARKOBJECTS(RegExpCtor); void init(QV4::ExecutionContext *scope); void clearLastMatch(); @@ -106,41 +107,53 @@ struct RegExpObject: Object { V4_INTERNALCLASS(RegExpObject) V4_PROTOTYPE(regExpPrototype) - // needs to be compatible with the flags in qv4jsir_p.h + // needs to be compatible with the flags in qv4compileddata_p.h enum Flags { RegExp_Global = 0x01, RegExp_IgnoreCase = 0x02, - RegExp_Multiline = 0x04 + RegExp_Multiline = 0x04, + RegExp_Unicode = 0x08, + RegExp_Sticky = 0x10 }; enum { Index_LastIndex = 0, - Index_Source = 1, - Index_Global = 2, - Index_IgnoreCase = 3, - Index_Multiline = 4, Index_ArrayIndex = Heap::ArrayObject::LengthPropertyIndex + 1, Index_ArrayInput = Index_ArrayIndex + 1 }; - Heap::RegExp *value() const { return d()->value; } - bool global() const { return d()->global; } + enum { NInlineProperties = 5 }; + void initProperties(); int lastIndex() const { - Q_ASSERT(Index_LastIndex == internalClass()->find(engine()->id_lastIndex())); + Q_ASSERT(internalClass()->verifyIndex(engine()->id_lastIndex()->propertyKey(), Index_LastIndex)); return propertyData(Index_LastIndex)->toInt32(); } void setLastIndex(int index) { - Q_ASSERT(Index_LastIndex == internalClass()->find(engine()->id_lastIndex())); - return setProperty(Index_LastIndex, Primitive::fromInt32(index)); + Q_ASSERT(internalClass()->verifyIndex(engine()->id_lastIndex()->propertyKey(), Index_LastIndex)); + if (!internalClass()->propertyData[Index_LastIndex].isWritable()) { + engine()->throwTypeError(); + return; + } + return setProperty(Index_LastIndex, Value::fromInt32(index)); } QRegExp toQRegExp() const; +#if QT_CONFIG(regularexpression) + QRegularExpression toQRegularExpression() const; +#endif QString toString() const; QString source() const; - uint flags() const; + + Heap::RegExp *value() const { return d()->value; } + uint flags() const { return d()->value->flags; } + bool global() const { return d()->value->global(); } + bool sticky() const { return d()->value->sticky(); } + bool unicode() const { return d()->value->unicode(); } + + ReturnedValue builtinExec(ExecutionEngine *engine, const String *s); }; struct RegExpCtor: FunctionObject @@ -152,25 +165,43 @@ struct RegExpCtor: FunctionObject int lastMatchStart() { return d()->lastMatchStart; } int lastMatchEnd() { return d()->lastMatchEnd; } - static void construct(const Managed *m, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; -struct RegExpPrototype: RegExpObject +struct RegExpPrototype: Object { void init(ExecutionEngine *engine, Object *ctor); - static void method_exec(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_test(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_compile(const BuiltinFunction *, Scope &scope, CallData *callData); - - template <int index> - static void method_get_lastMatch_n(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_lastParen(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_input(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_leftContext(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_rightContext(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_exec(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_flags(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_global(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_ignoreCase(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_match(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_multiline(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_replace(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_search(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_source(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_split(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_sticky(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_test(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_unicode(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + // Web extension + static ReturnedValue method_compile(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + // properties on the constructor, web extensions + template <uint index> + static ReturnedValue method_get_lastMatch_n(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_lastParen(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_input(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_leftContext(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_rightContext(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue execFirstMatch(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue exec(ExecutionEngine *engine, const Object *o, const String *s); }; } diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index df2af9de40..9753ee4b1d 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -51,13 +51,18 @@ #include "qv4lookup_p.h" #include "qv4function_p.h" #include "qv4numberobject_p.h" +#include "qv4regexp_p.h" +#include "qv4regexpobject_p.h" #include "private/qlocale_tools_p.h" #include "qv4scopedvalue_p.h" +#include "qv4jscall_p.h" #include <private/qv4qmlcontext_p.h> #include <private/qqmltypewrapper_p.h> #include <private/qqmlengine_p.h> #include <private/qqmljavascriptexpression_p.h> #include "qv4qobjectwrapper_p.h" +#include "qv4symbol_p.h" +#include "qv4generatorobject_p.h" #include <private/qv8engine_p.h> #endif @@ -220,13 +225,6 @@ void RuntimeCounters::count(const char *func, uint tag1, uint tag2) #ifndef V4_BOOTSTRAP -Runtime::Runtime() -{ -#define INIT_METHOD(returnvalue, name, args) runtimeMethods[name] = reinterpret_cast<void*>(&method_##name); -FOR_EACH_RUNTIME_METHOD(INIT_METHOD) -#undef INIT_METHOD -} - void RuntimeHelpers::numberToString(QString *result, double num, int radix) { Q_ASSERT(result); @@ -282,7 +280,7 @@ void RuntimeHelpers::numberToString(QString *result, double num, int radix) } double frac = num - ::floor(num); - num = Primitive::toInteger(num); + num = Value::toInteger(num); do { char c = (char)::fmod(num, radix); @@ -293,82 +291,113 @@ void RuntimeHelpers::numberToString(QString *result, double num, int radix) if (frac != 0) { result->append(QLatin1Char('.')); + double magnitude = 1; + double next = frac; do { - frac = frac * radix; - char c = (char)::floor(frac); + next *= radix; + const int floored = ::floor(next); + char c = char(floored); c = (c < 10) ? (c + '0') : (c - 10 + 'a'); result->append(QLatin1Char(c)); - frac = frac - ::floor(frac); - } while (frac != 0); + magnitude /= radix; + frac -= double(floored) * magnitude; + next -= double(floored); + + // The next digit still makes a difference + // if a value of "radix" for it would change frac. + // Otherwise we've reached the limit of numerical precision. + } while (frac > 0 && frac - magnitude != frac); } if (negative) result->prepend(QLatin1Char('-')); } -ReturnedValue Runtime::method_closure(ExecutionEngine *engine, int functionId) +ReturnedValue Runtime::Closure::call(ExecutionEngine *engine, int functionId) { - QV4::Function *clos = static_cast<CompiledData::CompilationUnit*>(engine->current->compilationUnit)->runtimeFunctions[functionId]; + QV4::Function *clos = static_cast<CompiledData::CompilationUnit*>(engine->currentStackFrame->v4Function->compilationUnit)->runtimeFunctions[functionId]; Q_ASSERT(clos); - return FunctionObject::createScriptFunction(engine->currentContext, clos)->asReturnedValue(); + ExecutionContext *current = static_cast<ExecutionContext *>(&engine->currentStackFrame->jsFrame->context); + if (clos->isGenerator()) + return GeneratorFunction::create(current, clos)->asReturnedValue(); + return FunctionObject::createScriptFunction(current, clos)->asReturnedValue(); } -ReturnedValue Runtime::method_deleteElement(ExecutionEngine *engine, const Value &base, const Value &index) +Bool Runtime::DeleteProperty_NoThrow::call(ExecutionEngine *engine, const Value &base, const Value &index) { Scope scope(engine); - ScopedObject o(scope, base); - if (o) { - uint n = index.asArrayIndex(); - if (n < UINT_MAX) { - return Encode((bool)o->deleteIndexedProperty(n)); - } - } + ScopedObject o(scope, base.toObject(engine)); + if (scope.engine->hasException) + return Encode::undefined(); + Q_ASSERT(o); - ScopedString name(scope, index.toString(engine)); - return method_deleteMemberString(engine, base, name); + ScopedPropertyKey key(scope, index.toPropertyKey(engine)); + if (engine->hasException) + return false; + return o->deleteProperty(key); } -ReturnedValue Runtime::method_deleteMember(ExecutionEngine *engine, const Value &base, int nameIndex) +ReturnedValue Runtime::DeleteProperty::call(ExecutionEngine *engine, QV4::Function *function, const QV4::Value &base, const QV4::Value &index) { - Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - return method_deleteMemberString(engine, base, name); + if (!Runtime::DeleteProperty_NoThrow::call(engine, base, index)) { + if (function->isStrict()) + engine->throwTypeError(); + return Encode(false); + } else { + return Encode(true); + } } -ReturnedValue Runtime::method_deleteMemberString(ExecutionEngine *engine, const Value &base, String *name) +Bool Runtime::DeleteName_NoThrow::call(ExecutionEngine *engine, int nameIndex) { Scope scope(engine); - ScopedObject obj(scope, base.toObject(engine)); - if (scope.engine->hasException) - return Encode::undefined(); - return Encode(obj->deleteProperty(name)); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + return static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).deleteProperty(name); } -ReturnedValue Runtime::method_deleteName(ExecutionEngine *engine, int nameIndex) +ReturnedValue Runtime::DeleteName::call(ExecutionEngine *engine, Function *function, int name) { - Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - return Encode(engine->currentContext->deleteProperty(name)); + if (!Runtime::DeleteName_NoThrow::call(engine, name)) { + if (function->isStrict()) + engine->throwTypeError(); + return Encode(false); + } else { + return Encode(true); + } } -QV4::ReturnedValue Runtime::method_instanceof(ExecutionEngine *engine, const Value &lval, const Value &rval) +QV4::ReturnedValue Runtime::Instanceof::call(ExecutionEngine *engine, const Value &lval, const Value &rval) { // 11.8.6, 5: rval must be an Object const Object *rhs = rval.as<Object>(); if (!rhs) return engine->throwTypeError(); - // 11.8.6, 7: call "HasInstance", which we term instanceOf, and return the result. - return rhs->instanceOf(lval); + const FunctionObject *f = rhs->as<FunctionObject>(); + // shortcut hasInstance evaluation. In this case we know that we are calling the regular hasInstance() + // method of the FunctionPrototype + if (f && f->d()->prototype() == engine->functionPrototype()->d() && !f->hasHasInstanceProperty()) + return Object::checkedInstanceOf(engine, f, lval); + + Scope scope(engine); + ScopedValue hasInstance(scope, rhs->get(engine->symbol_hasInstance())); + if (hasInstance->isUndefined()) + return rhs->instanceOf(lval); + FunctionObject *fHasInstance = hasInstance->as<FunctionObject>(); + if (!fHasInstance) + return engine->throwTypeError(); + + ScopedValue result(scope, fHasInstance->call(&rval, &lval, 1)); + return Encode(result->toBoolean()); } -QV4::ReturnedValue Runtime::method_in(ExecutionEngine *engine, const Value &left, const Value &right) +QV4::ReturnedValue Runtime::In::call(ExecutionEngine *engine, const Value &left, const Value &right) { Object *ro = right.objectValue(); if (!ro) return engine->throwTypeError(); Scope scope(engine); - ScopedString s(scope, left.toString(engine)); + ScopedPropertyKey s(scope, left.toPropertyKey(engine)); if (scope.hasException()) return Encode::undefined(); bool r = ro->hasProperty(s); @@ -378,12 +407,27 @@ QV4::ReturnedValue Runtime::method_in(ExecutionEngine *engine, const Value &left double RuntimeHelpers::stringToNumber(const QString &string) { const QStringRef s = QStringRef(&string).trimmed(); - if (s.startsWith(QLatin1String("0x")) || s.startsWith(QLatin1String("0X"))) - return s.toLong(0, 16); - bool ok; + if (s.startsWith(QLatin1Char('0'))) { + int base = -1; + if (s.startsWith(QLatin1String("0x")) || s.startsWith(QLatin1String("0X"))) + base = 16; + else if (s.startsWith(QLatin1String("0o")) || s.startsWith(QLatin1String("0O"))) + base = 8; + else if (s.startsWith(QLatin1String("0b")) || s.startsWith(QLatin1String("0B"))) + base = 2; + if (base > 0) { + bool ok = true; + qlonglong num; + num = s.mid(2).toLongLong(&ok, base); + if (!ok) + return qQNaN(); + return num; + } + } + bool ok = false; QByteArray ba = s.toLatin1(); const char *begin = ba.constData(); - const char *end = 0; + const char *end = nullptr; double d = qstrtod(begin, &end, &ok); if (end - begin != ba.size()) { if (ba == "Infinity" || ba == "+Infinity") @@ -405,32 +449,63 @@ Heap::String *RuntimeHelpers::stringFromNumber(ExecutionEngine *engine, double n ReturnedValue RuntimeHelpers::objectDefaultValue(const Object *object, int typeHint) { - if (typeHint == PREFERREDTYPE_HINT) { - if (object->as<DateObject>()) - typeHint = STRING_HINT; - else - typeHint = NUMBER_HINT; + ExecutionEngine *engine = object->internalClass()->engine; + if (engine->hasException) + return Encode::undefined(); + + String *hint; + switch (typeHint) { + case STRING_HINT: + hint = engine->id_string(); + break; + case NUMBER_HINT: + hint = engine->id_number(); + break; + default: + hint = engine->id_default(); + break; } - ExecutionEngine *engine = object->internalClass()->engine; + Scope scope(engine); + ScopedFunctionObject toPrimitive(scope, object->get(engine->symbol_toPrimitive())); if (engine->hasException) return Encode::undefined(); + if (toPrimitive) { + ScopedValue result(scope, toPrimitive->call(object, hint, 1)); + if (engine->hasException) + return Encode::undefined(); + if (!result->isPrimitive()) + return engine->throwTypeError(); + return result->asReturnedValue(); + } + + if (hint == engine->id_default()) + hint = engine->id_number(); + return ordinaryToPrimitive(engine, object, hint); +} + + +ReturnedValue RuntimeHelpers::ordinaryToPrimitive(ExecutionEngine *engine, const Object *object, String *typeHint) +{ + Q_ASSERT(!engine->hasException); String *meth1 = engine->id_toString(); String *meth2 = engine->id_valueOf(); - if (typeHint == NUMBER_HINT) + if (typeHint->propertyKey() == engine->id_number()->propertyKey()) { qSwap(meth1, meth2); + } else { + Q_ASSERT(typeHint->propertyKey() == engine->id_string()->propertyKey()); + } Scope scope(engine); - ScopedCallData callData(scope, 0); - callData->thisObject = *object; + ScopedValue result(scope); ScopedValue conv(scope, object->get(meth1)); if (FunctionObject *o = conv->as<FunctionObject>()) { - o->call(scope, callData); - if (scope.result.isPrimitive()) - return scope.result.asReturnedValue(); + result = o->call(object, nullptr, 0); + if (result->isPrimitive()) + return result->asReturnedValue(); } if (engine->hasException) @@ -438,28 +513,31 @@ ReturnedValue RuntimeHelpers::objectDefaultValue(const Object *object, int typeH conv = object->get(meth2); if (FunctionObject *o = conv->as<FunctionObject>()) { - o->call(scope, callData); - if (scope.result.isPrimitive()) - return scope.result.asReturnedValue(); + result = o->call(object, nullptr, 0); + if (result->isPrimitive()) + return result->asReturnedValue(); } return engine->throwTypeError(); } - Heap::Object *RuntimeHelpers::convertToObject(ExecutionEngine *engine, const Value &value) { Q_ASSERT(!value.isObject()); switch (value.type()) { case Value::Undefined_Type: + engine->throwTypeError(QLatin1String("Value is undefined and could not be converted to an object")); + return nullptr; case Value::Null_Type: - engine->throwTypeError(); - return 0; + engine->throwTypeError(QLatin1String("Value is null and could not be converted to an object")); + return nullptr; case Value::Boolean_Type: return engine->newBooleanObject(value.booleanValue()); case Value::Managed_Type: - Q_ASSERT(value.isString()); + Q_ASSERT(value.isStringOrSymbol()); + if (!value.isString()) + return engine->newSymbolObject(value.symbolValue()); return engine->newStringObject(value.stringValue()); case Value::Integer_Type: default: // double @@ -467,8 +545,9 @@ Heap::Object *RuntimeHelpers::convertToObject(ExecutionEngine *engine, const Val } } -Heap::String *RuntimeHelpers::convertToString(ExecutionEngine *engine, const Value &value) +Heap::String *RuntimeHelpers::convertToString(ExecutionEngine *engine, Value value, TypeHint hint) { + redo: switch (value.type()) { case Value::Empty_Type: Q_ASSERT(!"empty Value encountered"); @@ -482,15 +561,19 @@ Heap::String *RuntimeHelpers::convertToString(ExecutionEngine *engine, const Val return engine->id_true()->d(); else return engine->id_false()->d(); - case Value::Managed_Type: - if (String *s = value.stringValue()) - return s->d(); - { - Scope scope(engine); - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(value, STRING_HINT)); - Q_ASSERT(!prim->isManaged() || prim->isString()); - return RuntimeHelpers::convertToString(engine, prim); + case Value::Managed_Type: { + if (value.isString()) + return static_cast<const String &>(value).d(); + if (value.isSymbol()) { + engine->throwTypeError(QLatin1String("Cannot convert a symbol to a string.")); + return nullptr; } + value = Value::fromReturnedValue(RuntimeHelpers::toPrimitive(value, hint)); + Q_ASSERT(value.isPrimitive()); + if (value.isString()) + return static_cast<const String &>(value).d(); + goto redo; + } case Value::Integer_Type: return RuntimeHelpers::stringFromNumber(engine, value.int_32()); default: // double @@ -500,34 +583,9 @@ Heap::String *RuntimeHelpers::convertToString(ExecutionEngine *engine, const Val // This is slightly different from the method above, as // the + operator requires a slightly different conversion -static Heap::String *convert_to_string_add(ExecutionEngine *engine, const Value &value) +static Heap::String *convert_to_string_add(ExecutionEngine *engine, Value value) { - switch (value.type()) { - case Value::Empty_Type: - Q_ASSERT(!"empty Value encountered"); - Q_UNREACHABLE(); - case Value::Undefined_Type: - return engine->id_undefined()->d(); - case Value::Null_Type: - return engine->id_null()->d(); - case Value::Boolean_Type: - if (value.booleanValue()) - return engine->id_true()->d(); - else - return engine->id_false()->d(); - case Value::Managed_Type: - if (String *s = value.stringValue()) - return s->d(); - { - Scope scope(engine); - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(value, PREFERREDTYPE_HINT)); - return RuntimeHelpers::convertToString(engine, prim); - } - case Value::Integer_Type: - return RuntimeHelpers::stringFromNumber(engine, value.int_32()); - default: // double - return RuntimeHelpers::stringFromNumber(engine, value.doubleValue()); - } // switch + return RuntimeHelpers::convertToString(engine, value, PREFERREDTYPE_HINT); } QV4::ReturnedValue RuntimeHelpers::addHelper(ExecutionEngine *engine, const Value &left, const Value &right) @@ -547,56 +605,40 @@ QV4::ReturnedValue RuntimeHelpers::addHelper(ExecutionEngine *engine, const Valu pright = convert_to_string_add(engine, pright); sright = static_cast<String *>(pright.ptr); } - if (scope.engine->hasException) + if (engine->hasException) return Encode::undefined(); if (!sleft->d()->length()) return sright->asReturnedValue(); if (!sright->d()->length()) return sleft->asReturnedValue(); MemoryManager *mm = engine->memoryManager; - return (mm->alloc<String>(sleft->d(), sright->d()))->asReturnedValue(); + return (mm->alloc<ComplexString>(sleft->d(), sright->d()))->asReturnedValue(); } double x = RuntimeHelpers::toNumber(pleft); double y = RuntimeHelpers::toNumber(pright); return Encode(x + y); } -QV4::ReturnedValue Runtime::method_addString(ExecutionEngine *engine, const Value &left, const Value &right) +ReturnedValue Runtime::GetTemplateObject::call(Function *function, int index) { - Q_ASSERT(left.isString() || right.isString()); - - Scope scope(engine); - ScopedValue pleft(scope, left); - ScopedValue pright(scope, right); - String *sleft = pleft->stringValue(); - String *sright = pright->stringValue(); - - if (!sleft) { - pleft = convert_to_string_add(engine, pleft); - sleft = static_cast<String *>(pleft.ptr); - } - if (!sright) { - pright = convert_to_string_add(engine, pright); - sright = static_cast<String *>(pright.ptr); - } - if (scope.engine->hasException) - return Encode::undefined(); - if (!sleft->d()->length()) - return pright->asReturnedValue(); - if (!sright->d()->length()) - return pleft->asReturnedValue(); - MemoryManager *mm = engine->memoryManager; - return (mm->alloc<String>(sleft->d(), sright->d()))->asReturnedValue(); + return function->compilationUnit->templateObjectAt(index)->asReturnedValue(); } -void Runtime::method_setProperty(ExecutionEngine *engine, const Value &object, int nameIndex, const Value &value) +void Runtime::StoreProperty::call(ExecutionEngine *engine, const Value &object, int nameIndex, const Value &value) { Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - ScopedObject o(scope, object.toObject(engine)); - if (!o) - return; - o->put(name, value); + QV4::Function *v4Function = engine->currentStackFrame->v4Function; + ScopedString name(scope, v4Function->compilationUnit->runtimeStrings[nameIndex]); + ScopedObject o(scope, object); + if (!o) { + if (v4Function->isStrict()) { + engine->throwTypeError(); + return; + } + o = object.toObject(engine); + } + if ((!o || !o->put(name, value)) && v4Function->isStrict()) + engine->throwTypeError(); } static Q_NEVER_INLINE ReturnedValue getElementIntFallback(ExecutionEngine *engine, const Value &object, uint idx) @@ -629,12 +671,13 @@ static Q_NEVER_INLINE ReturnedValue getElementIntFallback(ExecutionEngine *engin return v->asReturnedValue(); } - return o->getIndexed(idx); + return o->get(idx); } static Q_NEVER_INLINE ReturnedValue getElementFallback(ExecutionEngine *engine, const Value &object, const Value &index) { - Q_ASSERT(index.asArrayIndex() == UINT_MAX); + Q_ASSERT(!index.isPositiveInt()); + Scope scope(engine); ScopedObject o(scope, object); @@ -648,18 +691,40 @@ static Q_NEVER_INLINE ReturnedValue getElementFallback(ExecutionEngine *engine, Q_ASSERT(!!o); // can't fail as null/undefined is covered above } - ScopedString name(scope, index.toString(engine)); + ScopedPropertyKey name(scope, index.toPropertyKey(engine)); if (scope.hasException()) return Encode::undefined(); return o->get(name); } -ReturnedValue Runtime::method_getElement(ExecutionEngine *engine, const Value &object, const Value &index) +ReturnedValue Runtime::LoadElement::call(ExecutionEngine *engine, const Value &object, const Value &index) +{ + if (index.isPositiveInt()) { + uint idx = static_cast<uint>(index.int_32()); + if (Heap::Base *b = object.heapObject()) { + if (b->internalClass->vtable->isObject) { + Heap::Object *o = static_cast<Heap::Object *>(b); + if (o->arrayData && o->arrayData->type == Heap::ArrayData::Simple) { + Heap::SimpleArrayData *s = o->arrayData.cast<Heap::SimpleArrayData>(); + if (idx < s->values.size) + if (!s->data(idx).isEmpty()) + return s->data(idx).asReturnedValue(); + } + } + } + return getElementIntFallback(engine, object, idx); + } + + return getElementFallback(engine, object, index); +} + +ReturnedValue Runtime::LoadElement_Traced::call(ExecutionEngine *engine, const Value &object, const Value &index, quint8 *traceSlot) { - uint idx; - if (index.asArrayIndex(idx)) { + *traceSlot |= quint8(ObservedTraceValues::ArrayWasAccessed); + if (index.isPositiveInt()) { + uint idx = static_cast<uint>(index.int_32()); if (Heap::Base *b = object.heapObject()) { - if (b->vtable()->isObject) { + if (b->internalClass->vtable->isObject) { Heap::Object *o = static_cast<Heap::Object *>(b); if (o->arrayData && o->arrayData->type == Heap::ArrayData::Simple) { Heap::SimpleArrayData *s = o->arrayData.cast<Heap::SimpleArrayData>(); @@ -669,42 +734,76 @@ ReturnedValue Runtime::method_getElement(ExecutionEngine *engine, const Value &o } } } + *traceSlot |= quint8(ObservedTraceValues::ArrayAccessNeededFallback); return getElementIntFallback(engine, object, idx); } + *traceSlot |= quint8(ObservedTraceValues::ArrayAccessNeededFallback); return getElementFallback(engine, object, index); } -static Q_NEVER_INLINE void setElementFallback(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value) +static Q_NEVER_INLINE bool setElementFallback(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value) { Scope scope(engine); - ScopedObject o(scope, object.toObject(engine)); + ScopedObject o(scope, object); + if (!o) { + if (engine->currentStackFrame->v4Function->isStrict()) { + engine->throwTypeError(); + return false; + } + + o = object.toObject(engine); + } if (engine->hasException) - return; + return false; - uint idx; - if (index.asArrayIndex(idx)) { + if (index.isPositiveInt()) { + uint idx = static_cast<uint>(index.int_32()); if (o->d()->arrayData && o->d()->arrayData->type == Heap::ArrayData::Simple) { Heap::SimpleArrayData *s = o->d()->arrayData.cast<Heap::SimpleArrayData>(); if (idx < s->values.size) { s->setData(engine, idx, value); - return; + return true; + } + } + return o->put(idx, value); + } + + ScopedPropertyKey name(scope, index.toPropertyKey(engine)); + if (engine->hasException) + return false; + return o->put(name, value); +} + +void Runtime::StoreElement::call(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value) +{ + if (index.isPositiveInt()) { + uint idx = static_cast<uint>(index.int_32()); + if (Heap::Base *b = object.heapObject()) { + if (b->internalClass->vtable->isObject) { + Heap::Object *o = static_cast<Heap::Object *>(b); + if (o->arrayData && o->arrayData->type == Heap::ArrayData::Simple) { + Heap::SimpleArrayData *s = o->arrayData.cast<Heap::SimpleArrayData>(); + if (idx < s->values.size) { + s->setData(engine, idx, value); + return; + } + } } } - o->putIndexed(idx, value); - return; } - ScopedString name(scope, index.toString(engine)); - o->put(name, value); + if (!setElementFallback(engine, object, index, value) && engine->currentStackFrame->v4Function->isStrict()) + engine->throwTypeError(); } -void Runtime::method_setElement(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value) +void Runtime::StoreElement_traced::call(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value, quint8 *traceSlot) { - uint idx; - if (index.asArrayIndex(idx)) { + *traceSlot |= quint8(ObservedTraceValues::ArrayWasAccessed); + if (index.isPositiveInt()) { + uint idx = static_cast<uint>(index.int_32()); if (Heap::Base *b = object.heapObject()) { - if (b->vtable()->isObject) { + if (b->internalClass->vtable->isObject) { Heap::Object *o = static_cast<Heap::Object *>(b); if (o->arrayData && o->arrayData->type == Heap::ArrayData::Simple) { Heap::SimpleArrayData *s = o->arrayData.cast<Heap::SimpleArrayData>(); @@ -717,40 +816,229 @@ void Runtime::method_setElement(ExecutionEngine *engine, const Value &object, co } } - return setElementFallback(engine, object, index, value); + *traceSlot |= quint8(ObservedTraceValues::ArrayAccessNeededFallback); + if (!setElementFallback(engine, object, index, value) && engine->currentStackFrame->v4Function->isStrict()) + engine->throwTypeError(); } -ReturnedValue Runtime::method_foreachIterator(ExecutionEngine *engine, const Value &in) +ReturnedValue Runtime::GetIterator::call(ExecutionEngine *engine, const Value &in, int iterator) { Scope scope(engine); - ScopedObject o(scope, (Object *)0); + ScopedObject o(scope, (Object *)nullptr); if (!in.isNullOrUndefined()) o = in.toObject(engine); - return engine->newForEachIteratorObject(o)->asReturnedValue(); + if (engine->hasException) + return Encode::undefined(); + if (iterator == static_cast<int>(QQmlJS::AST::ForEachType::Of)) { + if (!o) + return engine->throwTypeError(); + ScopedFunctionObject f(scope, o->get(engine->symbol_iterator())); + if (!f) + return engine->throwTypeError(); + JSCallData cData(scope, 0, nullptr, o); + ScopedObject it(scope, f->call(cData)); + if (!it) + return engine->throwTypeError(); + return it->asReturnedValue(); + } + return engine->newForInIteratorObject(o)->asReturnedValue(); } -ReturnedValue Runtime::method_foreachNextPropertyName(const Value &foreach_iterator) +ReturnedValue Runtime::IteratorNext::call(ExecutionEngine *engine, const Value &iterator, Value *value) { - Q_ASSERT(foreach_iterator.isObject()); + // if we throw an exception from here, return true, not undefined. This is to ensure iteratorDone is set to true + // and the stack unwinding won't close the iterator + Q_ASSERT(iterator.isObject()); - ForEachIteratorObject *it = static_cast<ForEachIteratorObject *>(foreach_iterator.objectValue()); - Q_ASSERT(it->as<ForEachIteratorObject>()); + Scope scope(engine); + ScopedFunctionObject f(scope, static_cast<const Object &>(iterator).get(engine->id_next())); + if (!f) { + engine->throwTypeError(); + return Encode(true); + } + JSCallData cData(scope, 0, nullptr, &iterator); + ScopedObject o(scope, f->call(cData)); + if (scope.hasException()) + return Encode(true); + if (!o) { + engine->throwTypeError(); + return Encode(true); + } + + ScopedValue d(scope, o->get(engine->id_done())); + if (scope.hasException()) + return Encode(true); + bool done = d->toBoolean(); + if (done) { + *value = Encode::undefined(); + return Encode(true); + } - return it->nextPropertyName(); + *value = o->get(engine->id_value()); + if (scope.hasException()) + return Encode(true); + return Encode(false); } +ReturnedValue Runtime::IteratorNextForYieldStar::call(ExecutionEngine *engine, const Value &received, const Value &iterator, Value *object) +{ + // the return value encodes how to continue the yield* iteration. + // true implies iteration is done, false for iteration to continue + // a return value of undefines is a special marker, that the iterator has been invoked with return() + + Scope scope(engine); + Q_ASSERT(iterator.isObject()); + + const Value *arg = &received; + bool returnCalled = false; + FunctionObject *f = nullptr; + if (engine->hasException) { + if (engine->exceptionValue->isEmpty()) { + // generator called with return() + *engine->exceptionValue = Encode::undefined(); + engine->hasException = false; + + ScopedValue ret(scope, static_cast<const Object &>(iterator).get(engine->id_return())); + if (ret->isUndefined()) { + // propagate return() + return Encode::undefined(); + } + returnCalled = true; + f = ret->as<FunctionObject>(); + } else { + // generator called with throw + ScopedValue exceptionValue(scope, *engine->exceptionValue); + *engine->exceptionValue = Encode::undefined(); + engine->hasException = false; + + ScopedValue t(scope, static_cast<const Object &>(iterator).get(engine->id_throw())); + if (engine->hasException) + return Encode::undefined(); + if (t->isUndefined()) { + // no throw method on the iterator + ScopedValue done(scope, Encode(false)); + IteratorClose::call(engine, iterator, done); + if (engine->hasException) + return Encode::undefined(); + return engine->throwTypeError(); + } + f = t->as<FunctionObject>(); + arg = exceptionValue; + } + } else { + // generator called with next() + ScopedFunctionObject next(scope, static_cast<const Object &>(iterator).get(engine->id_next())); + f = next->as<FunctionObject>(); + } + + if (!f) + return engine->throwTypeError(); -void Runtime::method_setActivationProperty(ExecutionEngine *engine, int nameIndex, const Value &value) + ScopedObject o(scope, f->call(&iterator, arg, 1)); + if (scope.hasException()) + return Encode(true); + if (!o) + return engine->throwTypeError(); + + ScopedValue d(scope, o->get(engine->id_done())); + if (scope.hasException()) + return Encode(true); + bool done = d->toBoolean(); + if (done) { + *object = o->get(engine->id_value()); + return returnCalled ? Encode::undefined() : Encode(true); + } + *object = o; + return Encode(false); +} + +ReturnedValue Runtime::IteratorClose::call(ExecutionEngine *engine, const Value &iterator, const Value &done) +{ + Q_ASSERT(iterator.isObject()); + Q_ASSERT(done.isBoolean()); + if (done.booleanValue()) + return Encode::undefined(); + + Scope scope(engine); + ScopedValue e(scope); + bool hadException = engine->hasException; + if (hadException) { + e = *engine->exceptionValue; + engine->hasException = false; + } + + auto originalCompletion = [=]() { + if (hadException) { + *engine->exceptionValue = e; + engine->hasException = hadException; + } + return Encode::undefined(); + }; + + ScopedValue ret(scope, static_cast<const Object &>(iterator).get(engine->id_return())); + ScopedObject o(scope); + if (!ret->isUndefined()) { + FunctionObject *f = ret->as<FunctionObject>(); + o = f->call(&iterator, nullptr, 0); + if (engine->hasException && !hadException) + return Encode::undefined(); + } + if (hadException || ret->isUndefined()) + return originalCompletion(); + + if (!o) + return engine->throwTypeError(); + + return originalCompletion(); +} + +ReturnedValue Runtime::DestructureRestElement::call(ExecutionEngine *engine, const Value &iterator) { + Q_ASSERT(iterator.isObject()); + Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - engine->currentContext->setProperty(name, value); + ScopedArrayObject array(scope, engine->newArrayObject()); + array->arrayCreate(); + uint index = 0; + while (1) { + ScopedValue n(scope); + ScopedValue done(scope, IteratorNext::call(engine, iterator, n)); + if (engine->hasException) + return Encode::undefined(); + Q_ASSERT(done->isBoolean()); + if (done->booleanValue()) + break; + array->arraySet(index, n); + ++index; + } + return array->asReturnedValue(); } -ReturnedValue Runtime::method_getProperty(ExecutionEngine *engine, const Value &object, int nameIndex) +void Runtime::StoreNameSloppy::call(ExecutionEngine *engine, int nameIndex, const Value &value) { Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + ExecutionContext::Error e = static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).setProperty(name, value); + + if (e == ExecutionContext::RangeError) + engine->globalObject->put(name, value); +} + +void Runtime::StoreNameStrict::call(ExecutionEngine *engine, int nameIndex, const Value &value) +{ + Scope scope(engine); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + ExecutionContext::Error e = static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).setProperty(name, value); + if (e == ExecutionContext::TypeError) + engine->throwTypeError(); + else if (e == ExecutionContext::RangeError) + engine->throwReferenceError(name); +} + +ReturnedValue Runtime::LoadProperty::call(ExecutionEngine *engine, const Value &object, int nameIndex) +{ + Scope scope(engine); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); ScopedObject o(scope, object); if (o) @@ -767,11 +1055,114 @@ ReturnedValue Runtime::method_getProperty(ExecutionEngine *engine, const Value & return o->get(name); } -ReturnedValue Runtime::method_getActivationProperty(ExecutionEngine *engine, int nameIndex) +ReturnedValue Runtime::LoadName::call(ExecutionEngine *engine, int nameIndex) +{ + Scope scope(engine); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + return static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).getProperty(name); +} + +static Object *getSuperBase(Scope &scope) +{ + if (scope.engine->currentStackFrame->jsFrame->thisObject.isEmpty()) { + scope.engine->throwReferenceError(QStringLiteral("Missing call to super()."), QString(), 0, 0); + return nullptr; + } + + ScopedFunctionObject f(scope, scope.engine->currentStackFrame->jsFrame->function); + ScopedObject homeObject(scope, f->getHomeObject()); + if (!homeObject) { + ScopedContext ctx(scope, static_cast<ExecutionContext *>(&scope.engine->currentStackFrame->jsFrame->context)); + Q_ASSERT(ctx); + while (ctx) { + if (CallContext *c = ctx->asCallContext()) { + f = c->d()->function; + QV4::Function *fn = f->function(); + if (fn && !fn->isArrowFunction() && !fn->isEval) + break; + } + ctx = ctx->d()->outer; + } + homeObject = f->getHomeObject(); + } + if (!homeObject) { + scope.engine->throwTypeError(); + return nullptr; + } + Q_ASSERT(homeObject); + ScopedObject proto(scope, homeObject->getPrototypeOf()); + if (!proto) { + scope.engine->throwTypeError(); + return nullptr; + } + return proto; +} + +ReturnedValue Runtime::LoadSuperProperty::call(ExecutionEngine *engine, const Value &property) +{ + Scope scope(engine); + Object *base = getSuperBase(scope); + if (!base) + return Encode::undefined(); + ScopedPropertyKey key(scope, property.toPropertyKey(engine)); + if (engine->hasException) + return Encode::undefined(); + return base->get(key, &engine->currentStackFrame->jsFrame->thisObject); +} + +void Runtime::StoreSuperProperty::call(ExecutionEngine *engine, const Value &property, const Value &value) { Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - return engine->currentContext->getProperty(name); + Object *base = getSuperBase(scope); + if (!base) + return; + ScopedPropertyKey key(scope, property.toPropertyKey(engine)); + if (engine->hasException) + return; + bool result = base->put(key, value, &engine->currentStackFrame->jsFrame->thisObject); + if (!result && engine->currentStackFrame->v4Function->isStrict()) + engine->throwTypeError(); +} + +ReturnedValue Runtime::LoadGlobalLookup::call(ExecutionEngine *engine, Function *f, int index) +{ + Lookup *l = f->compilationUnit->runtimeLookups + index; + return l->globalGetter(l, engine); +} + +ReturnedValue Runtime::GetLookup::call(ExecutionEngine *engine, Function *f, const Value &base, int index) +{ + Lookup *l = f->compilationUnit->runtimeLookups + index; + return l->getter(l, engine, base); +} + +void Runtime::SetLookupSloppy::call(Function *f, const Value &base, int index, const Value &value) +{ + ExecutionEngine *engine = f->internalClass->engine; + QV4::Lookup *l = f->compilationUnit->runtimeLookups + index; + l->setter(l, engine, const_cast<Value &>(base), value); +} + +void Runtime::SetLookupStrict::call(Function *f, const Value &base, int index, const Value &value) +{ + ExecutionEngine *engine = f->internalClass->engine; + QV4::Lookup *l = f->compilationUnit->runtimeLookups + index; + if (!l->setter(l, engine, const_cast<Value &>(base), value)) + engine->throwTypeError(); +} + +ReturnedValue Runtime::LoadSuperConstructor::call(ExecutionEngine *engine, const Value &t) +{ + if (engine->currentStackFrame->thisObject() != Value::emptyValue().asReturnedValue()) { + return engine->throwReferenceError(QStringLiteral("super() already called."), QString(), 0, 0); // ### fix line number + } + const FunctionObject *f = t.as<FunctionObject>(); + if (!f) + return engine->throwTypeError(); + Heap::Object *c = static_cast<const Object &>(t).getPrototypeOf(); + if (!c->vtable()->isFunctionObject || !static_cast<Heap::FunctionObject *>(c)->isConstructor()) + return engine->throwTypeError(); + return c->asReturnedValue(); } #endif // V4_BOOTSTRAP @@ -793,9 +1184,9 @@ uint RuntimeHelpers::equalHelper(const Value &x, const Value &y) double dx = RuntimeHelpers::toNumber(x); return dx == y.asDouble(); } else if (x.isBoolean()) { - return Runtime::method_compareEqual(Primitive::fromDouble((double) x.booleanValue()), y); + return Runtime::CompareEqual::call(Value::fromDouble((double) x.booleanValue()), y); } else if (y.isBoolean()) { - return Runtime::method_compareEqual(x, Primitive::fromDouble((double) y.booleanValue())); + return Runtime::CompareEqual::call(x, Value::fromDouble((double) y.booleanValue())); } else { #ifdef V4_BOOTSTRAP Q_UNIMPLEMENTED(); @@ -805,11 +1196,11 @@ uint RuntimeHelpers::equalHelper(const Value &x, const Value &y) if (yo && (x.isNumber() || x.isString())) { Scope scope(yo->engine()); ScopedValue py(scope, RuntimeHelpers::objectDefaultValue(yo, PREFERREDTYPE_HINT)); - return Runtime::method_compareEqual(x, py); + return Runtime::CompareEqual::call(x, py); } else if (xo && (y.isNumber() || y.isString())) { Scope scope(xo->engine()); ScopedValue px(scope, RuntimeHelpers::objectDefaultValue(xo, PREFERREDTYPE_HINT)); - return Runtime::method_compareEqual(px, y); + return Runtime::CompareEqual::call(px, y); } #endif } @@ -832,7 +1223,7 @@ Bool RuntimeHelpers::strictEqual(const Value &x, const Value &y) return false; } -QV4::Bool Runtime::method_compareGreaterThan(const Value &l, const Value &r) +QV4::Bool Runtime::CompareGreaterThan::call(const Value &l, const Value &r) { TRACE2(l, r); if (l.isInteger() && r.isInteger()) @@ -846,7 +1237,7 @@ QV4::Bool Runtime::method_compareGreaterThan(const Value &l, const Value &r) Q_UNIMPLEMENTED(); return false; #else - return sr->compare(sl); + return sr->lessThan(sl); #endif } @@ -860,7 +1251,7 @@ QV4::Bool Runtime::method_compareGreaterThan(const Value &l, const Value &r) QV4::Scope scope(e); QV4::ScopedValue pl(scope, lo ? RuntimeHelpers::objectDefaultValue(lo, QV4::NUMBER_HINT) : l.asReturnedValue()); QV4::ScopedValue pr(scope, ro ? RuntimeHelpers::objectDefaultValue(ro, QV4::NUMBER_HINT) : r.asReturnedValue()); - return Runtime::method_compareGreaterThan(pl, pr); + return Runtime::CompareGreaterThan::call(pl, pr); #endif } @@ -869,7 +1260,7 @@ QV4::Bool Runtime::method_compareGreaterThan(const Value &l, const Value &r) return dl > dr; } -QV4::Bool Runtime::method_compareLessThan(const Value &l, const Value &r) +QV4::Bool Runtime::CompareLessThan::call(const Value &l, const Value &r) { TRACE2(l, r); if (l.isInteger() && r.isInteger()) @@ -883,7 +1274,7 @@ QV4::Bool Runtime::method_compareLessThan(const Value &l, const Value &r) Q_UNIMPLEMENTED(); return false; #else - return sl->compare(sr); + return sl->lessThan(sr); #endif } @@ -897,7 +1288,7 @@ QV4::Bool Runtime::method_compareLessThan(const Value &l, const Value &r) QV4::Scope scope(e); QV4::ScopedValue pl(scope, lo ? RuntimeHelpers::objectDefaultValue(lo, QV4::NUMBER_HINT) : l.asReturnedValue()); QV4::ScopedValue pr(scope, ro ? RuntimeHelpers::objectDefaultValue(ro, QV4::NUMBER_HINT) : r.asReturnedValue()); - return Runtime::method_compareLessThan(pl, pr); + return Runtime::CompareLessThan::call(pl, pr); #endif } @@ -906,7 +1297,7 @@ QV4::Bool Runtime::method_compareLessThan(const Value &l, const Value &r) return dl < dr; } -QV4::Bool Runtime::method_compareGreaterEqual(const Value &l, const Value &r) +QV4::Bool Runtime::CompareGreaterEqual::call(const Value &l, const Value &r) { TRACE2(l, r); if (l.isInteger() && r.isInteger()) @@ -920,7 +1311,7 @@ QV4::Bool Runtime::method_compareGreaterEqual(const Value &l, const Value &r) Q_UNIMPLEMENTED(); return false; #else - return !sl->compare(sr); + return !sl->lessThan(sr); #endif } @@ -934,7 +1325,7 @@ QV4::Bool Runtime::method_compareGreaterEqual(const Value &l, const Value &r) QV4::Scope scope(e); QV4::ScopedValue pl(scope, lo ? RuntimeHelpers::objectDefaultValue(lo, QV4::NUMBER_HINT) : l.asReturnedValue()); QV4::ScopedValue pr(scope, ro ? RuntimeHelpers::objectDefaultValue(ro, QV4::NUMBER_HINT) : r.asReturnedValue()); - return Runtime::method_compareGreaterEqual(pl, pr); + return Runtime::CompareGreaterEqual::call(pl, pr); #endif } @@ -943,7 +1334,7 @@ QV4::Bool Runtime::method_compareGreaterEqual(const Value &l, const Value &r) return dl >= dr; } -QV4::Bool Runtime::method_compareLessEqual(const Value &l, const Value &r) +QV4::Bool Runtime::CompareLessEqual::call(const Value &l, const Value &r) { TRACE2(l, r); if (l.isInteger() && r.isInteger()) @@ -957,7 +1348,7 @@ QV4::Bool Runtime::method_compareLessEqual(const Value &l, const Value &r) Q_UNIMPLEMENTED(); return false; #else - return !sr->compare(sl); + return !sr->lessThan(sl); #endif } @@ -971,7 +1362,7 @@ QV4::Bool Runtime::method_compareLessEqual(const Value &l, const Value &r) QV4::Scope scope(e); QV4::ScopedValue pl(scope, lo ? RuntimeHelpers::objectDefaultValue(lo, QV4::NUMBER_HINT) : l.asReturnedValue()); QV4::ScopedValue pr(scope, ro ? RuntimeHelpers::objectDefaultValue(ro, QV4::NUMBER_HINT) : r.asReturnedValue()); - return Runtime::method_compareLessEqual(pl, pr); + return Runtime::CompareLessEqual::call(pl, pr); #endif } @@ -981,259 +1372,317 @@ QV4::Bool Runtime::method_compareLessEqual(const Value &l, const Value &r) } #ifndef V4_BOOTSTRAP -Bool Runtime::method_compareInstanceof(ExecutionEngine *engine, const Value &left, const Value &right) +Bool Runtime::CompareInstanceof::call(ExecutionEngine *engine, const Value &left, const Value &right) { TRACE2(left, right); Scope scope(engine); - ScopedValue v(scope, method_instanceof(engine, left, right)); + ScopedValue v(scope, Instanceof::call(engine, left, right)); return v->booleanValue(); } -uint Runtime::method_compareIn(ExecutionEngine *engine, const Value &left, const Value &right) +uint Runtime::CompareIn::call(ExecutionEngine *engine, const Value &left, const Value &right) { TRACE2(left, right); Scope scope(engine); - ScopedValue v(scope, method_in(engine, left, right)); + ScopedValue v(scope, In::call(engine, left, right)); return v->booleanValue(); } -ReturnedValue Runtime::method_callGlobalLookup(ExecutionEngine *engine, uint index, CallData *callData) +ReturnedValue Runtime::CallGlobalLookup::call(ExecutionEngine *engine, uint index, Value argv[], int argc) { - Scope scope(engine); - Q_ASSERT(callData->thisObject.isUndefined()); - - Lookup *l = engine->current->lookups + index; - ScopedFunctionObject o(scope, l->globalGetter(l, engine)); - if (!o) + Lookup *l = engine->currentStackFrame->v4Function->compilationUnit->runtimeLookups + index; + Value function = Value::fromReturnedValue(l->globalGetter(l, engine)); + if (!function.isFunctionObject()) return engine->throwTypeError(); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[l->nameIndex]); - if (o->d() == scope.engine->evalFunction()->d() && name->equals(scope.engine->id_eval())) { - static_cast<EvalFunction *>(o.getPointer())->evalCall(scope, callData, true); - } else { - o->call(scope, callData); - } - - return scope.result.asReturnedValue(); + Value thisObject = Value::undefinedValue(); + return static_cast<FunctionObject &>(function).call(&thisObject, argv, argc); } - -ReturnedValue Runtime::method_callActivationProperty(ExecutionEngine *engine, int nameIndex, CallData *callData) +ReturnedValue Runtime::CallPossiblyDirectEval::call(ExecutionEngine *engine, Value *argv, int argc) { - Q_ASSERT(callData->thisObject.isUndefined()); Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); + ScopedValue thisObject(scope); - ScopedObject base(scope); - ScopedValue func(scope, engine->currentContext->getPropertyAndBase(name, base.getRef())); - if (scope.engine->hasException) + ExecutionContext &ctx = static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context); + ScopedFunctionObject function(scope, ctx.getPropertyAndBase(engine->id_eval(), thisObject)); + if (engine->hasException) return Encode::undefined(); - if (base) - callData->thisObject = base; - - FunctionObject *o = func->as<FunctionObject>(); - if (!o) { + if (!function) { QString objectAsString = QStringLiteral("[null]"); - if (base) - objectAsString = ScopedValue(scope, base.asReturnedValue())->toQStringNoThrow(); - QString msg = QStringLiteral("Property '%1' of object %2 is not a function").arg(name->toQString()).arg(objectAsString); + if (!thisObject->isUndefined()) + objectAsString = thisObject->toQStringNoThrow(); + QString msg = QStringLiteral("Property 'eval' of object %2 is not a function").arg(objectAsString); return engine->throwTypeError(msg); } - if (o->d() == scope.engine->evalFunction()->d() && name->equals(scope.engine->id_eval())) { - static_cast<EvalFunction *>(o)->evalCall(scope, callData, true); - } else { - o->call(scope, callData); - } + if (function->d() == engine->evalFunction()->d()) + return static_cast<EvalFunction *>(function.getPointer())->evalCall(thisObject, argv, argc, true); - return scope.result.asReturnedValue(); + return function->call(thisObject, argv, argc); } -ReturnedValue Runtime::method_callQmlScopeObjectProperty(ExecutionEngine *engine, int propertyIndex, CallData *callData) +ReturnedValue Runtime::CallName::call(ExecutionEngine *engine, int nameIndex, Value *argv, int argc) { Scope scope(engine); - ScopedFunctionObject o(scope, method_getQmlScopeObjectProperty(engine, callData->thisObject, propertyIndex, /*captureRequired*/true)); - if (!o) { - QString error = QStringLiteral("Property '%1' of scope object is not a function").arg(propertyIndex); - return engine->throwTypeError(error); - } + ScopedValue thisObject(scope); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); - o->call(scope, callData); - return scope.result.asReturnedValue(); -} + ExecutionContext &ctx = static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context); + ScopedFunctionObject f(scope, ctx.getPropertyAndBase(name, thisObject)); + if (engine->hasException) + return Encode::undefined(); -ReturnedValue Runtime::method_callQmlContextObjectProperty(ExecutionEngine *engine, int propertyIndex, CallData *callData) -{ - Scope scope(engine); - ScopedFunctionObject o(scope, method_getQmlContextObjectProperty(engine, callData->thisObject, propertyIndex, /*captureRequired*/true)); - if (!o) { - QString error = QStringLiteral("Property '%1' of context object is not a function").arg(propertyIndex); - return engine->throwTypeError(error); + if (!f) { + QString objectAsString = QStringLiteral("[null]"); + if (!thisObject->isUndefined()) + objectAsString = thisObject->toQStringNoThrow(); + QString msg = QStringLiteral("Property '%1' of object %2 is not a function") + .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]->toQString(), + objectAsString); + return engine->throwTypeError(msg); } - o->call(scope, callData); - return scope.result.asReturnedValue(); + return f->call(thisObject, argv, argc); } -ReturnedValue Runtime::method_callProperty(ExecutionEngine *engine, int nameIndex, CallData *callData) +ReturnedValue Runtime::CallProperty::call(ExecutionEngine *engine, const Value &baseRef, int nameIndex, Value *argv, int argc) { + const Value *base = &baseRef; Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - ScopedObject baseObject(scope, callData->thisObject); - if (!baseObject) { - Q_ASSERT(!callData->thisObject.isEmpty()); - if (callData->thisObject.isNullOrUndefined()) { - QString message = QStringLiteral("Cannot call method '%1' of %2").arg(name->toQString()).arg(callData->thisObject.toQStringNoThrow()); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + ScopedObject lookupObject(scope, base); + + if (!lookupObject) { + Q_ASSERT(!base->isEmpty()); + if (base->isNullOrUndefined()) { + QString message = QStringLiteral("Cannot call method '%1' of %2") + .arg(name->toQString(), base->toQStringNoThrow()); return engine->throwTypeError(message); } - baseObject = RuntimeHelpers::convertToObject(scope.engine, callData->thisObject); - if (!baseObject) // type error - return Encode::undefined(); - callData->thisObject = baseObject.asReturnedValue(); + if (base->isManaged()) { + const Managed *m = static_cast<const Managed *>(base); + lookupObject = m->internalClass()->prototype; + Q_ASSERT(m->internalClass()->prototype); + } else { + lookupObject = RuntimeHelpers::convertToObject(engine, *base); + if (engine->hasException) // type error + return Encode::undefined(); + if (!engine->currentStackFrame->v4Function->isStrict()) + base = lookupObject; + } } - ScopedFunctionObject o(scope, baseObject->get(name)); - if (o) { - o->call(scope, callData); - return scope.result.asReturnedValue(); - } else { - QString error = QStringLiteral("Property '%1' of object %2 is not a function").arg(name->toQString(), callData->thisObject.toQStringNoThrow()); + ScopedFunctionObject f(scope, static_cast<Object *>(lookupObject)->get(name)); + + if (!f) { + QString error = QStringLiteral("Property '%1' of object %2 is not a function") + .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]->toQString(), + base->toQStringNoThrow()); return engine->throwTypeError(error); } + return f->call(base, argv, argc); } -ReturnedValue Runtime::method_callPropertyLookup(ExecutionEngine *engine, uint index, CallData *callData) +ReturnedValue Runtime::CallPropertyLookup::call(ExecutionEngine *engine, const Value &base, uint index, Value *argv, int argc) { - Lookup *l = engine->current->lookups + index; - Value v; - v = l->getter(l, engine, callData->thisObject); - Object *o = v.objectValue(); - if (Q_LIKELY(o)) { - Scope scope(engine); - o->call(scope, callData); - return scope.result.asReturnedValue(); - } - return engine->throwTypeError(); + Lookup *l = engine->currentStackFrame->v4Function->compilationUnit->runtimeLookups + index; + // ok to have the value on the stack here + Value f = Value::fromReturnedValue(l->getter(l, engine, base)); + + if (!f.isFunctionObject()) + return engine->throwTypeError(); + + return static_cast<FunctionObject &>(f).call(&base, argv, argc); } -ReturnedValue Runtime::method_callElement(ExecutionEngine *engine, const Value &index, CallData *callData) +ReturnedValue Runtime::CallElement::call(ExecutionEngine *engine, const Value &baseRef, const Value &index, Value *argv, int argc) { + const Value *base = &baseRef; Scope scope(engine); - ScopedObject baseObject(scope, callData->thisObject.toObject(engine)); - ScopedString s(scope, index.toString(engine)); + ScopedValue thisObject(scope, base->toObject(engine)); + base = thisObject; - if (scope.engine->hasException) + ScopedPropertyKey str(scope, index.toPropertyKey(engine)); + if (engine->hasException) return Encode::undefined(); - callData->thisObject = baseObject; - ScopedObject o(scope, baseObject->get(s)); - if (!o) + ScopedFunctionObject f(scope, static_cast<const Object *>(base)->get(str)); + if (!f) return engine->throwTypeError(); - o->call(scope, callData); - return scope.result.asReturnedValue(); + return f->call(base, argv, argc); } -ReturnedValue Runtime::method_callValue(ExecutionEngine *engine, const Value &func, CallData *callData) +ReturnedValue Runtime::CallValue::call(ExecutionEngine *engine, const Value &func, Value *argv, int argc) { - if (Object *o = func.objectValue()) { - Scope scope(engine); - o->call(scope, callData); - return scope.result.asReturnedValue(); - } - return engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); + if (!func.isFunctionObject()) + return engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); + Value undef = Value::undefinedValue(); + return static_cast<const FunctionObject &>(func).call(&undef, argv, argc); } +ReturnedValue Runtime::CallWithReceiver::call(ExecutionEngine *engine, const Value &func, + const Value &thisObject, Value argv[], int argc) +{ + if (!func.isFunctionObject()) + return engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); + return static_cast<const FunctionObject &>(func).call(&thisObject, argv, argc); +} -ReturnedValue Runtime::method_constructGlobalLookup(ExecutionEngine *engine, uint index, CallData *callData) +ReturnedValue Runtime::CallQmlScopeObjectProperty::call(ExecutionEngine *engine, const Value &base, + int propertyIndex, Value argv[], int argc) { Scope scope(engine); - Q_ASSERT(callData->thisObject.isUndefined()); + ScopedFunctionObject fo(scope, LoadQmlScopeObjectProperty::call(engine, base, propertyIndex, + /*captureRequired*/true)); + if (!fo) { + QString error = QStringLiteral("Property '%1' of scope object is not a function").arg(propertyIndex); + return engine->throwTypeError(error); + } - Lookup *l = engine->current->lookups + index; - ScopedObject f(scope, l->globalGetter(l, engine)); - if (f) { - f->construct(scope, callData); - return scope.result.asReturnedValue(); - } else { - return engine->throwTypeError(); + QObject *qmlScopeObj = static_cast<const QmlContext *>(&base)->d()->qml()->scopeObject; + ScopedValue qmlScopeValue(scope, QObjectWrapper::wrap(engine, qmlScopeObj)); + return fo->call(qmlScopeValue, argv, argc); +} + +ReturnedValue Runtime::CallQmlContextObjectProperty::call(ExecutionEngine *engine, + const Value &base, + int propertyIndex, + Value argv[], + int argc) +{ + Scope scope(engine); + ScopedFunctionObject fo(scope, LoadQmlContextObjectProperty::call(engine, base, propertyIndex, + /*captureRequired*/true)); + if (!fo) { + QString error = QStringLiteral("Property '%1' of context object is not a function").arg(propertyIndex); + return engine->throwTypeError(error); } + + QObject *qmlContextObj = static_cast<const QmlContext *>(&base)->d()->qml()->context->contextData()->contextObject; + ScopedValue qmlContextValue(scope, QObjectWrapper::wrap(engine, qmlContextObj)); + return fo->call(qmlContextValue, argv, argc); } +struct CallArgs { + Value *argv; + int argc; +}; -ReturnedValue Runtime::method_constructActivationProperty(ExecutionEngine *engine, int nameIndex, CallData *callData) +static CallArgs createSpreadArguments(Scope &scope, Value *argv, int argc) { - Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - ScopedValue func(scope, engine->currentContext->getProperty(name)); - if (scope.engine->hasException) - return Encode::undefined(); + ScopedValue it(scope); + ScopedValue done(scope); - Object *f = func->as<Object>(); - if (!f) - return engine->throwTypeError(); + int argCount = 0; - f->construct(scope, callData); - return scope.result.asReturnedValue(); + Value *v = scope.alloc<Scope::Uninitialized>(); + Value *arguments = v; + for (int i = 0; i < argc; ++i) { + if (!argv[i].isEmpty()) { + *v = argv[i]; + ++argCount; + v = scope.alloc<Scope::Uninitialized>(); + continue; + } + // spread element + ++i; + it = Runtime::GetIterator::call(scope.engine, argv[i], /* ForInIterator */ 1); + if (scope.engine->hasException) + return { nullptr, 0 }; + while (1) { + done = Runtime::IteratorNext::call(scope.engine, it, v); + if (scope.engine->hasException) + return { nullptr, 0 }; + Q_ASSERT(done->isBoolean()); + if (done->booleanValue()) + break; + ++argCount; + v = scope.alloc<Scope::Uninitialized>(); + } + } + return { arguments, argCount }; } -ReturnedValue Runtime::method_constructValue(ExecutionEngine *engine, const Value &func, CallData *callData) +ReturnedValue Runtime::CallWithSpread::call(ExecutionEngine *engine, const Value &function, const Value &thisObject, Value *argv, int argc) { - const Object *f = func.as<Object>(); - if (!f) + Q_ASSERT(argc >= 1); + if (!function.isFunctionObject()) return engine->throwTypeError(); Scope scope(engine); - f->construct(scope, callData); - return scope.result.asReturnedValue(); + CallArgs arguments = createSpreadArguments(scope, argv, argc); + if (engine->hasException) + return Encode::undefined(); + + return static_cast<const FunctionObject &>(function).call(&thisObject, arguments.argv, arguments.argc); +} + +ReturnedValue Runtime::Construct::call(ExecutionEngine *engine, const Value &function, const Value &newTarget, Value *argv, int argc) +{ + if (!function.isFunctionObject()) + return engine->throwTypeError(); + + return static_cast<const FunctionObject &>(function).callAsConstructor(argv, argc, &newTarget); } -ReturnedValue Runtime::method_constructProperty(ExecutionEngine *engine, int nameIndex, CallData *callData) +ReturnedValue Runtime::ConstructWithSpread::call(ExecutionEngine *engine, const Value &function, const Value &newTarget, Value *argv, int argc) { + if (!function.isFunctionObject()) + return engine->throwTypeError(); + Scope scope(engine); - ScopedObject thisObject(scope, callData->thisObject.toObject(engine)); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - if (scope.engine->hasException) + CallArgs arguments = createSpreadArguments(scope, argv, argc); + if (engine->hasException) return Encode::undefined(); - ScopedObject f(scope, thisObject->get(name)); - if (f) { - Scope scope(engine); - f->construct(scope, callData); - return scope.result.asReturnedValue(); - } else { - return engine->throwTypeError(); - } + return static_cast<const FunctionObject &>(function).callAsConstructor(arguments.argv, arguments.argc, &newTarget); } -ReturnedValue Runtime::method_constructPropertyLookup(ExecutionEngine *engine, uint index, CallData *callData) +ReturnedValue Runtime::TailCall::call(CppStackFrame *frame, ExecutionEngine *engine) { - Lookup *l = engine->current->lookups + index; - Value v; - v = l->getter(l, engine, callData->thisObject); - Object *o = v.objectValue(); - if (Q_LIKELY(o)) { - Scope scope(engine); - o->construct(scope, callData); - return scope.result.asReturnedValue(); + // IMPORTANT! The JIT assumes that this method has the same amount (or less) arguments than + // the jitted function, so it can safely do a tail call. + + Value *tos = engine->jsStackTop; + const Value &function = tos[StackOffsets::tailCall_function]; + const Value &thisObject = tos[StackOffsets::tailCall_thisObject]; + Value *argv = reinterpret_cast<Value *>(frame->jsFrame) + tos[StackOffsets::tailCall_argv].int_32(); + int argc = tos[StackOffsets::tailCall_argc].int_32(); + Q_ASSERT(argc >= 0); + + if (!function.isFunctionObject()) + return engine->throwTypeError(); + + const FunctionObject &fo = static_cast<const FunctionObject &>(function); + if (!frame->callerCanHandleTailCall || !fo.canBeTailCalled() || engine->debugger() + || unsigned(argc) > fo.formalParameterCount()) { + // Cannot tailcall, do a normal call: + return fo.call(&thisObject, argv, argc); } - return engine->throwTypeError(); -} + memcpy(frame->jsFrame->args, argv, argc * sizeof(Value)); + frame->init(engine, fo.function(), frame->jsFrame->args, argc, frame->callerCanHandleTailCall); + frame->setupJSFrame(frame->savedStackTop, fo, fo.scope(), thisObject, Primitive::undefinedValue()); + engine->jsStackTop = frame->savedStackTop + frame->requiredJSStackFrameSize(); + frame->pendingTailCall = true; + return Encode::undefined(); +} -void Runtime::method_throwException(ExecutionEngine *engine, const Value &value) +void Runtime::ThrowException::call(ExecutionEngine *engine, const Value &value) { if (!value.isEmpty()) engine->throwError(value); } -ReturnedValue Runtime::method_typeofValue(ExecutionEngine *engine, const Value &value) +ReturnedValue Runtime::TypeofValue::call(ExecutionEngine *engine, const Value &value) { Scope scope(engine); ScopedString res(scope); @@ -1250,6 +1699,8 @@ ReturnedValue Runtime::method_typeofValue(ExecutionEngine *engine, const Value & case Value::Managed_Type: if (value.isString()) res = engine->id_string(); + else if (value.isSymbol()) + res = engine->id_symbol(); else if (value.objectValue()->as<FunctionObject>()) res = engine->id_function(); else @@ -1262,307 +1713,359 @@ ReturnedValue Runtime::method_typeofValue(ExecutionEngine *engine, const Value & return res.asReturnedValue(); } -QV4::ReturnedValue Runtime::method_typeofName(ExecutionEngine *engine, int nameIndex) +QV4::ReturnedValue Runtime::TypeofName::call(ExecutionEngine *engine, int nameIndex) { Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - ScopedValue prop(scope, engine->currentContext->getProperty(name)); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + ScopedValue prop(scope, static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).getProperty(name)); // typeof doesn't throw. clear any possible exception scope.engine->hasException = false; - return method_typeofValue(engine, prop); + return TypeofValue::call(engine, prop); } -#ifndef V4_BOOTSTRAP -ReturnedValue Runtime::method_typeofScopeObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex) +void Runtime::PushCallContext::call(CppStackFrame *frame) { - Scope scope(engine); - ScopedValue prop(scope, method_getQmlScopeObjectProperty(engine, context, propertyIndex, /*captureRequired*/true)); - if (scope.engine->hasException) - return Encode::undefined(); - return method_typeofValue(engine, prop); + frame->jsFrame->context = ExecutionContext::newCallContext(frame)->asReturnedValue(); } -ReturnedValue Runtime::method_typeofContextObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex) +ReturnedValue Runtime::PushWithContext::call(ExecutionEngine *engine, const Value &acc) { - Scope scope(engine); - ScopedValue prop(scope, method_getQmlContextObjectProperty(engine, context, propertyIndex, /*captureRequired*/true)); - if (scope.engine->hasException) - return Encode::undefined(); - return method_typeofValue(engine, prop); + CallData *jsFrame = engine->currentStackFrame->jsFrame; + Value &newAcc = jsFrame->accumulator; + newAcc = Value::fromHeapObject(acc.toObject(engine)); + if (!engine->hasException) { + Q_ASSERT(newAcc.isObject()); + const Object &obj = static_cast<const Object &>(newAcc); + Value &context = jsFrame->context; + auto ec = static_cast<const ExecutionContext *>(&context); + context = ec->newWithContext(obj.d())->asReturnedValue(); + } + return newAcc.asReturnedValue(); } -#endif // V4_BOOTSTRAP -QV4::ReturnedValue Runtime::method_typeofMember(ExecutionEngine *engine, const Value &base, int nameIndex) +void Runtime::PushCatchContext::call(ExecutionEngine *engine, int blockIndex, int exceptionVarNameIndex) { - Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - ScopedObject obj(scope, base.toObject(engine)); - if (scope.engine->hasException) - return Encode::undefined(); - ScopedValue prop(scope, obj->get(name)); - return method_typeofValue(engine, prop); + auto name = engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[exceptionVarNameIndex]; + engine->currentStackFrame->jsFrame->context = ExecutionContext::newCatchContext(engine->currentStackFrame, blockIndex, name)->asReturnedValue(); } -QV4::ReturnedValue Runtime::method_typeofElement(ExecutionEngine *engine, const Value &base, const Value &index) +void Runtime::PushBlockContext::call(ExecutionEngine *engine, int index) { - Scope scope(engine); - ScopedString name(scope, index.toString(engine)); - ScopedObject obj(scope, base.toObject(engine)); - if (scope.engine->hasException) - return Encode::undefined(); - ScopedValue prop(scope, obj->get(name)); - return method_typeofValue(engine, prop); + engine->currentStackFrame->jsFrame->context = ExecutionContext::newBlockContext(engine->currentStackFrame, index)->asReturnedValue(); +} + +void Runtime::CloneBlockContext::call(ExecutionEngine *engine) +{ + auto frame = engine->currentStackFrame; + auto context = static_cast<Heap::CallContext *>(frame->jsFrame->context.m()); + frame->jsFrame->context = + ExecutionContext::cloneBlockContext(engine, context)->asReturnedValue(); +} + +void Runtime::PushScriptContext::call(ExecutionEngine *engine, int index) +{ + Q_ASSERT(engine->currentStackFrame->context()->d()->type == Heap::ExecutionContext::Type_GlobalContext || + engine->currentStackFrame->context()->d()->type == Heap::ExecutionContext::Type_QmlContext); + ReturnedValue c = ExecutionContext::newBlockContext(engine->currentStackFrame, index)->asReturnedValue(); + engine->setScriptContext(c); + engine->currentStackFrame->jsFrame->context = c; } -ReturnedValue Runtime::method_unwindException(ExecutionEngine *engine) +void Runtime::PopScriptContext::call(ExecutionEngine *engine) { - if (!engine->hasException) - return Primitive::emptyValue().asReturnedValue(); - return engine->catchException(0); + ReturnedValue root = engine->rootContext()->asReturnedValue(); + engine->setScriptContext(root); + engine->currentStackFrame->jsFrame->context = root; } -/* The next three methods are a bit tricky. They can't open up a Scope, as that - * would mess up the pushing of the context. - * - * Instead the push/pop pair acts as a non local scope. - */ -void Runtime::method_pushWithScope(const Value &o, NoThrowEngine *engine) +void Runtime::ThrowReferenceError::call(ExecutionEngine *engine, int nameIndex) { - QV4::Value *v = engine->jsAlloca(1); - Heap::Object *withObject = o.toObject(engine); - *v = withObject; - engine->pushContext(engine->currentContext->newWithContext(withObject)); - Q_ASSERT(engine->jsStackTop == engine->currentContext + 2); + Scope scope(engine); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + engine->throwReferenceError(name); } -void Runtime::method_pushCatchScope(NoThrowEngine *engine, int exceptionVarNameIndex) +void Runtime::ThrowOnNullOrUndefined::call(ExecutionEngine *engine, const Value &v) { - engine->jsAlloca(1); // keep this symmetric with pushWithScope - ExecutionContext *c = engine->currentContext; - engine->pushContext(c->newCatchContext(c->d()->compilationUnit->runtimeStrings[exceptionVarNameIndex], engine->catchException(0))); - Q_ASSERT(engine->jsStackTop == engine->currentContext + 2); + if (v.isNullOrUndefined()) + engine->throwTypeError(); } -void Runtime::method_popScope(NoThrowEngine *engine) +ReturnedValue Runtime::ConvertThisToObject::call(ExecutionEngine *engine, const Value &t) { - Q_ASSERT(engine->jsStackTop == engine->currentContext + 2); - engine->popContext(); - engine->jsStackTop -= 3; + if (!t.isObject()) { + if (t.isNullOrUndefined()) { + return engine->globalObject->asReturnedValue(); + } else { + return t.toObject(engine)->asReturnedValue(); + } + } + return t.asReturnedValue(); } -void Runtime::method_declareVar(ExecutionEngine *engine, bool deletable, int nameIndex) +void Runtime::DeclareVar::call(ExecutionEngine *engine, Bool deletable, int nameIndex) { Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - engine->currentContext->createMutableBinding(name, deletable); + ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); + static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).createMutableBinding(name, deletable); } -ReturnedValue Runtime::method_arrayLiteral(ExecutionEngine *engine, Value *values, uint length) +ReturnedValue Runtime::ArrayLiteral::call(ExecutionEngine *engine, Value *values, uint length) { return engine->newArrayObject(values, length)->asReturnedValue(); } -ReturnedValue Runtime::method_objectLiteral(ExecutionEngine *engine, const QV4::Value *args, int classId, int arrayValueCount, int arrayGetterSetterCountAndFlags) +ReturnedValue Runtime::ObjectLiteral::call(ExecutionEngine *engine, int classId, QV4::Value args[], int argc) { Scope scope(engine); - QV4::InternalClass *klass = static_cast<CompiledData::CompilationUnit*>(engine->current->compilationUnit)->runtimeClasses[classId]; - ScopedObject o(scope, engine->newObject(klass, engine->objectPrototype())); + Scoped<InternalClass> klass(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeClasses[classId]); + ScopedObject o(scope, engine->newObject(klass->d())); - { - bool needSparseArray = arrayGetterSetterCountAndFlags >> 30; - if (needSparseArray) - o->initSparseArray(); - } + Q_ASSERT(uint(argc) >= klass->d()->size); - for (uint i = 0; i < klass->size; ++i) + for (uint i = 0; i < klass->d()->size; ++i) o->setProperty(i, *args++); - if (arrayValueCount > 0) { - ScopedValue entry(scope); - for (int i = 0; i < arrayValueCount; ++i) { - uint idx = args->toUInt32(); - ++args; - entry = *args++; - o->arraySet(idx, entry); + Q_ASSERT((argc - klass->d()->size) % 3 == 0); + int additionalArgs = (argc - int(klass->d()->size))/3; + + if (!additionalArgs) + return o->asReturnedValue(); + + ScopedPropertyKey name(scope); + ScopedProperty pd(scope); + ScopedFunctionObject fn(scope); + ScopedString fnName(scope); + ScopedValue value(scope); + for (int i = 0; i < additionalArgs; ++i) { + Q_ASSERT(args->isInteger()); + ObjectLiteralArgument arg = ObjectLiteralArgument(args->integerValue()); + name = args[1].toPropertyKey(engine); + value = args[2]; + if (engine->hasException) + return Encode::undefined(); + if (arg != ObjectLiteralArgument::Value) { + Q_ASSERT(args[2].isInteger()); + int functionId = args[2].integerValue(); + QV4::Function *clos = static_cast<CompiledData::CompilationUnit*>(engine->currentStackFrame->v4Function->compilationUnit)->runtimeFunctions[functionId]; + Q_ASSERT(clos); + + PropertyKey::FunctionNamePrefix prefix = PropertyKey::None; + if (arg == ObjectLiteralArgument::Getter) + prefix = PropertyKey::Getter; + else if (arg == ObjectLiteralArgument::Setter) + prefix = PropertyKey::Setter; + else + arg = ObjectLiteralArgument::Value; + fnName = name->asFunctionName(engine, prefix); + + ExecutionContext *current = static_cast<ExecutionContext *>(&engine->currentStackFrame->jsFrame->context); + if (clos->isGenerator()) + value = MemberGeneratorFunction::create(current, clos, o, fnName)->asReturnedValue(); + else + value = FunctionObject::createMemberFunction(current, clos, o, fnName)->asReturnedValue(); + } else if (args[2].isFunctionObject()) { + fn = static_cast<const FunctionObject &>(args[2]); + + fnName = name->asFunctionName(engine, PropertyKey::None); + fn->setName(fnName); } - } - - uint arrayGetterSetterCount = arrayGetterSetterCountAndFlags & ((1 << 30) - 1); - if (arrayGetterSetterCount > 0) { - ScopedProperty pd(scope); - for (uint i = 0; i < arrayGetterSetterCount; ++i) { - uint idx = args->toUInt32(); - ++args; - pd->value = *args; - ++args; - pd->set = *args; - ++args; - o->arraySet(idx, pd, Attr_Accessor); + Q_ASSERT(arg != ObjectLiteralArgument::Method); + Q_ASSERT(arg == ObjectLiteralArgument::Value || value->isFunctionObject()); + if (arg == ObjectLiteralArgument::Value || arg == ObjectLiteralArgument::Getter) { + pd->value = value; + pd->set = Value::emptyValue(); + } else { + pd->value = Value::emptyValue(); + pd->set = value; } - } + bool ok = o->defineOwnProperty(name, pd, (arg == ObjectLiteralArgument::Value ? Attr_Data : Attr_Accessor)); + if (!ok) + return engine->throwTypeError(); + args += 3; + } return o.asReturnedValue(); } -QV4::ReturnedValue Runtime::method_setupArgumentsObject(ExecutionEngine *engine) -{ - Q_ASSERT(engine->current->type == Heap::ExecutionContext::Type_CallContext); - QV4::CallContext *c = static_cast<QV4::CallContext *>(engine->currentContext); - QV4::InternalClass *ic = engine->internalClasses[c->d()->strictMode ? EngineBase::Class_StrictArgumentsObject : EngineBase::Class_ArgumentsObject]; - return engine->memoryManager->allocObject<ArgumentsObject>(ic, engine->objectPrototype(), c)->asReturnedValue(); -} - -#endif // V4_BOOTSTRAP - -QV4::ReturnedValue Runtime::method_increment(const Value &value) +ReturnedValue Runtime::CreateClass::call(ExecutionEngine *engine, int classIndex, + const Value &superClass, Value computedNames[]) { - TRACE1(value); + const CompiledData::CompilationUnit *unit = engine->currentStackFrame->v4Function->compilationUnit; + const QV4::CompiledData::Class *cls = unit->unitData()->classAt(classIndex); - if (value.isInteger() && value.integerValue() < INT_MAX) - return Encode(value.integerValue() + 1); - else { - double d = value.toNumber(); - return Encode(d + 1.); + Scope scope(engine); + ScopedObject protoParent(scope, engine->objectPrototype()); + ScopedObject constructorParent(scope, engine->functionPrototype()); + if (!superClass.isEmpty()) { + if (superClass.isNull()) { + protoParent = Encode::null(); + } else { + const FunctionObject *superFunction = superClass.as<FunctionObject>(); + // ### check that the heritage object is a constructor + if (!superFunction || !superFunction->isConstructor()) + return engine->throwTypeError(QStringLiteral("The superclass is not a function object.")); + const FunctionObject *s = static_cast<const FunctionObject *>(&superClass); + ScopedValue result(scope, s->get(scope.engine->id_prototype())); + if (!result->isObject() && !result->isNull()) + return engine->throwTypeError(QStringLiteral("The value of the superclass's prototype property is not an object.")); + protoParent = *result; + constructorParent = superClass; + } } -} - -QV4::ReturnedValue Runtime::method_decrement(const Value &value) -{ - TRACE1(value); - if (value.isInteger() && value.integerValue() > INT_MIN) - return Encode(value.integerValue() - 1); - else { - double d = value.toNumber(); - return Encode(d - 1.); + ScopedObject proto(scope, engine->newObject()); + proto->setPrototypeUnchecked(protoParent); + ExecutionContext *current = static_cast<ExecutionContext *>(&engine->currentStackFrame->jsFrame->context); + + ScopedFunctionObject constructor(scope); + QV4::Function *f = cls->constructorFunction != UINT_MAX ? unit->runtimeFunctions[cls->constructorFunction] : nullptr; + constructor = FunctionObject::createConstructorFunction(current, f, proto, !superClass.isEmpty())->asReturnedValue(); + constructor->setPrototypeUnchecked(constructorParent); + Value argCount = Value::fromInt32(f ? f->nFormals : 0); + constructor->defineReadonlyConfigurableProperty(scope.engine->id_length(), argCount); + constructor->defineReadonlyConfigurableProperty(engine->id_prototype(), proto); + proto->defineDefaultProperty(engine->id_constructor(), constructor); + + ScopedString name(scope); + if (cls->nameIndex != UINT_MAX) { + name = unit->runtimeStrings[cls->nameIndex]; + constructor->defineReadonlyConfigurableProperty(engine->id_name(), name); } -} -ReturnedValue Runtime::method_toDouble(const Value &value) -{ - TRACE1(value); - return Encode(value.toNumber()); -} - -int Runtime::method_toInt(const Value &value) -{ - TRACE1(value); - return value.toInt32(); -} + ScopedObject receiver(scope, *constructor); + ScopedPropertyKey propertyName(scope); + ScopedFunctionObject function(scope); + ScopedProperty property(scope); + const CompiledData::Method *methods = cls->methodTable(); + for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { + if (i == cls->nStaticMethods) + receiver = proto; + if (methods[i].name == UINT_MAX) { + propertyName = computedNames->toPropertyKey(engine); + if (propertyName == scope.engine->id_prototype()->propertyKey() && receiver->d() == constructor->d()) + return engine->throwTypeError(QStringLiteral("Cannot declare a static method named 'prototype'.")); + if (engine->hasException) + return Encode::undefined(); + ++computedNames; + } else { + name = unit->runtimeStrings[methods[i].name]; + propertyName = name->toPropertyKey(); + } + QV4::Function *f = unit->runtimeFunctions[methods[i].function]; + Q_ASSERT(f); + PropertyKey::FunctionNamePrefix prefix = PropertyKey::None; + if (methods[i].type == CompiledData::Method::Getter) + prefix = PropertyKey::Getter; + else if (methods[i].type == CompiledData::Method::Setter) + prefix = PropertyKey::Setter; + + name = propertyName->asFunctionName(engine, prefix); + + if (f->isGenerator()) + function = MemberGeneratorFunction::create(current, f, receiver, name); + else + function = FunctionObject::createMemberFunction(current, f, receiver, name); + Q_ASSERT(function); + PropertyAttributes attributes; + switch (methods[i].type) { + case CompiledData::Method::Getter: + property->setGetter(function); + property->set = Value::emptyValue(); + attributes = Attr_Accessor|Attr_NotEnumerable; + break; + case CompiledData::Method::Setter: + property->value = Value::emptyValue(); + property->setSetter(function); + attributes = Attr_Accessor|Attr_NotEnumerable; + break; + default: // Regular + property->value = function; + property->set = Value::emptyValue(); + attributes = Attr_Data|Attr_NotEnumerable; + break; + } + receiver->defineOwnProperty(propertyName, property, attributes); + } -int Runtime::method_doubleToInt(const double &d) -{ - TRACE0(); - return Primitive::toInt32(d); + return constructor->asReturnedValue(); } -unsigned Runtime::method_toUInt(const Value &value) +QV4::ReturnedValue Runtime::CreateMappedArgumentsObject::call(ExecutionEngine *engine) { - TRACE1(value); - return value.toUInt32(); + Q_ASSERT(engine->currentContext()->d()->type == Heap::ExecutionContext::Type_CallContext); + Heap::InternalClass *ic = engine->internalClasses(EngineBase::Class_ArgumentsObject); + return engine->memoryManager->allocObject<ArgumentsObject>(ic, engine->currentStackFrame)->asReturnedValue(); } -unsigned Runtime::method_doubleToUInt(const double &d) +QV4::ReturnedValue Runtime::CreateUnmappedArgumentsObject::call(ExecutionEngine *engine) { - TRACE0(); - return Primitive::toUInt32(d); + Heap::InternalClass *ic = engine->internalClasses(EngineBase::Class_StrictArgumentsObject); + return engine->memoryManager->allocObject<StrictArgumentsObject>(ic, engine->currentStackFrame)->asReturnedValue(); } -#ifndef V4_BOOTSTRAP - -ReturnedValue Runtime::method_getQmlContext(NoThrowEngine *engine) +QV4::ReturnedValue Runtime::CreateRestParameter::call(ExecutionEngine *engine, int argIndex) { - return engine->qmlContext()->asReturnedValue(); + const Value *values = engine->currentStackFrame->originalArguments + argIndex; + int nValues = engine->currentStackFrame->originalArgumentsCount - argIndex; + if (nValues <= 0) + return engine->newArrayObject(0)->asReturnedValue(); + return engine->newArrayObject(values, nValues)->asReturnedValue(); } -ReturnedValue Runtime::method_regexpLiteral(ExecutionEngine *engine, int id) -{ - return static_cast<CompiledData::CompilationUnit*>(engine->current->compilationUnit)->runtimeRegularExpressions[id].asReturnedValue(); -} -ReturnedValue Runtime::method_getQmlQObjectProperty(ExecutionEngine *engine, const Value &object, int propertyIndex, bool captureRequired) +ReturnedValue Runtime::LoadQmlContext::call(ExecutionEngine *engine) { - Scope scope(engine); - QV4::Scoped<QObjectWrapper> wrapper(scope, object); - if (!wrapper) { - engine->throwTypeError(QStringLiteral("Cannot read property of null")); - return Encode::undefined(); - } - return QV4::QObjectWrapper::getProperty(scope.engine, wrapper->object(), propertyIndex, captureRequired); + Heap::QmlContext *ctx = engine->qmlContext(); + Q_ASSERT(ctx); + return ctx->asReturnedValue(); } -QV4::ReturnedValue Runtime::method_getQmlAttachedProperty(ExecutionEngine *engine, int attachedPropertiesId, int propertyIndex) +ReturnedValue Runtime::RegexpLiteral::call(ExecutionEngine *engine, int id) { - QObject *scopeObject = engine->qmlScopeObject(); - QObject *attachedObject = qmlAttachedPropertiesObjectById(attachedPropertiesId, scopeObject); - - QJSEngine *jsEngine = engine->jsEngine(); - QQmlData::ensurePropertyCache(jsEngine, attachedObject); - return QV4::QObjectWrapper::getProperty(engine, attachedObject, propertyIndex, /*captureRequired*/true); + Heap::RegExpObject *ro = engine->newRegExpObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeRegularExpressions[id].as<RegExp>()); + return ro->asReturnedValue(); } -ReturnedValue Runtime::method_getQmlScopeObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex, bool captureRequired) +ReturnedValue Runtime::LoadQmlScopeObjectProperty::call(ExecutionEngine *engine, const Value &context, int propertyIndex, Bool captureRequired) { const QmlContext &c = static_cast<const QmlContext &>(context); - return QV4::QObjectWrapper::getProperty(engine, c.d()->qml->scopeObject, propertyIndex, captureRequired); + return QV4::QObjectWrapper::getProperty(engine, c.d()->qml()->scopeObject, propertyIndex, captureRequired); } -ReturnedValue Runtime::method_getQmlContextObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex, bool captureRequired) +ReturnedValue Runtime::LoadQmlContextObjectProperty::call(ExecutionEngine *engine, const Value &context, int propertyIndex, Bool captureRequired) { const QmlContext &c = static_cast<const QmlContext &>(context); - return QV4::QObjectWrapper::getProperty(engine, (*c.d()->qml->context)->contextObject, propertyIndex, captureRequired); -} - -ReturnedValue Runtime::method_getQmlSingletonQObjectProperty(ExecutionEngine *engine, const Value &object, int propertyIndex, bool captureRequired) -{ - Scope scope(engine); - QV4::Scoped<QQmlTypeWrapper> wrapper(scope, object); - if (!wrapper) { - scope.engine->throwTypeError(QStringLiteral("Cannot read property of null")); - return Encode::undefined(); - } - return QV4::QObjectWrapper::getProperty(scope.engine, wrapper->singletonObject(), propertyIndex, captureRequired); + return QV4::QObjectWrapper::getProperty(engine, (*c.d()->qml()->context)->contextObject, propertyIndex, captureRequired); } -ReturnedValue Runtime::method_getQmlIdObject(ExecutionEngine *engine, const Value &c, uint index) +ReturnedValue Runtime::LoadQmlIdObject::call(ExecutionEngine *engine, const Value &c, uint index) { - Scope scope(engine); const QmlContext &qmlContext = static_cast<const QmlContext &>(c); - QQmlContextData *context = *qmlContext.d()->qml->context; + QQmlContextData *context = *qmlContext.d()->qml()->context; if (!context || index >= (uint)context->idValueCount) return Encode::undefined(); - QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : 0; + QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : nullptr; if (ep && ep->propertyCapture) ep->propertyCapture->captureProperty(&context->idValues[index].bindings); return QObjectWrapper::wrap(engine, context->idValues[index].data()); } -void Runtime::method_setQmlScopeObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value) +void Runtime::StoreQmlScopeObjectProperty::call(ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value) { const QmlContext &c = static_cast<const QmlContext &>(context); - return QV4::QObjectWrapper::setProperty(engine, c.d()->qml->scopeObject, propertyIndex, value); + return QV4::QObjectWrapper::setProperty(engine, c.d()->qml()->scopeObject, propertyIndex, value); } -void Runtime::method_setQmlContextObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value) +void Runtime::StoreQmlContextObjectProperty::call(ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value) { const QmlContext &c = static_cast<const QmlContext &>(context); - return QV4::QObjectWrapper::setProperty(engine, (*c.d()->qml->context)->contextObject, propertyIndex, value); -} - -void Runtime::method_setQmlQObjectProperty(ExecutionEngine *engine, const Value &object, int propertyIndex, const Value &value) -{ - Scope scope(engine); - QV4::Scoped<QObjectWrapper> wrapper(scope, object); - if (!wrapper) { - engine->throwTypeError(QStringLiteral("Cannot write property of null")); - return; - } - wrapper->setProperty(engine, propertyIndex, value); + return QV4::QObjectWrapper::setProperty(engine, (*c.d()->qml()->context)->contextObject, propertyIndex, value); } -ReturnedValue Runtime::method_getQmlImportedScripts(NoThrowEngine *engine) +ReturnedValue Runtime::LoadQmlImportedScripts::call(ExecutionEngine *engine) { QQmlContextData *context = engine->callingQmlContext(); if (!context) @@ -1570,44 +2073,32 @@ ReturnedValue Runtime::method_getQmlImportedScripts(NoThrowEngine *engine) return context->importedScripts.value(); } -QV4::ReturnedValue Runtime::method_getQmlSingleton(QV4::NoThrowEngine *engine, int nameIndex) +ReturnedValue Runtime::ToObject::call(ExecutionEngine *engine, const Value &obj) { - Scope scope(engine); - ScopedString name(scope, engine->current->compilationUnit->runtimeStrings[nameIndex]); - return engine->qmlSingletonWrapper(name); + if (obj.isObject()) + return obj.asReturnedValue(); + + return obj.toObject(engine)->asReturnedValue(); } -void Runtime::method_convertThisToObject(ExecutionEngine *engine) +Bool Runtime::ToBoolean::call(const Value &obj) { - Value *t = &engine->current->callData->thisObject; - if (t->isObject()) - return; - if (t->isNullOrUndefined()) { - *t = engine->globalObject->asReturnedValue(); - } else { - *t = t->toObject(engine)->asReturnedValue(); - } + return obj.toBoolean(); } -ReturnedValue Runtime::method_uPlus(const Value &value) +ReturnedValue Runtime::ToNumber::call(ExecutionEngine *, const Value &v) { - TRACE1(value); - - if (value.isNumber()) - return value.asReturnedValue(); - if (value.integerCompatible()) - return Encode(value.int_32()); - - double n = value.toNumberImpl(); - return Encode(n); + return Encode(v.toNumber()); } +#endif // V4_BOOTSTRAP -ReturnedValue Runtime::method_uMinus(const Value &value) +ReturnedValue Runtime::UMinus::call(const Value &value) { TRACE1(value); // +0 != -0, so we need to convert to double when negating 0 - if (value.isInteger() && value.integerValue()) + if (value.isInteger() && value.integerValue() && + value.integerValue() != std::numeric_limits<int>::min()) return Encode(-value.integerValue()); else { double n = RuntimeHelpers::toNumber(value); @@ -1615,98 +2106,57 @@ ReturnedValue Runtime::method_uMinus(const Value &value) } } -ReturnedValue Runtime::method_complement(const Value &value) -{ - TRACE1(value); - - int n = value.toInt32(); - return Encode((int)~n); -} - -ReturnedValue Runtime::method_uNot(const Value &value) -{ - TRACE1(value); - - bool b = value.toBoolean(); - return Encode(!b); -} - // binary operators -ReturnedValue Runtime::method_bitOr(const Value &left, const Value &right) -{ - TRACE2(left, right); - - int lval = left.toInt32(); - int rval = right.toInt32(); - return Encode(lval | rval); -} - -ReturnedValue Runtime::method_bitXor(const Value &left, const Value &right) -{ - TRACE2(left, right); - - int lval = left.toInt32(); - int rval = right.toInt32(); - return Encode(lval ^ rval); -} - -ReturnedValue Runtime::method_bitAnd(const Value &left, const Value &right) -{ - TRACE2(left, right); - - int lval = left.toInt32(); - int rval = right.toInt32(); - return Encode(lval & rval); -} #ifndef V4_BOOTSTRAP -ReturnedValue Runtime::method_add(ExecutionEngine *engine, const Value &left, const Value &right) +ReturnedValue Runtime::Add::call(ExecutionEngine *engine, const Value &left, const Value &right) { TRACE2(left, right); - if (Q_LIKELY(left.isInteger() && right.isInteger())) + if (Q_LIKELY(left.integerCompatible() && right.integerCompatible())) return add_int32(left.integerValue(), right.integerValue()); if (left.isNumber() && right.isNumber()) - return Primitive::fromDouble(left.asDouble() + right.asDouble()).asReturnedValue(); + return Value::fromDouble(left.asDouble() + right.asDouble()).asReturnedValue(); return RuntimeHelpers::addHelper(engine, left, right); } -#endif // V4_BOOTSTRAP -ReturnedValue Runtime::method_sub(const Value &left, const Value &right) +ReturnedValue Runtime::Sub::call(const Value &left, const Value &right) { TRACE2(left, right); - if (Q_LIKELY(left.isInteger() && right.isInteger())) + if (Q_LIKELY(left.integerCompatible() && right.integerCompatible())) return sub_int32(left.integerValue(), right.integerValue()); double lval = left.isNumber() ? left.asDouble() : left.toNumberImpl(); double rval = right.isNumber() ? right.asDouble() : right.toNumberImpl(); - return Primitive::fromDouble(lval - rval).asReturnedValue(); + return Value::fromDouble(lval - rval).asReturnedValue(); } -ReturnedValue Runtime::method_mul(const Value &left, const Value &right) +ReturnedValue Runtime::Mul::call(const Value &left, const Value &right) { TRACE2(left, right); - if (Q_LIKELY(left.isInteger() && right.isInteger())) + if (Q_LIKELY(left.integerCompatible() && right.integerCompatible())) return mul_int32(left.integerValue(), right.integerValue()); double lval = left.isNumber() ? left.asDouble() : left.toNumberImpl(); double rval = right.isNumber() ? right.asDouble() : right.toNumberImpl(); - return Primitive::fromDouble(lval * rval).asReturnedValue(); + return Value::fromDouble(lval * rval).asReturnedValue(); } -ReturnedValue Runtime::method_div(const Value &left, const Value &right) +ReturnedValue Runtime::Div::call(const Value &left, const Value &right) { TRACE2(left, right); if (Value::integerCompatible(left, right)) { int lval = left.integerValue(); int rval = right.integerValue(); - if (rval != 0 && (lval % rval == 0)) + if (rval != 0 // division by zero should result in a NaN + && (lval % rval == 0) // fractions can't be stored in an int + && !(lval == 0 && rval < 0)) // 0 / -something results in -0.0 return Encode(int(lval / rval)); else return Encode(double(lval) / rval); @@ -1714,17 +2164,20 @@ ReturnedValue Runtime::method_div(const Value &left, const Value &right) double lval = left.toNumber(); double rval = right.toNumber(); - return Primitive::fromDouble(lval / rval).asReturnedValue(); + return Value::fromDouble(lval / rval).asReturnedValue(); } -ReturnedValue Runtime::method_mod(const Value &left, const Value &right) +ReturnedValue Runtime::Mod::call(const Value &left, const Value &right) { TRACE2(left, right); - if (Value::integerCompatible(left, right) && right.integerValue() != 0) { - int intRes = left.integerValue() % right.integerValue(); - if (intRes != 0 || left.integerValue() >= 0) - return Encode(intRes); + if (Value::integerCompatible(left, right) && left.integerValue() >= 0 && right.integerValue() > 0) { + // special cases are handled by fmod, among them: + // - arithmic execeptions for ints in c++, eg: INT_MIN % -1 + // - undefined behavior in c++, e.g.: anything % 0 + // - uncommon cases which would complicate the condition, e.g.: negative integers + // (this makes sure that -1 % 1 == -0 by passing it to fmod) + return Encode(left.integerValue() % right.integerValue()); } double lval = RuntimeHelpers::toNumber(left); @@ -1732,10 +2185,46 @@ ReturnedValue Runtime::method_mod(const Value &left, const Value &right) #ifdef fmod # undef fmod #endif - return Primitive::fromDouble(std::fmod(lval, rval)).asReturnedValue(); + return Value::fromDouble(std::fmod(lval, rval)).asReturnedValue(); +} + +ReturnedValue Runtime::Exp::call(const Value &base, const Value &exp) +{ + double b = base.toNumber(); + double e = exp.toNumber(); + if (qt_is_inf(e) && (b == 1 || b == -1)) + return Encode(qt_snan()); + return Encode(pow(b,e)); +} + +ReturnedValue Runtime::BitAnd::call(const Value &left, const Value &right) +{ + TRACE2(left, right); + + int lval = left.toInt32(); + int rval = right.toInt32(); + return Encode((int)(lval & rval)); +} + +ReturnedValue Runtime::BitOr::call(const Value &left, const Value &right) +{ + TRACE2(left, right); + + int lval = left.toInt32(); + int rval = right.toInt32(); + return Encode((int)(lval | rval)); +} + +ReturnedValue Runtime::BitXor::call(const Value &left, const Value &right) +{ + TRACE2(left, right); + + int lval = left.toInt32(); + int rval = right.toInt32(); + return Encode((int)(lval ^ rval)); } -ReturnedValue Runtime::method_shl(const Value &left, const Value &right) +ReturnedValue Runtime::Shl::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -1744,7 +2233,7 @@ ReturnedValue Runtime::method_shl(const Value &left, const Value &right) return Encode((int)(lval << rval)); } -ReturnedValue Runtime::method_shr(const Value &left, const Value &right) +ReturnedValue Runtime::Shr::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -1753,7 +2242,7 @@ ReturnedValue Runtime::method_shr(const Value &left, const Value &right) return Encode((int)(lval >> rval)); } -ReturnedValue Runtime::method_ushr(const Value &left, const Value &right) +ReturnedValue Runtime::UShr::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -1766,75 +2255,184 @@ ReturnedValue Runtime::method_ushr(const Value &left, const Value &right) #endif // V4_BOOTSTRAP -ReturnedValue Runtime::method_greaterThan(const Value &left, const Value &right) +ReturnedValue Runtime::GreaterThan::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareGreaterThan(left, right); + bool r = CompareGreaterThan::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_lessThan(const Value &left, const Value &right) +ReturnedValue Runtime::LessThan::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareLessThan(left, right); + bool r = CompareLessThan::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_greaterEqual(const Value &left, const Value &right) +ReturnedValue Runtime::GreaterEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareGreaterEqual(left, right); + bool r = CompareGreaterEqual::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_lessEqual(const Value &left, const Value &right) +ReturnedValue Runtime::LessEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareLessEqual(left, right); + bool r = CompareLessEqual::call(left, right); return Encode(r); } -Bool Runtime::method_compareEqual(const Value &left, const Value &right) +struct LazyScope +{ + ExecutionEngine *engine = nullptr; + Value *stackMark = nullptr; + ~LazyScope() { + if (engine) + engine->jsStackTop = stackMark; + } + template <typename T> + void set(Value **scopedValue, T value, ExecutionEngine *e) { + if (!engine) { + engine = e; + stackMark = engine->jsStackTop; + } + if (!*scopedValue) + *scopedValue = e->jsAlloca(1); + **scopedValue = value; + } +}; + +Bool Runtime::CompareEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - if (left.rawValue() == right.rawValue()) - // NaN != NaN - return !left.isNaN(); + Value lhs = left; + Value rhs = right; - if (left.type() == right.type()) { - if (left.isDouble() && left.doubleValue() == 0 && right.doubleValue() == 0) - return true; // this takes care of -0 == +0 (which obviously have different raw values) - if (!left.isManaged()) - return false; - if (left.isString() == right.isString()) - return left.cast<Managed>()->isEqualTo(right.cast<Managed>()); +#ifndef V4_BOOTSTRAP + LazyScope scope; + Value *lhsGuard = nullptr; + Value *rhsGuard = nullptr; +#endif + + redo: + if (lhs.asReturnedValue() == rhs.asReturnedValue()) + return !lhs.isNaN(); + + int lt = lhs.quickType(); + int rt = rhs.quickType(); + if (rt < lt) { + qSwap(lhs, rhs); + qSwap(lt, rt); } - return RuntimeHelpers::equalHelper(left, right); + switch (lt) { + case QV4::Value::QT_ManagedOrUndefined: + if (lhs.isUndefined()) + return rhs.isNullOrUndefined(); + Q_FALLTHROUGH(); + case QV4::Value::QT_ManagedOrUndefined1: + case QV4::Value::QT_ManagedOrUndefined2: + case QV4::Value::QT_ManagedOrUndefined3: + // LHS: Managed + switch (rt) { + case QV4::Value::QT_ManagedOrUndefined: + if (rhs.isUndefined()) + return false; + Q_FALLTHROUGH(); + case QV4::Value::QT_ManagedOrUndefined1: + case QV4::Value::QT_ManagedOrUndefined2: + case QV4::Value::QT_ManagedOrUndefined3: { +#ifndef V4_BOOTSTRAP + // RHS: Managed + Heap::Base *l = lhs.m(); + Heap::Base *r = rhs.m(); + Q_ASSERT(l); + Q_ASSERT(r); + if (l->internalClass->vtable->isStringOrSymbol == r->internalClass->vtable->isStringOrSymbol) + return static_cast<QV4::Managed &>(lhs).isEqualTo(&static_cast<QV4::Managed &>(rhs)); + if (l->internalClass->vtable->isStringOrSymbol) { + scope.set(&rhsGuard, RuntimeHelpers::objectDefaultValue(&static_cast<QV4::Object &>(rhs), PREFERREDTYPE_HINT), r->internalClass->engine); + rhs = rhsGuard->asReturnedValue(); + break; + } else { + Q_ASSERT(r->internalClass->vtable->isStringOrSymbol); + scope.set(&lhsGuard, RuntimeHelpers::objectDefaultValue(&static_cast<QV4::Object &>(lhs), PREFERREDTYPE_HINT), l->internalClass->engine); + lhs = lhsGuard->asReturnedValue(); + break; + } +#endif + return false; + } + case QV4::Value::QT_Empty: + Q_UNREACHABLE(); + case QV4::Value::QT_Null: + return false; + case QV4::Value::QT_Bool: + case QV4::Value::QT_Int: + rhs = Value::fromDouble(rhs.int_32()); + // fall through + default: // double +#ifndef V4_BOOTSTRAP + if (lhs.m()->internalClass->vtable->isStringOrSymbol) { + return lhs.m()->internalClass->vtable->isString ? (RuntimeHelpers::toNumber(lhs) == rhs.doubleValue()) : false; + } else { + scope.set(&lhsGuard, RuntimeHelpers::objectDefaultValue(&static_cast<QV4::Object &>(lhs), PREFERREDTYPE_HINT), lhs.m()->internalClass->engine); + lhs = lhsGuard->asReturnedValue(); + } +#else + Q_UNIMPLEMENTED(); +#endif + } + goto redo; + case QV4::Value::QT_Empty: + Q_UNREACHABLE(); + case QV4::Value::QT_Null: + return rhs.isNull(); + case QV4::Value::QT_Bool: + case QV4::Value::QT_Int: + switch (rt) { + case QV4::Value::QT_ManagedOrUndefined: + case QV4::Value::QT_ManagedOrUndefined1: + case QV4::Value::QT_ManagedOrUndefined2: + case QV4::Value::QT_ManagedOrUndefined3: + case QV4::Value::QT_Empty: + case QV4::Value::QT_Null: + Q_UNREACHABLE(); + case QV4::Value::QT_Bool: + case QV4::Value::QT_Int: + return lhs.int_32() == rhs.int_32(); + default: // double + return lhs.int_32() == rhs.doubleValue(); + } + default: // double + Q_ASSERT(rhs.isDouble()); + return lhs.doubleValue() == rhs.doubleValue(); + } } -ReturnedValue Runtime::method_equal(const Value &left, const Value &right) +ReturnedValue Runtime::Equal::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareEqual(left, right); + bool r = CompareEqual::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_notEqual(const Value &left, const Value &right) +ReturnedValue Runtime::NotEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = !method_compareEqual(left, right); + bool r = !CompareEqual::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_strictEqual(const Value &left, const Value &right) +ReturnedValue Runtime::StrictEqual::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -1842,7 +2440,7 @@ ReturnedValue Runtime::method_strictEqual(const Value &left, const Value &right) return Encode(r); } -ReturnedValue Runtime::method_strictNotEqual(const Value &left, const Value &right) +ReturnedValue Runtime::StrictNotEqual::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -1850,32 +2448,27 @@ ReturnedValue Runtime::method_strictNotEqual(const Value &left, const Value &rig return Encode(r); } -Bool Runtime::method_compareNotEqual(const Value &left, const Value &right) +Bool Runtime::CompareNotEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - return !Runtime::method_compareEqual(left, right); + return !Runtime::CompareEqual::call(left, right); } -Bool Runtime::method_compareStrictEqual(const Value &left, const Value &right) +Bool Runtime::CompareStrictEqual::call(const Value &left, const Value &right) { TRACE2(left, right); return RuntimeHelpers::strictEqual(left, right); } -Bool Runtime::method_compareStrictNotEqual(const Value &left, const Value &right) +Bool Runtime::CompareStrictNotEqual::call(const Value &left, const Value &right) { TRACE2(left, right); return ! RuntimeHelpers::strictEqual(left, right); } -Bool Runtime::method_toBoolean(const Value &value) -{ - return value.toBoolean(); -} - } // namespace QV4 QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4runtime_p.h b/src/qml/jsruntime/qv4runtime_p.h index 0d787714cf..72af90d1dc 100644 --- a/src/qml/jsruntime/qv4runtime_p.h +++ b/src/qml/jsruntime/qv4runtime_p.h @@ -83,8 +83,8 @@ private: }; # define TRACE0() RuntimeCounters::instance->count(Q_FUNC_INFO); -# define TRACE1(x) RuntimeCounters::instance->count(Q_FUNC_INFO, x->type()); -# define TRACE2(x, y) RuntimeCounters::instance->count(Q_FUNC_INFO, x->type(), y->type()); +# define TRACE1(x) RuntimeCounters::instance->count(Q_FUNC_INFO, x.type()); +# define TRACE2(x, y) RuntimeCounters::instance->count(Q_FUNC_INFO, x.type(), y.type()); #else # define TRACE0() # define TRACE1(x) @@ -99,14 +99,15 @@ enum TypeHint { struct Q_QML_PRIVATE_EXPORT RuntimeHelpers { static ReturnedValue objectDefaultValue(const Object *object, int typeHint); - static ReturnedValue toPrimitive(const Value &value, int typeHint); + static ReturnedValue toPrimitive(const Value &value, TypeHint typeHint); + static ReturnedValue ordinaryToPrimitive(ExecutionEngine *engine, const Object *object, String *typeHint); static double stringToNumber(const QString &s); static Heap::String *stringFromNumber(ExecutionEngine *engine, double number); static double toNumber(const Value &value); static void numberToString(QString *result, double num, int radix = 10); - static Heap::String *convertToString(ExecutionEngine *engine, const Value &value); + static Heap::String *convertToString(ExecutionEngine *engine, Value value, TypeHint = STRING_HINT); static Heap::Object *convertToObject(ExecutionEngine *engine, const Value &value); static Bool equalHelper(const Value &x, const Value &y); @@ -118,12 +119,11 @@ struct Q_QML_PRIVATE_EXPORT RuntimeHelpers { // type conversion and testing #ifndef V4_BOOTSTRAP -inline ReturnedValue RuntimeHelpers::toPrimitive(const Value &value, int typeHint) +inline ReturnedValue RuntimeHelpers::toPrimitive(const Value &value, TypeHint typeHint) { - const Object *o = value.as<Object>(); - if (!o) + if (!value.isObject()) return value.asReturnedValue(); - return RuntimeHelpers::objectDefaultValue(o, typeHint); + return RuntimeHelpers::objectDefaultValue(&reinterpret_cast<const Object &>(value), typeHint); } #endif diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h index 302facba06..0312522d90 100644 --- a/src/qml/jsruntime/qv4runtimeapi_p.h +++ b/src/qml/jsruntime/qv4runtimeapi_p.h @@ -57,202 +57,492 @@ QT_BEGIN_NAMESPACE namespace QV4 { typedef uint Bool; -struct NoThrowEngine; -namespace { -template <typename T> -struct ExceptionCheck { - enum { NeedsCheck = 1 }; -}; -// push_catch and pop context methods shouldn't check for exceptions -template <> -struct ExceptionCheck<void (*)(QV4::NoThrowEngine *)> { - enum { NeedsCheck = 0 }; -}; -template <typename A> -struct ExceptionCheck<void (*)(A, QV4::NoThrowEngine)> { - enum { NeedsCheck = 0 }; -}; -template <> -struct ExceptionCheck<QV4::ReturnedValue (*)(QV4::NoThrowEngine *)> { - enum { NeedsCheck = 0 }; -}; -template <typename A> -struct ExceptionCheck<QV4::ReturnedValue (*)(QV4::NoThrowEngine *, A)> { - enum { NeedsCheck = 0 }; -}; -template <typename A, typename B> -struct ExceptionCheck<QV4::ReturnedValue (*)(QV4::NoThrowEngine *, A, B)> { - enum { NeedsCheck = 0 }; -}; -template <typename A, typename B, typename C> -struct ExceptionCheck<void (*)(QV4::NoThrowEngine *, A, B, C)> { - enum { NeedsCheck = 0 }; -}; -} // anonymous namespace - -#define FOR_EACH_RUNTIME_METHOD(F) \ - /* call */ \ - F(ReturnedValue, callGlobalLookup, (ExecutionEngine *engine, uint index, CallData *callData)) \ - F(ReturnedValue, callActivationProperty, (ExecutionEngine *engine, int nameIndex, CallData *callData)) \ - F(ReturnedValue, callQmlScopeObjectProperty, (ExecutionEngine *engine, int propertyIndex, CallData *callData)) \ - F(ReturnedValue, callQmlContextObjectProperty, (ExecutionEngine *engine, int propertyIndex, CallData *callData)) \ - F(ReturnedValue, callProperty, (ExecutionEngine *engine, int nameIndex, CallData *callData)) \ - F(ReturnedValue, callPropertyLookup, (ExecutionEngine *engine, uint index, CallData *callData)) \ - F(ReturnedValue, callElement, (ExecutionEngine *engine, const Value &index, CallData *callData)) \ - F(ReturnedValue, callValue, (ExecutionEngine *engine, const Value &func, CallData *callData)) \ - \ - /* construct */ \ - F(ReturnedValue, constructGlobalLookup, (ExecutionEngine *engine, uint index, CallData *callData)) \ - F(ReturnedValue, constructActivationProperty, (ExecutionEngine *engine, int nameIndex, CallData *callData)) \ - F(ReturnedValue, constructProperty, (ExecutionEngine *engine, int nameIndex, CallData *callData)) \ - F(ReturnedValue, constructPropertyLookup, (ExecutionEngine *engine, uint index, CallData *callData)) \ - F(ReturnedValue, constructValue, (ExecutionEngine *engine, const Value &func, CallData *callData)) \ - \ - /* set & get */ \ - F(void, setActivationProperty, (ExecutionEngine *engine, int nameIndex, const Value &value)) \ - F(void, setProperty, (ExecutionEngine *engine, const Value &object, int nameIndex, const Value &value)) \ - F(void, setElement, (ExecutionEngine *engine, const Value &object, const Value &index, const Value &value)) \ - F(ReturnedValue, getProperty, (ExecutionEngine *engine, const Value &object, int nameIndex)) \ - F(ReturnedValue, getActivationProperty, (ExecutionEngine *engine, int nameIndex)) \ - F(ReturnedValue, getElement, (ExecutionEngine *engine, const Value &object, const Value &index)) \ - \ - /* typeof */ \ - F(ReturnedValue, typeofValue, (ExecutionEngine *engine, const Value &val)) \ - F(ReturnedValue, typeofName, (ExecutionEngine *engine, int nameIndex)) \ - F(ReturnedValue, typeofScopeObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex)) \ - F(ReturnedValue, typeofContextObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex)) \ - F(ReturnedValue, typeofMember, (ExecutionEngine *engine, const Value &base, int nameIndex)) \ - F(ReturnedValue, typeofElement, (ExecutionEngine *engine, const Value &base, const Value &index)) \ - \ - /* delete */ \ - F(ReturnedValue, deleteElement, (ExecutionEngine *engine, const Value &base, const Value &index)) \ - F(ReturnedValue, deleteMember, (ExecutionEngine *engine, const Value &base, int nameIndex)) \ - F(ReturnedValue, deleteMemberString, (ExecutionEngine *engine, const Value &base, String *name)) \ - F(ReturnedValue, deleteName, (ExecutionEngine *engine, int nameIndex)) \ - \ - /* exceptions & scopes */ \ - F(void, throwException, (ExecutionEngine *engine, const Value &value)) \ - F(ReturnedValue, unwindException, (ExecutionEngine *engine)) \ - F(void, pushWithScope, (const Value &o, NoThrowEngine *engine)) \ - F(void, pushCatchScope, (NoThrowEngine *engine, int exceptionVarNameIndex)) \ - F(void, popScope, (NoThrowEngine *engine)) \ - \ - /* closures */ \ - F(ReturnedValue, closure, (ExecutionEngine *engine, int functionId)) \ - \ - /* function header */ \ - F(void, declareVar, (ExecutionEngine *engine, bool deletable, int nameIndex)) \ - F(ReturnedValue, setupArgumentsObject, (ExecutionEngine *engine)) \ - F(void, convertThisToObject, (ExecutionEngine *engine)) \ - \ - /* literals */ \ - F(ReturnedValue, arrayLiteral, (ExecutionEngine *engine, Value *values, uint length)) \ - F(ReturnedValue, objectLiteral, (ExecutionEngine *engine, const Value *args, int classId, int arrayValueCount, int arrayGetterSetterCountAndFlags)) \ - F(ReturnedValue, regexpLiteral, (ExecutionEngine *engine, int id)) \ - \ - /* foreach */ \ - F(ReturnedValue, foreachIterator, (ExecutionEngine *engine, const Value &in)) \ - F(ReturnedValue, foreachNextPropertyName, (const Value &foreach_iterator)) \ - \ - /* unary operators */ \ - F(ReturnedValue, uPlus, (const Value &value)) \ - F(ReturnedValue, uMinus, (const Value &value)) \ - F(ReturnedValue, uNot, (const Value &value)) \ - F(ReturnedValue, complement, (const Value &value)) \ - F(ReturnedValue, increment, (const Value &value)) \ - F(ReturnedValue, decrement, (const Value &value)) \ - \ - /* binary operators */ \ - F(ReturnedValue, instanceof, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - F(ReturnedValue, in, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - F(ReturnedValue, add, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - F(ReturnedValue, addString, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - F(ReturnedValue, bitOr, (const Value &left, const Value &right)) \ - F(ReturnedValue, bitXor, (const Value &left, const Value &right)) \ - F(ReturnedValue, bitAnd, (const Value &left, const Value &right)) \ - F(ReturnedValue, sub, (const Value &left, const Value &right)) \ - F(ReturnedValue, mul, (const Value &left, const Value &right)) \ - F(ReturnedValue, div, (const Value &left, const Value &right)) \ - F(ReturnedValue, mod, (const Value &left, const Value &right)) \ - F(ReturnedValue, shl, (const Value &left, const Value &right)) \ - F(ReturnedValue, shr, (const Value &left, const Value &right)) \ - F(ReturnedValue, ushr, (const Value &left, const Value &right)) \ - F(ReturnedValue, greaterThan, (const Value &left, const Value &right)) \ - F(ReturnedValue, lessThan, (const Value &left, const Value &right)) \ - F(ReturnedValue, greaterEqual, (const Value &left, const Value &right)) \ - F(ReturnedValue, lessEqual, (const Value &left, const Value &right)) \ - F(ReturnedValue, equal, (const Value &left, const Value &right)) \ - F(ReturnedValue, notEqual, (const Value &left, const Value &right)) \ - F(ReturnedValue, strictEqual, (const Value &left, const Value &right)) \ - F(ReturnedValue, strictNotEqual, (const Value &left, const Value &right)) \ - \ - /* comparisons */ \ - F(Bool, compareGreaterThan, (const Value &l, const Value &r)) \ - F(Bool, compareLessThan, (const Value &l, const Value &r)) \ - F(Bool, compareGreaterEqual, (const Value &l, const Value &r)) \ - F(Bool, compareLessEqual, (const Value &l, const Value &r)) \ - F(Bool, compareEqual, (const Value &left, const Value &right)) \ - F(Bool, compareNotEqual, (const Value &left, const Value &right)) \ - F(Bool, compareStrictEqual, (const Value &left, const Value &right)) \ - F(Bool, compareStrictNotEqual, (const Value &left, const Value &right)) \ - \ - F(Bool, compareInstanceof, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - F(Bool, compareIn, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - \ - /* conversions */ \ - F(Bool, toBoolean, (const Value &value)) \ - F(ReturnedValue, toDouble, (const Value &value)) \ - F(int, toInt, (const Value &value)) \ - F(int, doubleToInt, (const double &d)) \ - F(unsigned, toUInt, (const Value &value)) \ - F(unsigned, doubleToUInt, (const double &d)) \ - \ - /* qml */ \ - F(ReturnedValue, getQmlContext, (NoThrowEngine *engine)) \ - F(ReturnedValue, getQmlImportedScripts, (NoThrowEngine *engine)) \ - F(ReturnedValue, getQmlSingleton, (NoThrowEngine *engine, int nameIndex)) \ - F(ReturnedValue, getQmlAttachedProperty, (ExecutionEngine *engine, int attachedPropertiesId, int propertyIndex)) \ - F(ReturnedValue, getQmlScopeObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex, bool captureRequired)) \ - F(ReturnedValue, getQmlContextObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex, bool captureRequired)) \ - F(ReturnedValue, getQmlQObjectProperty, (ExecutionEngine *engine, const Value &object, int propertyIndex, bool captureRequired)) \ - F(ReturnedValue, getQmlSingletonQObjectProperty, (ExecutionEngine *engine, const Value &object, int propertyIndex, bool captureRequired)) \ - F(ReturnedValue, getQmlIdObject, (ExecutionEngine *engine, const Value &context, uint index)) \ - \ - F(void, setQmlScopeObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value)) \ - F(void, setQmlContextObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value)) \ - F(void, setQmlQObjectProperty, (ExecutionEngine *engine, const Value &object, int propertyIndex, const Value &value)) struct Q_QML_PRIVATE_EXPORT Runtime { - Runtime(); - typedef ReturnedValue (*UnaryOperation)(const Value &value); typedef ReturnedValue (*BinaryOperation)(const Value &left, const Value &right); - typedef ReturnedValue (*BinaryOperationContext)(ExecutionEngine *engine, const Value &left, const Value &right); + typedef ReturnedValue (*BinaryOperationContext)(ExecutionEngine *, const Value &left, const Value &right); + + enum class Throws { No, Yes }; + enum class ChangesContext { No, Yes }; + enum class Pure { No, Yes }; + enum class LastArgumentIsOutputValue { No, Yes }; + + template<Throws t, ChangesContext c = ChangesContext::No, Pure p = Pure::No, + LastArgumentIsOutputValue out = LastArgumentIsOutputValue::No> + struct Method + { + static constexpr bool throws = t == Throws::Yes; + static constexpr bool changesContext = c == ChangesContext::Yes; + static constexpr bool pure = p == Pure::Yes; + static constexpr bool lastArgumentIsOutputValue = out == LastArgumentIsOutputValue::Yes; + }; + using PureMethod = Method<Throws::No, ChangesContext::No, Pure::Yes>; + using IteratorMethod = Method<Throws::Yes, ChangesContext::No, Pure::No, + LastArgumentIsOutputValue::Yes>; + + /* call */ + struct Q_QML_PRIVATE_EXPORT CallGlobalLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, uint, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallName : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, int, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallPropertyLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, uint, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallElement : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallValue : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallWithReceiver : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallPossiblyDirectEval : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallWithSpread : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT TailCall : Method<Throws::Yes> + { + static ReturnedValue call(CppStackFrame *, ExecutionEngine *); + }; + + /* construct */ + struct Q_QML_PRIVATE_EXPORT Construct : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT ConstructWithSpread : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + + /* load & store */ + struct Q_QML_PRIVATE_EXPORT StoreNameStrict : Method<Throws::Yes> + { + static void call(ExecutionEngine *, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreNameSloppy : Method<Throws::Yes> + { + static void call(ExecutionEngine *, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreProperty : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreElement : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreElement_traced : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, const Value &, const Value &, quint8 *); + }; + struct Q_QML_PRIVATE_EXPORT LoadProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int); + }; + struct Q_QML_PRIVATE_EXPORT LoadName : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT LoadElement : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LoadElement_Traced : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, quint8 *); + }; + struct Q_QML_PRIVATE_EXPORT LoadSuperProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreSuperProperty : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LoadSuperConstructor : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LoadGlobalLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Function *, int); + }; + struct Q_QML_PRIVATE_EXPORT GetLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Function *, const Value &, int); + }; + struct Q_QML_PRIVATE_EXPORT SetLookupStrict : Method<Throws::Yes> + { + static void call(Function *, const Value &, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT SetLookupSloppy : Method<Throws::Yes> + { + static void call(Function *, const Value &, int, const Value &); + }; + + /* typeof */ + struct Q_QML_PRIVATE_EXPORT TypeofValue : PureMethod + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT TypeofName : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *, int); + }; + + /* delete */ + struct Q_QML_PRIVATE_EXPORT DeleteProperty_NoThrow : Method<Throws::No> + { + static Bool call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT DeleteProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Function *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT DeleteName_NoThrow : Method<Throws::No> + { + static Bool call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT DeleteName : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Function *, int); + }; + + /* exceptions & scopes */ + struct Q_QML_PRIVATE_EXPORT ThrowException : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT PushCallContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(CppStackFrame *); + }; + struct Q_QML_PRIVATE_EXPORT PushWithContext : Method<Throws::Yes, ChangesContext::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT PushCatchContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *, int, int); + }; + struct Q_QML_PRIVATE_EXPORT PushBlockContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT CloneBlockContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT PushScriptContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT PopScriptContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT ThrowReferenceError : Method<Throws::Yes> + { + static void call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT ThrowOnNullOrUndefined : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &); + }; + + /* closures */ + struct Q_QML_PRIVATE_EXPORT Closure : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *, int); + }; + + /* Function header */ + struct Q_QML_PRIVATE_EXPORT ConvertThisToObject : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT DeclareVar : Method<Throws::Yes> + { + static void call(ExecutionEngine *, Bool, int); + }; + struct Q_QML_PRIVATE_EXPORT CreateMappedArgumentsObject : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT CreateUnmappedArgumentsObject : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT CreateRestParameter : PureMethod + { + static ReturnedValue call(ExecutionEngine *, int); + }; -#define DEFINE_RUNTIME_METHOD_ENUM(returnvalue, name, args) name, - enum RuntimeMethods { - FOR_EACH_RUNTIME_METHOD(DEFINE_RUNTIME_METHOD_ENUM) - RuntimeMethodCount, - InvalidRuntimeMethod = RuntimeMethodCount + /* literals */ + struct Q_QML_PRIVATE_EXPORT ArrayLiteral : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Value[], uint); + }; + struct Q_QML_PRIVATE_EXPORT ObjectLiteral : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, int, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CreateClass : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, int, const Value &, Value[]); }; -#undef DEFINE_RUNTIME_METHOD_ENUM - void *runtimeMethods[RuntimeMethodCount]; + /* for-in, for-of and array destructuring */ + struct Q_QML_PRIVATE_EXPORT GetIterator : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int); + }; + struct Q_QML_PRIVATE_EXPORT IteratorNext : IteratorMethod + { + static ReturnedValue call(ExecutionEngine *, const Value &, Value *); + }; + struct Q_QML_PRIVATE_EXPORT IteratorNextForYieldStar : IteratorMethod + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value *); + }; + struct Q_QML_PRIVATE_EXPORT IteratorClose : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT DestructureRestElement : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; - static uint runtimeMethodOffset(RuntimeMethods method) { return method*QT_POINTER_SIZE; } + /* conversions */ + struct Q_QML_PRIVATE_EXPORT ToObject : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT ToBoolean : PureMethod + { + static Bool call(const Value &); + }; + struct Q_QML_PRIVATE_EXPORT ToNumber : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + /* unary operators */ + struct Q_QML_PRIVATE_EXPORT UMinus : Method<Throws::Yes> + { + static ReturnedValue call(const Value &); + }; -#define RUNTIME_METHOD(returnvalue, name, args) \ - typedef returnvalue (*Method_##name)args; \ - enum { Method_##name##_NeedsExceptionCheck = ExceptionCheck<Method_##name>::NeedsCheck }; \ - static returnvalue method_##name args; - FOR_EACH_RUNTIME_METHOD(RUNTIME_METHOD) -#undef RUNTIME_METHOD + /* binary operators */ + struct Q_QML_PRIVATE_EXPORT Instanceof : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT In : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Add : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Sub : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Mul : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Div : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Mod : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Exp : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT BitAnd : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT BitOr : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT BitXor : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Shl : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Shr : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT UShr : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT GreaterThan : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LessThan : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT GreaterEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LessEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Equal : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT NotEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StrictEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StrictNotEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + + /* comparisons */ + struct Q_QML_PRIVATE_EXPORT CompareGreaterThan : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareLessThan : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareGreaterEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareLessEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareNotEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareStrictEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareStrictNotEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + + struct Q_QML_PRIVATE_EXPORT CompareInstanceof : Method<Throws::Yes> + { + static Bool call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareIn : Method<Throws::Yes> + { + static Bool call(ExecutionEngine *, const Value &, const Value &); + }; + + struct Q_QML_PRIVATE_EXPORT RegexpLiteral : PureMethod + { + static ReturnedValue call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT GetTemplateObject : PureMethod + { + static ReturnedValue call(Function *, int); + }; + + /* qml */ + struct Q_QML_PRIVATE_EXPORT LoadQmlContext : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT LoadQmlImportedScripts : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT LoadQmlScopeObjectProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int, Bool); + }; + struct Q_QML_PRIVATE_EXPORT LoadQmlContextObjectProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int, Bool); + }; + struct Q_QML_PRIVATE_EXPORT LoadQmlIdObject : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, uint); + }; + struct Q_QML_PRIVATE_EXPORT CallQmlScopeObjectProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallQmlContextObjectProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT StoreQmlScopeObjectProperty : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreQmlContextObjectProperty : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, int, const Value &); + }; + + struct StackOffsets { + static const int tailCall_function = -1; + static const int tailCall_thisObject = -2; + static const int tailCall_argv = -3; + static const int tailCall_argc = -4; + }; }; static_assert(std::is_standard_layout<Runtime>::value, "Runtime needs to be standard layout in order for us to be able to use offsetof"); -static_assert(offsetof(Runtime, runtimeMethods) == 0, "JIT expects this to be the first member"); static_assert(sizeof(Runtime::BinaryOperation) == sizeof(void*), "JIT expects a function pointer to fit into a regular pointer, for cross-compilation offset translation"); } // namespace QV4 diff --git a/src/qml/jsruntime/qv4runtimecodegen.cpp b/src/qml/jsruntime/qv4runtimecodegen.cpp new file mode 100644 index 0000000000..9866966936 --- /dev/null +++ b/src/qml/jsruntime/qv4runtimecodegen.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4runtimecodegen_p.h" +#include "qv4compilerscanfunctions_p.h" + +using namespace QV4; + +void RuntimeCodegen::generateFromFunctionExpression(const QString &fileName, + const QString &sourceCode, + AST::FunctionExpression *ast, + Compiler::Module *module) +{ + _module = module; + _module->fileName = fileName; + _module->finalUrl = fileName; + _context = nullptr; + + Compiler::ScanFunctions scan(this, sourceCode, Compiler::ContextType::Global); + // fake a global environment + scan.enterEnvironment(nullptr, Compiler::ContextType::Function, QString()); + scan(ast); + scan.leaveEnvironment(); + + if (hasError) + return; + + int index = defineFunction(ast->name.toString(), ast, ast->formals, ast->body); + _module->rootContext = _module->functions.at(index); +} + +void RuntimeCodegen::throwSyntaxError(const AST::SourceLocation &loc, const QString &detail) +{ + if (hasError) + return; + hasError = true; + engine->throwSyntaxError(detail, _module->fileName, loc.startLine, loc.startColumn); +} + +void RuntimeCodegen::throwReferenceError(const AST::SourceLocation &loc, const QString &detail) +{ + if (hasError) + return; + hasError = true; + engine->throwReferenceError(detail, _module->fileName, loc.startLine, loc.startColumn); +} + diff --git a/src/qml/jsruntime/qv4runtimecodegen_p.h b/src/qml/jsruntime/qv4runtimecodegen_p.h new file mode 100644 index 0000000000..be66dc57ca --- /dev/null +++ b/src/qml/jsruntime/qv4runtimecodegen_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4RUNTIMECODEGEN_P_H +#define QV4RUNTIMECODEGEN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4codegen_p.h> + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +class RuntimeCodegen : public Compiler::Codegen +{ +public: + RuntimeCodegen(ExecutionEngine *engine, Compiler::JSUnitGenerator *jsUnitGenerator, bool strict) + : Codegen(jsUnitGenerator, strict) + , engine(engine) + {} + + void generateFromFunctionExpression(const QString &fileName, + const QString &sourceCode, + AST::FunctionExpression *ast, + Compiler::Module *module); + + void throwSyntaxError(const AST::SourceLocation &loc, const QString &detail) override; + void throwReferenceError(const AST::SourceLocation &loc, const QString &detail) override; +private: + ExecutionEngine *engine; +}; + +} + +QT_END_NAMESPACE + +#endif // QV4CODEGEN_P_H diff --git a/src/qml/jsruntime/qv4scopedvalue_p.h b/src/qml/jsruntime/qv4scopedvalue_p.h index 04a0c74133..e4aceef3ee 100644 --- a/src/qml/jsruntime/qv4scopedvalue_p.h +++ b/src/qml/jsruntime/qv4scopedvalue_p.h @@ -52,8 +52,8 @@ #include "qv4engine_p.h" #include "qv4value_p.h" -#include "qv4persistent_p.h" #include "qv4property_p.h" +#include "qv4propertykey_p.h" #ifdef V4_USE_VALGRIND #include <valgrind/memcheck.h> @@ -71,56 +71,45 @@ struct ScopedValue; #define CHECK_EXCEPTION() \ do { \ if (scope.hasException()) { \ - scope.result = QV4::Encode::undefined(); \ - return; \ + return QV4::Encode::undefined(); \ } \ } while (false) #define RETURN_UNDEFINED() \ - do { \ - scope.result = QV4::Encode::undefined(); \ - return; \ - } while (false) + return QV4::Encode::undefined() #define RETURN_RESULT(r) \ - do { \ - scope.result = r; \ - return; \ - } while (false) + return QV4::Encode(r) #define THROW_TYPE_ERROR() \ - do { \ - scope.result = scope.engine->throwTypeError(); \ - return; \ - } while (false) + return scope.engine->throwTypeError() #define THROW_GENERIC_ERROR(str) \ - do { \ - scope.result = scope.engine->throwError(QString::fromUtf8(str)); \ - return; \ - } while (false) + return scope.engine->throwError(QString::fromUtf8(str)) struct Scope { - inline Scope(ExecutionContext *ctx) + explicit Scope(ExecutionContext *ctx) : engine(ctx->engine()) , mark(engine->jsStackTop) - , result(*engine->jsAlloca(1)) { - result = Encode::undefined(); } explicit Scope(ExecutionEngine *e) : engine(e) , mark(engine->jsStackTop) - , result(*engine->jsAlloca(1)) { - result = Encode::undefined(); + } + + explicit Scope(const Managed *m) + : engine(m->engine()) + , mark(engine->jsStackTop) + { } ~Scope() { #ifndef QT_NO_DEBUG Q_ASSERT(engine->jsStackTop >= mark); - Q_ASSERT(engine->currentContext < mark); +// Q_ASSERT(engine->currentContext < mark); memset(mark, 0, (engine->jsStackTop - mark)*sizeof(Value)); #endif #ifdef V4_USE_VALGRIND @@ -129,8 +118,45 @@ struct Scope { engine->jsStackTop = mark; } - QML_NEARLY_ALWAYS_INLINE Value *alloc(int nValues) const { - return engine->jsAlloca(nValues); + enum AllocMode { + Undefined, + Empty, + /* Be careful when using Uninitialized, the stack has to be fully initialized before calling into the memory manager again */ + Uninitialized + }; + template <AllocMode mode = Undefined> + QML_NEARLY_ALWAYS_INLINE Value *alloc(int nValues) const + { + Value *ptr = engine->jsAlloca(nValues); + switch (mode) { + case Undefined: + for (int i = 0; i < nValues; ++i) + ptr[i] = Value::undefinedValue(); + break; + case Empty: + for (int i = 0; i < nValues; ++i) + ptr[i] = Value::emptyValue(); + break; + case Uninitialized: + break; + } + return ptr; + } + template <AllocMode mode = Undefined> + QML_NEARLY_ALWAYS_INLINE Value *alloc() const + { + Value *ptr = engine->jsAlloca(1); + switch (mode) { + case Undefined: + *ptr = Value::undefinedValue(); + break; + case Empty: + *ptr = Value::emptyValue(); + break; + case Uninitialized: + break; + } + return ptr; } bool hasException() const { @@ -139,7 +165,6 @@ struct Scope { ExecutionEngine *engine; Value *mark; - Value &result; private: Q_DISABLE_COPY(Scope) @@ -149,31 +174,31 @@ struct ScopedValue { ScopedValue(const Scope &scope) { - ptr = scope.engine->jsStackTop++; + ptr = scope.alloc<Scope::Uninitialized>(); ptr->setRawValue(0); } ScopedValue(const Scope &scope, const Value &v) { - ptr = scope.engine->jsStackTop++; + ptr = scope.alloc<Scope::Uninitialized>(); *ptr = v; } ScopedValue(const Scope &scope, Heap::Base *o) { - ptr = scope.engine->jsStackTop++; + ptr = scope.alloc<Scope::Uninitialized>(); ptr->setM(o); } ScopedValue(const Scope &scope, Managed *m) { - ptr = scope.engine->jsStackTop++; + ptr = scope.alloc<Scope::Uninitialized>(); ptr->setRawValue(m->asReturnedValue()); } ScopedValue(const Scope &scope, const ReturnedValue &v) { - ptr = scope.engine->jsStackTop++; + ptr = scope.alloc<Scope::Uninitialized>(); ptr->setRawValue(v); } @@ -216,77 +241,121 @@ struct ScopedValue Value *ptr; }; + +struct ScopedPropertyKey +{ + ScopedPropertyKey(const Scope &scope) + { + ptr = reinterpret_cast<PropertyKey *>(scope.alloc<Scope::Uninitialized>()); + *ptr = PropertyKey::invalid(); + } + + ScopedPropertyKey(const Scope &scope, const PropertyKey &v) + { + ptr = reinterpret_cast<PropertyKey *>(scope.alloc<Scope::Uninitialized>()); + *ptr = v; + } + + ScopedPropertyKey &operator=(const PropertyKey &other) { + *ptr = other; + return *this; + } + + PropertyKey *operator->() { + return ptr; + } + operator PropertyKey() const { + return *ptr; + } + + bool operator==(const PropertyKey &other) const { + return *ptr == other; + } + bool operator==(const ScopedPropertyKey &other) const { + return *ptr == *other.ptr; + } + bool operator!=(const PropertyKey &other) const { + return *ptr != other; + } + bool operator!=(const ScopedPropertyKey &other) const { + return *ptr != *other.ptr; + } + + PropertyKey *ptr; +}; + + template<typename T> struct Scoped { enum ConvertType { Convert }; QML_NEARLY_ALWAYS_INLINE void setPointer(const Managed *p) { - ptr->setM(p ? p->m() : 0); + ptr->setM(p ? p->m() : nullptr); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Undefined>(); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, const Value &v) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); setPointer(v.as<T>()); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, Heap::Base *o) { Value v; v = o; - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); setPointer(v.as<T>()); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, const ScopedValue &v) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); setPointer(v.ptr->as<T>()); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, const Value &v, ConvertType) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); ptr->setRawValue(value_convert<T>(scope.engine, v)); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, const Value *v) { - ptr = scope.engine->jsAlloca(1); - setPointer(v ? v->as<T>() : 0); + ptr = scope.alloc<Scope::Uninitialized>(); + setPointer(v ? v->as<T>() : nullptr); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, T *t) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); setPointer(t); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, const T *t) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); setPointer(t); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, typename T::Data *t) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); *ptr = t; } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, const ReturnedValue &v) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); setPointer(QV4::Value::fromReturnedValue(v).as<T>()); } QML_NEARLY_ALWAYS_INLINE Scoped(const Scope &scope, const ReturnedValue &v, ConvertType) { - ptr = scope.engine->jsAlloca(1); + ptr = scope.alloc<Scope::Uninitialized>(); ptr->setRawValue(value_convert<T>(scope.engine, QV4::Value::fromReturnedValue(v))); } @@ -303,7 +372,7 @@ struct Scoped return *this; } Scoped<T> &operator=(Value *v) { - setPointer(v ? v->as<T>() : 0); + setPointer(v ? v->as<T>() : nullptr); return *this; } @@ -337,10 +406,7 @@ struct Scoped return getPointer(); } - bool operator!() const { - return !ptr->m(); - } - operator void *() const { + explicit operator bool() const { return ptr->m(); } @@ -363,27 +429,6 @@ struct Scoped Value *ptr; }; -struct ScopedCallData { - ScopedCallData(const Scope &scope, int argc = 0) - { - int size = int(offsetof(QV4::CallData, args)/sizeof(QV4::Value)) + qMax(argc , int(QV4::Global::ReservedArgumentCount)); - ptr = reinterpret_cast<CallData *>(scope.alloc(size)); - ptr->tag = quint32(QV4::Value::ValueTypeInternal::Integer); - ptr->argc = argc; - } - - CallData *operator->() { - return ptr; - } - - operator CallData *() const { - return ptr; - } - - CallData *ptr; -}; - - inline Value &Value::operator =(const ScopedValue &v) { _val = v.ptr->rawValue(); @@ -411,25 +456,6 @@ struct ScopedProperty Property *property; }; -struct ExecutionContextSaver -{ - Scope scope; // this makes sure that a reference to context on the JS stack goes out of scope as soon as the context is not used anymore. - ExecutionContext *savedContext; - - ExecutionContextSaver(const Scope &scope) - : scope(scope.engine) - { - savedContext = scope.engine->currentContext; - } - ~ExecutionContextSaver() - { - Q_ASSERT(scope.engine->jsStackTop > scope.engine->currentContext); - scope.engine->currentContext = savedContext; - scope.engine->current = savedContext->d(); - } -}; - - } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp index 62145f36cc..7bbef3335e 100644 --- a/src/qml/jsruntime/qv4script.cpp +++ b/src/qml/jsruntime/qv4script.cpp @@ -45,6 +45,7 @@ #include "qv4debugging_p.h" #include "qv4profiling_p.h" #include "qv4scopedvalue_p.h" +#include "qv4jscall_p.h" #include <private/qqmljsengine_p.h> #include <private/qqmljslexer_p.h> @@ -52,24 +53,24 @@ #include <private/qqmljsast_p.h> #include <private/qqmlengine_p.h> #include <private/qv4profiling_p.h> -#include <qv4jsir_p.h> -#include <qv4codegen_p.h> +#include <qv4runtimecodegen_p.h> #include <QtCore/QDebug> #include <QtCore/QString> +#include <QScopedValueRollback> using namespace QV4; -Script::Script(ExecutionEngine *v4, QmlContext *qml, CompiledData::CompilationUnit *compilationUnit) - : line(0), column(0), scope(v4->rootContext()), strictMode(false), inheritContext(true), parsed(false) - , compilationUnit(compilationUnit), vmFunction(0), parseAsBinding(true) +Script::Script(ExecutionEngine *v4, QmlContext *qml, const QQmlRefPointer<CompiledData::CompilationUnit> &compilationUnit) + : line(1), column(0), context(v4->rootContext()), strictMode(false), inheritContext(true), parsed(false) + , compilationUnit(compilationUnit), vmFunction(nullptr), parseAsBinding(true) { if (qml) qmlContext.set(v4, *qml); parsed = true; - vmFunction = compilationUnit ? compilationUnit->linkToEngine(v4) : 0; + vmFunction = compilationUnit ? compilationUnit->linkToEngine(v4) : nullptr; } Script::~Script() @@ -81,16 +82,23 @@ void Script::parse() if (parsed) return; - using namespace QQmlJS; + using namespace QV4::Compiler; parsed = true; - ExecutionEngine *v4 = scope->engine(); + ExecutionEngine *v4 = context->engine(); Scope valueScope(v4); - IR::Module module(v4->debugger() != 0); + Module module(v4->debugger() != nullptr); - QQmlJS::Engine ee, *engine = ⅇ + if (sourceCode.startsWith(QLatin1String("function("))) { + static const int snippetLength = 70; + qWarning() << "Warning: Using function expressions as statements in scripts is not compliant with the ECMAScript specification:\n" + << (sourceCode.leftRef(snippetLength) + QLatin1String("...")) + << "\nThis will throw a syntax error in Qt 5.12. If you want a function expression, surround it by parentheses."; + } + + Engine ee, *engine = ⅇ Lexer lexer(engine); lexer.setCode(sourceCode, line, parseAsBinding); Parser parser(engine); @@ -98,7 +106,7 @@ void Script::parse() const bool parsed = parser.parseProgram(); const auto diagnosticMessages = parser.diagnosticMessages(); - for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { + for (const DiagnosticMessage &m : diagnosticMessages) { if (m.isError()) { valueScope.engine->throwSyntaxError(m.message, sourceFile, m.loc.startLine, m.loc.startColumn); return; @@ -117,25 +125,15 @@ void Script::parse() return; } - QStringList inheritedLocals; - if (inheritContext) { - Scoped<CallContext> ctx(valueScope, scope); - if (ctx) { - for (Identifier * const *i = ctx->variables(), * const *ei = i + ctx->variableCount(); i < ei; ++i) - inheritedLocals.append(*i ? (*i)->string : QString()); - } - } - - RuntimeCodegen cg(v4, strictMode); - cg.generateFromProgram(sourceFile, sourceCode, program, &module, QQmlJS::Codegen::EvalCode, inheritedLocals); + QV4::Compiler::JSUnitGenerator jsGenerator(&module); + RuntimeCodegen cg(v4, &jsGenerator, strictMode); + if (inheritContext) + cg.setUseFastLookups(false); + cg.generateFromProgram(sourceFile, sourceFile, sourceCode, program, &module, contextType); if (v4->hasException) return; - QV4::Compiler::JSUnitGenerator jsGenerator(&module); - QScopedPointer<EvalInstructionSelection> isel(v4->iselFactory->create(QQmlEnginePrivate::get(v4), v4->executableAllocator, &module, &jsGenerator)); - if (inheritContext) - isel->setUseFastLookups(false); - compilationUnit = isel->compile(); + compilationUnit = cg.generateCompilationUnit(); vmFunction = compilationUnit->linkToEngine(v4); } @@ -146,36 +144,24 @@ void Script::parse() } } -ReturnedValue Script::run() +ReturnedValue Script::run(const QV4::Value *thisObject) { if (!parsed) parse(); if (!vmFunction) return Encode::undefined(); - QV4::ExecutionEngine *engine = scope->engine(); + QV4::ExecutionEngine *engine = context->engine(); QV4::Scope valueScope(engine); if (qmlContext.isUndefined()) { - TemporaryAssignment<Function*> savedGlobalCode(engine->globalCode, vmFunction); - - ExecutionContextSaver ctxSaver(valueScope); - ContextStateSaver stateSaver(valueScope, scope); - scope->d()->strictMode = vmFunction->isStrict(); - scope->d()->lookups = vmFunction->compilationUnit->runtimeLookups; - scope->d()->constantTable = vmFunction->compilationUnit->constants; - scope->d()->compilationUnit = vmFunction->compilationUnit; + QScopedValueRollback<Function*> savedGlobalCode(engine->globalCode, vmFunction); - return Q_V4_PROFILE(engine, vmFunction); + return vmFunction->call(thisObject ? thisObject : engine->globalObject, nullptr, 0, + context); } else { Scoped<QmlContext> qml(valueScope, qmlContext.value()); - ScopedCallData callData(valueScope); - callData->thisObject = Primitive::undefinedValue(); - if (vmFunction->canUseSimpleFunction()) - qml->simpleCall(valueScope, callData, vmFunction); - else - qml->call(valueScope, callData, vmFunction); - return valueScope.result.asReturnedValue(); + return vmFunction->call(thisObject, nullptr, 0, qml); } } @@ -186,76 +172,76 @@ Function *Script::function() return vmFunction; } -QQmlRefPointer<QV4::CompiledData::CompilationUnit> Script::precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, QList<QQmlError> *reportedErrors, QQmlJS::Directives *directivesCollector) +QQmlRefPointer<QV4::CompiledData::CompilationUnit> Script::precompile(QV4::Compiler::Module *module, QQmlJS::Engine *jsEngine, Compiler::JSUnitGenerator *unitGenerator, + const QString &fileName, const QString &finalUrl, const QString &source, + QList<QQmlError> *reportedErrors, + QV4::Compiler::ContextType contextType) { - using namespace QQmlJS; + using namespace QV4::Compiler; using namespace QQmlJS::AST; - QQmlJS::Engine ee; - if (directivesCollector) - ee.setDirectives(directivesCollector); - QQmlJS::Lexer lexer(&ee); + Lexer lexer(jsEngine); lexer.setCode(source, /*line*/1, /*qml mode*/false); - QQmlJS::Parser parser(&ee); + Parser parser(jsEngine); parser.parseProgram(); - QList<QQmlError> errors; - - const auto diagnosticMessages = parser.diagnosticMessages(); - for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { - if (m.isWarning()) { - qWarning("%s:%d : %s", qPrintable(url.toString()), m.loc.startLine, qPrintable(m.message)); - continue; - } - - QQmlError error; - error.setUrl(url); - error.setDescription(m.message); - error.setLine(m.loc.startLine); - error.setColumn(m.loc.startColumn); - errors << error; - } - + QList<QQmlError> errors = QQmlEnginePrivate::qmlErrorFromDiagnostics(fileName, parser.diagnosticMessages()); if (!errors.isEmpty()) { if (reportedErrors) *reportedErrors << errors; - return 0; + return nullptr; } Program *program = AST::cast<Program *>(parser.rootNode()); if (!program) { // if parsing was successful, and we have no program, then // we're done...: - return 0; + return nullptr; } - QQmlJS::Codegen cg(/*strict mode*/false); - cg.generateFromProgram(url.toString(), source, program, module, QQmlJS::Codegen::EvalCode); + Codegen cg(unitGenerator, /*strict mode*/false); + cg.setUseFastLookups(false); + cg.generateFromProgram(fileName, finalUrl, source, program, module, contextType); errors = cg.qmlErrors(); if (!errors.isEmpty()) { if (reportedErrors) *reportedErrors << errors; - return 0; + return nullptr; } - QScopedPointer<EvalInstructionSelection> isel(engine->iselFactory->create(QQmlEnginePrivate::get(engine), engine->executableAllocator, module, unitGenerator)); - isel->setUseFastLookups(false); - return isel->compile(/*generate unit data*/false); + return cg.generateCompilationUnit(/*generate unit data*/false); } -QV4::ReturnedValue Script::evaluate(ExecutionEngine *engine, const QString &script, QmlContext *qmlContext) +Script *Script::createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl, QString *error) { - QV4::Scope scope(engine); - QV4::Script qmlScript(engine, qmlContext, script, QString()); - - qmlScript.parse(); - QV4::ScopedValue result(scope); - if (!scope.engine->hasException) - result = qmlScript.run(); - if (scope.engine->hasException) { - scope.engine->catchException(); - return Encode::undefined(); + if (error) + error->clear(); + + QQmlMetaType::CachedUnitLookupError cacheError = QQmlMetaType::CachedUnitLookupError::NoError; + if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(originalUrl, &cacheError)) { + QQmlRefPointer<QV4::CompiledData::CompilationUnit> jsUnit; + jsUnit.adopt(new QV4::CompiledData::CompilationUnit(cachedUnit)); + return new QV4::Script(engine, qmlContext, jsUnit); } - return result->asReturnedValue(); + + QFile f(fileName); + if (!f.open(QIODevice::ReadOnly)) { + if (error) { + if (cacheError == QQmlMetaType::CachedUnitLookupError::VersionMismatch) + *error = originalUrl.toString() + QString::fromUtf8(" was compiled ahead of time with an incompatible version of Qt and the original source code cannot be found. Please recompile"); + else + *error = QString::fromUtf8("Error opening source file %1: %2").arg(originalUrl.toString()).arg(f.errorString()); + } + return nullptr; + } + + QByteArray data = f.readAll(); + QString sourceCode = QString::fromUtf8(data); + QmlIR::Document::removeScriptPragmas(sourceCode); + + auto result = new QV4::Script(engine, qmlContext, /*parseAsBinding*/false, sourceCode, originalUrl.toString()); + result->contextType = QV4::Compiler::ContextType::ScriptImportedByQML; + result->parse(); + return result; } diff --git a/src/qml/jsruntime/qv4script_p.h b/src/qml/jsruntime/qv4script_p.h index 4ebe2dd609..a1e9b83a8b 100644 --- a/src/qml/jsruntime/qv4script_p.h +++ b/src/qml/jsruntime/qv4script_p.h @@ -54,6 +54,7 @@ #include "qv4engine_p.h" #include "qv4functionobject_p.h" #include "qv4qmlcontext_p.h" +#include "private/qv4compilercontext_p.h" #include <QQmlError> @@ -62,87 +63,49 @@ QT_BEGIN_NAMESPACE class QQmlContextData; namespace QQmlJS { -class Directives; +class Engine; } namespace QV4 { -struct ContextStateSaver { - Value *savedContext; - bool strictMode; - Lookup *lookups; - const QV4::Value *constantTable; - CompiledData::CompilationUnitBase *compilationUnit; - int lineNumber; - - ContextStateSaver(const Scope &scope, ExecutionContext *context) - : savedContext(scope.alloc(1)) - , strictMode(context->d()->strictMode) - , lookups(context->d()->lookups) - , constantTable(context->d()->constantTable) - , compilationUnit(context->d()->compilationUnit) - , lineNumber(context->d()->lineNumber) - { - savedContext->setM(context->d()); - } - ContextStateSaver(const Scope &scope, Heap::ExecutionContext *context) - : savedContext(scope.alloc(1)) - , strictMode(context->strictMode) - , lookups(context->lookups) - , constantTable(context->constantTable) - , compilationUnit(context->compilationUnit) - , lineNumber(context->lineNumber) - { - savedContext->setM(context); - } - - ~ContextStateSaver() - { - Heap::ExecutionContext *ctx = static_cast<Heap::ExecutionContext *>(savedContext->m()); - ctx->strictMode = strictMode; - ctx->lookups = lookups; - ctx->constantTable = constantTable; - ctx->compilationUnit = compilationUnit; - ctx->lineNumber = lineNumber; - } -}; - struct Q_QML_EXPORT Script { - Script(ExecutionContext *scope, const QString &sourceCode, const QString &source = QString(), int line = 1, int column = 0) + Script(ExecutionContext *scope, QV4::Compiler::ContextType mode, const QString &sourceCode, const QString &source = QString(), int line = 1, int column = 0) : sourceFile(source), line(line), column(column), sourceCode(sourceCode) - , scope(scope), strictMode(false), inheritContext(false), parsed(false) - , vmFunction(0), parseAsBinding(false) {} - Script(ExecutionEngine *engine, QmlContext *qml, const QString &sourceCode, const QString &source = QString(), int line = 1, int column = 0) + , context(scope), strictMode(false), inheritContext(false), parsed(false), contextType(mode) + , vmFunction(nullptr), parseAsBinding(false) {} + Script(ExecutionEngine *engine, QmlContext *qml, bool parseAsBinding, const QString &sourceCode, const QString &source = QString(), int line = 1, int column = 0) : sourceFile(source), line(line), column(column), sourceCode(sourceCode) - , scope(engine->rootContext()), strictMode(false), inheritContext(true), parsed(false) - , vmFunction(0), parseAsBinding(true) { + , context(engine->rootContext()), strictMode(false), inheritContext(true), parsed(false) + , vmFunction(nullptr), parseAsBinding(parseAsBinding) { if (qml) qmlContext.set(engine, *qml); } - Script(ExecutionEngine *engine, QmlContext *qml, CompiledData::CompilationUnit *compilationUnit); + Script(ExecutionEngine *engine, QmlContext *qml, const QQmlRefPointer<CompiledData::CompilationUnit> &compilationUnit); ~Script(); QString sourceFile; int line; int column; QString sourceCode; - ExecutionContext *scope; + ExecutionContext *context; bool strictMode; bool inheritContext; bool parsed; + QV4::Compiler::ContextType contextType = QV4::Compiler::ContextType::Eval; QV4::PersistentValue qmlContext; QQmlRefPointer<CompiledData::CompilationUnit> compilationUnit; Function *vmFunction; bool parseAsBinding; void parse(); - ReturnedValue run(); + ReturnedValue run(const QV4::Value *thisObject = nullptr); Function *function(); - static QQmlRefPointer<CompiledData::CompilationUnit> precompile(IR::Module *module, Compiler::JSUnitGenerator *unitGenerator, ExecutionEngine *engine, const QUrl &url, const QString &source, - QList<QQmlError> *reportedErrors = 0, QQmlJS::Directives *directivesCollector = 0); - - static ReturnedValue evaluate(ExecutionEngine *engine, const QString &script, QmlContext *qmlContext); + static QQmlRefPointer<CompiledData::CompilationUnit> precompile(QV4::Compiler::Module *module, QQmlJS::Engine *jsEngine, Compiler::JSUnitGenerator *unitGenerator, + const QString &fileName, const QString &finalUrl, const QString &source, + QList<QQmlError> *reportedErrors = nullptr, + QV4::Compiler::ContextType contextType = QV4::Compiler::ContextType::Global); + static Script *createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl, QString *error); }; } diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp index 8afc672aa2..1eef12a491 100644 --- a/src/qml/jsruntime/qv4sequenceobject.cpp +++ b/src/qml/jsruntime/qv4sequenceobject.cpp @@ -45,6 +45,7 @@ #include <private/qv4arrayobject_p.h> #include <private/qqmlengine_p.h> #include <private/qv4scopedvalue_p.h> +#include <private/qv4jscall_p.h> #include "qv4runtime_p.h" #include "qv4objectiterator_p.h" #include <private/qqmlvaluetypewrapper_p.h> @@ -66,10 +67,10 @@ static void generateWarning(QV4::ExecutionEngine *v4, const QString& description QQmlError retn; retn.setDescription(description); - QV4::StackFrame frame = v4->currentStackFrame(); + QV4::CppStackFrame *stackFrame = v4->currentStackFrame; - retn.setLine(frame.line); - retn.setUrl(QUrl(frame.source)); + retn.setLine(stackFrame->lineNumber()); + retn.setUrl(QUrl(stackFrame->source())); QQmlEnginePrivate::warning(engine, retn); } @@ -226,7 +227,7 @@ namespace Heap { template <typename Container> struct QQmlSequence : Object { void init(const Container &container); - void init(QObject *object, int propertyIndex); + void init(QObject *object, int propertyIndex, bool readOnly); void destroy() { delete container; object.destroy(); @@ -236,7 +237,8 @@ struct QQmlSequence : Object { mutable Container *container; QQmlQPointer<QObject> object; int propertyIndex; - bool isReference; + bool isReference : 1; + bool isReadOnly : 1; }; } @@ -293,6 +295,9 @@ public: return false; } + if (d()->isReadOnly) + return false; + if (d()->isReference) { if (!d()->object) return false; @@ -337,27 +342,37 @@ public: return (index < size_t(d()->container->size())) ? QV4::Attr_Data : QV4::Attr_Invalid; } - void containerAdvanceIterator(ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attrs) + struct OwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator { - name->setM(0); - *index = UINT_MAX; + ~OwnPropertyKeyIterator() override = default; + PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override + { + const QQmlSequence *s = static_cast<const QQmlSequence *>(o); - if (d()->isReference) { - if (!d()->object) { - QV4::Object::advanceIterator(this, it, name, index, p, attrs); - return; + if (s->d()->isReference) { + if (!s->d()->object) + return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); + s->loadReference(); } - loadReference(); - } - if (it->arrayIndex < static_cast<uint>(d()->container->size())) { - *index = it->arrayIndex; - ++it->arrayIndex; - *attrs = QV4::Attr_Data; - p->value = convertElementToValue(engine(), d()->container->at(*index)); - return; + if (arrayIndex < static_cast<uint>(s->d()->container->size())) { + uint index = arrayIndex; + ++arrayIndex; + if (attrs) + *attrs = QV4::Attr_Data; + if (pd) + pd->value = convertElementToValue(s->engine(), s->d()->container->at(index)); + return PropertyKey::fromArrayIndex(index); + } + + return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); } - QV4::Object::advanceIterator(this, it, name, index, p, attrs); + }; + + static OwnPropertyKeyIterator *containerOwnPropertyKeys(const Object *m, Value *target) + { + *target = *m; + return new OwnPropertyKeyIterator; } bool containerDeleteIndexedProperty(uint index) @@ -365,6 +380,8 @@ public: /* Qt containers have int (rather than uint) allowable indexes. */ if (index > INT_MAX) return false; + if (d()->isReadOnly) + return false; if (d()->isReference) { if (!d()->object) return false; @@ -416,13 +433,14 @@ public: bool operator()(typename Container::value_type lhs, typename Container::value_type rhs) { QV4::Scope scope(m_v4); - ScopedObject compare(scope, m_compareFn); - ScopedCallData callData(scope, 2); - callData->args[0] = convertElementToValue(m_v4, lhs); - callData->args[1] = convertElementToValue(m_v4, rhs); - callData->thisObject = m_v4->globalObject; - compare->call(scope, callData); - return scope.result.toNumber() < 0; + ScopedFunctionObject compare(scope, m_compareFn); + if (!compare) + return m_v4->throwTypeError(); + Value *argv = scope.alloc(2); + argv[0] = convertElementToValue(m_v4, lhs); + argv[1] = convertElementToValue(m_v4, rhs); + QV4::ScopedValue result(scope, compare->call(m_v4->globalObject, argv, 2)); + return result->toNumber() < 0; } private: @@ -430,16 +448,18 @@ public: const QV4::Value *m_compareFn; }; - void sort(const BuiltinFunction *, Scope &scope, CallData *callData) + bool sort(const FunctionObject *f, const Value *, const Value *argv, int argc) { + if (d()->isReadOnly) + return false; if (d()->isReference) { if (!d()->object) - return; + return false; loadReference(); } - if (callData->argc == 1 && callData->args[0].as<FunctionObject>()) { - CompareFunctor cf(scope.engine, callData->args[0]); + if (argc == 1 && argv[0].as<FunctionObject>()) { + CompareFunctor cf(f->engine(), argv[0]); std::sort(d()->container->begin(), d()->container->end(), cf); } else { DefaultCompareFunctor cf; @@ -448,11 +468,14 @@ public: if (d()->isReference) storeReference(); + + return true; } - static void method_get_length(const BuiltinFunction *, Scope &scope, CallData *callData) + static QV4::ReturnedValue method_get_length(const FunctionObject *b, const Value *thisObject, const Value *, int) { - QV4::Scoped<QQmlSequence<Container> > This(scope, callData->thisObject.as<QQmlSequence<Container> >()); + QV4::Scope scope(b); + QV4::Scoped<QQmlSequence<Container>> This(scope, thisObject->as<QQmlSequence<Container> >()); if (!This) THROW_TYPE_ERROR(); @@ -464,18 +487,23 @@ public: RETURN_RESULT(Encode(qint32(This->d()->container->size()))); } - static void method_set_length(const BuiltinFunction *, Scope &scope, CallData *callData) + static QV4::ReturnedValue method_set_length(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { - QV4::Scoped<QQmlSequence<Container> > This(scope, callData->thisObject.as<QQmlSequence<Container> >()); + QV4::Scope scope(f); + QV4::Scoped<QQmlSequence<Container>> This(scope, thisObject->as<QQmlSequence<Container> >()); if (!This) THROW_TYPE_ERROR(); - quint32 newLength = callData->args[0].toUInt32(); + quint32 newLength = argc ? argv[0].toUInt32() : 0; /* Qt containers have int (rather than uint) allowable indexes. */ if (newLength > INT_MAX) { generateWarning(scope.engine, QLatin1String("Index out of range during length set")); RETURN_UNDEFINED(); } + + if (This->d()->isReadOnly) + THROW_TYPE_ERROR(); + /* Read the sequence from the QObject property if we're a reference */ if (This->d()->isReference) { if (!This->d()->object) @@ -520,7 +548,7 @@ public: quint32 length = array->getLength(); QV4::ScopedValue v(scope); for (quint32 i = 0; i < length; ++i) - result.push_back(convertValueToElement<typename Container::value_type>((v = array->getIndexed(i)))); + result.push_back(convertValueToElement<typename Container::value_type>((v = array->get(i)))); return QVariant::fromValue(result); } @@ -531,7 +559,7 @@ public: { Q_ASSERT(d()->object); Q_ASSERT(d()->isReference); - void *a[] = { d()->container, 0 }; + void *a[] = { d()->container, nullptr }; QMetaObject::metacall(d()->object, QMetaObject::ReadProperty, d()->propertyIndex, a); } @@ -541,22 +569,36 @@ public: Q_ASSERT(d()->isReference); int status = -1; QQmlPropertyData::WriteFlags flags = QQmlPropertyData::DontRemoveBinding; - void *a[] = { d()->container, 0, &status, &flags }; + void *a[] = { d()->container, nullptr, &status, &flags }; QMetaObject::metacall(d()->object, QMetaObject::WriteProperty, d()->propertyIndex, a); } - static QV4::ReturnedValue getIndexed(const QV4::Managed *that, uint index, bool *hasProperty) - { return static_cast<const QQmlSequence<Container> *>(that)->containerGetIndexed(index, hasProperty); } - static bool putIndexed(Managed *that, uint index, const QV4::Value &value) - { return static_cast<QQmlSequence<Container> *>(that)->containerPutIndexed(index, value); } + static QV4::ReturnedValue virtualGet(const QV4::Managed *that, PropertyKey id, const Value *receiver, bool *hasProperty) + { + if (!id.isArrayIndex()) + return Object::virtualGet(that, id, receiver, hasProperty); + return static_cast<const QQmlSequence<Container> *>(that)->containerGetIndexed(id.asArrayIndex(), hasProperty); + } + static bool virtualPut(Managed *that, PropertyKey id, const QV4::Value &value, Value *receiver) + { + if (id.isArrayIndex()) + return static_cast<QQmlSequence<Container> *>(that)->containerPutIndexed(id.asArrayIndex(), value); + return Object::virtualPut(that, id, value, receiver); + } static QV4::PropertyAttributes queryIndexed(const QV4::Managed *that, uint index) { return static_cast<const QQmlSequence<Container> *>(that)->containerQueryIndexed(index); } - static bool deleteIndexedProperty(QV4::Managed *that, uint index) - { return static_cast<QQmlSequence<Container> *>(that)->containerDeleteIndexedProperty(index); } - static bool isEqualTo(Managed *that, Managed *other) + static bool virtualDeleteProperty(QV4::Managed *that, PropertyKey id) + { + if (id.isArrayIndex()) { + uint index = id.asArrayIndex(); + return static_cast<QQmlSequence<Container> *>(that)->containerDeleteIndexedProperty(index); + } + return Object::virtualDeleteProperty(that, id); + } + static bool virtualIsEqualTo(Managed *that, Managed *other) { return static_cast<QQmlSequence<Container> *>(that)->containerIsEqualTo(other); } - static void advanceIterator(Managed *that, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attrs) - { return static_cast<QQmlSequence<Container> *>(that)->containerAdvanceIterator(it, name, index, p, attrs); } + static QV4::OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target) + { return static_cast<const QQmlSequence<Container> *>(m)->containerOwnPropertyKeys(m, target);} }; @@ -568,6 +610,7 @@ void Heap::QQmlSequence<Container>::init(const Container &container) this->container = new Container(container); propertyIndex = -1; isReference = false; + isReadOnly = false; object.init(); QV4::Scope scope(internalClass->engine); @@ -577,12 +620,13 @@ void Heap::QQmlSequence<Container>::init(const Container &container) } template <typename Container> -void Heap::QQmlSequence<Container>::init(QObject *object, int propertyIndex) +void Heap::QQmlSequence<Container>::init(QObject *object, int propertyIndex, bool readOnly) { Object::init(); this->container = new Container; this->propertyIndex = propertyIndex; isReference = true; + this->isReadOnly = readOnly; this->object.init(object); QV4::Scope scope(internalClass->engine); QV4::Scoped<QV4::QQmlSequence<Container> > o(scope, this); @@ -647,25 +691,32 @@ void SequencePrototype::init() } #undef REGISTER_QML_SEQUENCE_METATYPE -void SequencePrototype::method_sort(const BuiltinFunction *b, Scope &scope, CallData *callData) +ReturnedValue SequencePrototype::method_valueOf(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + return Encode(thisObject->toString(f->engine())); +} + +ReturnedValue SequencePrototype::method_sort(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QV4::ScopedObject o(scope, callData->thisObject); + Scope scope(b); + QV4::ScopedObject o(scope, thisObject); if (!o || !o->isListType()) THROW_TYPE_ERROR(); - if (callData->argc >= 2) - RETURN_RESULT(o); + if (argc >= 2) + return o.asReturnedValue(); #define CALL_SORT(SequenceElementType, SequenceElementTypeName, SequenceType, DefaultValue) \ if (QQml##SequenceElementTypeName##List *s = o->as<QQml##SequenceElementTypeName##List>()) { \ - s->sort(b, scope, callData); \ + if (!s->sort(b, thisObject, argv, argc)) \ + THROW_TYPE_ERROR(); \ } else FOREACH_QML_SEQUENCE_TYPE(CALL_SORT) #undef CALL_SORT {} - RETURN_RESULT(o); + return o.asReturnedValue(); } #define IS_SEQUENCE(unused1, unused2, SequenceType, unused3) \ @@ -681,11 +732,11 @@ bool SequencePrototype::isSequenceType(int sequenceTypeId) #define NEW_REFERENCE_SEQUENCE(ElementType, ElementTypeName, SequenceType, unused) \ if (sequenceType == qMetaTypeId<SequenceType>()) { \ - QV4::ScopedObject obj(scope, engine->memoryManager->allocObject<QQml##ElementTypeName##List>(object, propertyIndex)); \ + QV4::ScopedObject obj(scope, engine->memoryManager->allocate<QQml##ElementTypeName##List>(object, propertyIndex, readOnly)); \ return obj.asReturnedValue(); \ } else -ReturnedValue SequencePrototype::newSequence(QV4::ExecutionEngine *engine, int sequenceType, QObject *object, int propertyIndex, bool *succeeded) +ReturnedValue SequencePrototype::newSequence(QV4::ExecutionEngine *engine, int sequenceType, QObject *object, int propertyIndex, bool readOnly, bool *succeeded) { QV4::Scope scope(engine); // This function is called when the property is a QObject Q_PROPERTY of @@ -699,7 +750,7 @@ ReturnedValue SequencePrototype::newSequence(QV4::ExecutionEngine *engine, int s #define NEW_COPY_SEQUENCE(ElementType, ElementTypeName, SequenceType, unused) \ if (sequenceType == qMetaTypeId<SequenceType>()) { \ - QV4::ScopedObject obj(scope, engine->memoryManager->allocObject<QQml##ElementTypeName##List>(v.value<SequenceType >())); \ + QV4::ScopedObject obj(scope, engine->memoryManager->allocate<QQml##ElementTypeName##List>(v.value<SequenceType >())); \ return obj.asReturnedValue(); \ } else diff --git a/src/qml/jsruntime/qv4sequenceobject_p.h b/src/qml/jsruntime/qv4sequenceobject_p.h index 2b8d1ea716..da71215bed 100644 --- a/src/qml/jsruntime/qv4sequenceobject_p.h +++ b/src/qml/jsruntime/qv4sequenceobject_p.h @@ -59,6 +59,8 @@ #include "qv4context_p.h" #include "qv4string_p.h" +QT_REQUIRE_CONFIG(qml_sequence_object); + QT_BEGIN_NAMESPACE namespace QV4 { @@ -68,15 +70,11 @@ struct SequencePrototype : public QV4::Object V4_PROTOTYPE(arrayPrototype) void init(); - static void method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData) - { - scope.result = callData->thisObject.toString(scope.engine); - } - - static void method_sort(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_valueOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_sort(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); static bool isSequenceType(int sequenceTypeId); - static ReturnedValue newSequence(QV4::ExecutionEngine *engine, int sequenceTypeId, QObject *object, int propertyIndex, bool *succeeded); + static ReturnedValue newSequence(QV4::ExecutionEngine *engine, int sequenceTypeId, QObject *object, int propertyIndex, bool readOnly, bool *succeeded); static ReturnedValue fromVariant(QV4::ExecutionEngine *engine, const QVariant& v, bool *succeeded); static int metaTypeForSequence(const Object *object); static QVariant toVariant(Object *object); diff --git a/src/qml/jsruntime/qv4serialize.cpp b/src/qml/jsruntime/qv4serialize.cpp index 14def49d0a..50871a4d87 100644 --- a/src/qml/jsruntime/qv4serialize.cpp +++ b/src/qml/jsruntime/qv4serialize.cpp @@ -40,13 +40,17 @@ #include "qv4serialize_p.h" #include <private/qv8engine_p.h> +#if QT_CONFIG(qml_list_model) #include <private/qqmllistmodel_p.h> #include <private/qqmllistmodelworkeragent_p.h> +#endif #include <private/qv4value_p.h> #include <private/qv4dateobject_p.h> #include <private/qv4regexpobject_p.h> +#if QT_CONFIG(qml_sequence_object) #include <private/qv4sequenceobject_p.h> +#endif #include <private/qv4objectproto_p.h> #include <private/qv4qobjectwrapper_p.h> @@ -82,8 +86,12 @@ enum Type { WorkerNumber, WorkerDate, WorkerRegexp, +#if QT_CONFIG(qml_list_model) WorkerListModel, +#endif +#if QT_CONFIG(qml_sequence_object) WorkerSequence +#endif }; static inline quint32 valueheader(Type type, quint32 size = 0) @@ -189,7 +197,7 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine push(data, valueheader(WorkerArray, length)); ScopedValue val(scope); for (uint ii = 0; ii < length; ++ii) - serialize(data, (val = array->getIndexed(ii)), engine); + serialize(data, (val = array->get(ii)), engine); } else if (v.isInteger()) { reserve(data, 2 * sizeof(quint32)); push(data, valueheader(WorkerInt32)); @@ -228,6 +236,7 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine } else if (const QObjectWrapper *qobjectWrapper = v.as<QV4::QObjectWrapper>()) { // XXX TODO: Generalize passing objects between the main thread and worker scripts so // that others can trivially plug in their elements. +#if QT_CONFIG(qml_list_model) QQmlListModel *lm = qobject_cast<QQmlListModel *>(qobjectWrapper->object()); if (lm && lm->agent()) { QQmlListModelWorkerAgent *agent = lm->agent(); @@ -236,9 +245,13 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine push(data, (void *)agent); return; } +#else + Q_UNUSED(qobjectWrapper); +#endif // No other QObject's are allowed to be sent push(data, valueheader(WorkerUndefined)); } else if (const Object *o = v.as<Object>()) { +#if QT_CONFIG(qml_sequence_object) if (o->isListType()) { // valid sequence. we generate a length (sequence length + 1 for the sequence type) uint seqLength = ScopedValue(scope, o->get(engine->id_length()))->toUInt32(); @@ -249,13 +262,14 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine } reserve(data, sizeof(quint32) + length * sizeof(quint32)); push(data, valueheader(WorkerSequence, length)); - serialize(data, QV4::Primitive::fromInt32(QV4::SequencePrototype::metaTypeForSequence(o)), engine); // sequence type + serialize(data, QV4::Value::fromInt32(QV4::SequencePrototype::metaTypeForSequence(o)), engine); // sequence type ScopedValue val(scope); for (uint ii = 0; ii < seqLength; ++ii) - serialize(data, (val = o->getIndexed(ii)), engine); // sequence elements + serialize(data, (val = o->get(ii)), engine); // sequence elements return; } +#endif // regular object QV4::ScopedValue val(scope, v); @@ -269,7 +283,7 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine QV4::ScopedValue s(scope); for (quint32 ii = 0; ii < length; ++ii) { - s = properties->getIndexed(ii); + s = properties->get(ii); serialize(data, s, engine); QV4::String *str = s->as<String>(); @@ -318,7 +332,7 @@ ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) ScopedValue v(scope); for (quint32 ii = 0; ii < size; ++ii) { v = deserialize(data, engine); - a->putIndexed(ii, v); + a->put(ii, v); } return a.asReturnedValue(); } @@ -344,7 +358,7 @@ ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) case WorkerNumber: return QV4::Encode(popDouble(data)); case WorkerDate: - return QV4::Encode(engine->newDateObject(QV4::Primitive::fromDouble(popDouble(data)))); + return QV4::Encode(engine->newDateObject(QV4::Value::fromDouble(popDouble(data)))); case WorkerRegexp: { quint32 flags = headersize(header); @@ -353,6 +367,7 @@ ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) data += ALIGN(length * sizeof(quint16)); return Encode(engine->newRegExpObject(pattern, flags)); } +#if QT_CONFIG(qml_list_model) case WorkerListModel: { void *ptr = popPtr(data); @@ -369,6 +384,8 @@ ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) agent->setEngine(engine); return rv->asReturnedValue(); } +#endif +#if QT_CONFIG(qml_sequence_object) case WorkerSequence: { ScopedValue value(scope); @@ -387,6 +404,7 @@ ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) QVariant seqVariant = QV4::SequencePrototype::toVariant(array, sequenceType, &succeeded); return QV4::SequencePrototype::fromVariant(engine, seqVariant, &succeeded); } +#endif } Q_ASSERT(!"Unreachable"); return QV4::Encode::undefined(); diff --git a/src/qml/jsruntime/qv4setiterator.cpp b/src/qml/jsruntime/qv4setiterator.cpp new file mode 100644 index 0000000000..d32e2079a0 --- /dev/null +++ b/src/qml/jsruntime/qv4setiterator.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qv4iterator_p.h> +#include <private/qv4estable_p.h> +#include <private/qv4setiterator_p.h> +#include <private/qv4setobject_p.h> +#include <private/qv4symbol_p.h> + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(SetIteratorObject); + +void SetIteratorPrototype::init(ExecutionEngine *e) +{ + defineDefaultProperty(QStringLiteral("next"), method_next, 0); + + Scope scope(e); + ScopedString val(scope, e->newString(QLatin1String("Set Iterator"))); + defineReadonlyConfigurableProperty(e->symbol_toStringTag(), val); +} + +ReturnedValue SetIteratorPrototype::method_next(const FunctionObject *b, const Value *that, const Value *, int) +{ + Scope scope(b); + const SetIteratorObject *thisObject = that->as<SetIteratorObject>(); + if (!thisObject) + return scope.engine->throwTypeError(QLatin1String("Not a Set Iterator instance")); + + Scoped<SetObject> s(scope, thisObject->d()->iteratedSet); + uint index = thisObject->d()->setNextIndex; + IteratorKind itemKind = thisObject->d()->iterationKind; + + if (!s) { + QV4::Value undefined = Value::undefinedValue(); + return IteratorPrototype::createIterResultObject(scope.engine, undefined, true); + } + + Value *arguments = scope.alloc(2); + + while (index < s->d()->esTable->size()) { + s->d()->esTable->iterate(index, &arguments[0], &arguments[1]); + thisObject->d()->setNextIndex = index + 1; + + if (itemKind == KeyValueIteratorKind) { + ScopedArrayObject resultArray(scope, scope.engine->newArrayObject()); + resultArray->arrayReserve(2); + resultArray->arrayPut(0, arguments[0]); + resultArray->arrayPut(1, arguments[0]); // yes, the key is repeated. + resultArray->setArrayLengthUnchecked(2); + + return IteratorPrototype::createIterResultObject(scope.engine, resultArray, false); + } + + return IteratorPrototype::createIterResultObject(scope.engine, arguments[0], false); + } + + thisObject->d()->iteratedSet.set(scope.engine, nullptr); + QV4::Value undefined = Value::undefinedValue(); + return IteratorPrototype::createIterResultObject(scope.engine, undefined, true); +} + diff --git a/src/qml/jsruntime/qv4setiterator_p.h b/src/qml/jsruntime/qv4setiterator_p.h new file mode 100644 index 0000000000..78eda6d57b --- /dev/null +++ b/src/qml/jsruntime/qv4setiterator_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4SETITERATOR_P_H +#define QV4SETITERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4iterator_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +namespace Heap { + +#define SetIteratorObjectMembers(class, Member) \ + Member(class, Pointer, Object *, iteratedSet) \ + Member(class, NoMark, IteratorKind, iterationKind) \ + Member(class, NoMark, quint32, setNextIndex) + +DECLARE_HEAP_OBJECT(SetIteratorObject, Object) { + DECLARE_MARKOBJECTS(SetIteratorObject); + void init(Object *obj, QV4::ExecutionEngine *engine) + { + Object::init(); + this->iteratedSet.set(engine, obj); + this->setNextIndex = 0; + } +}; + +} + +struct SetIteratorPrototype : Object +{ + V4_PROTOTYPE(iteratorPrototype) + void init(ExecutionEngine *engine); + + static ReturnedValue method_next(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); +}; + +struct SetIteratorObject : Object +{ + V4_OBJECT2(SetIteratorObject, Object) + Q_MANAGED_TYPE(SetIteratorObject) + V4_PROTOTYPE(setIteratorPrototype) + + void init(ExecutionEngine *engine); +}; + + +} + +QT_END_NAMESPACE + +#endif // QV4SETITERATOR_P_H + diff --git a/src/qml/jsruntime/qv4setobject.cpp b/src/qml/jsruntime/qv4setobject.cpp new file mode 100644 index 0000000000..1664d1bd71 --- /dev/null +++ b/src/qml/jsruntime/qv4setobject.cpp @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qv4setobject_p.h" +#include "qv4setiterator_p.h" +#include "qv4estable_p.h" +#include "qv4symbol_p.h" + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(SetCtor); +DEFINE_OBJECT_VTABLE(WeakSetCtor); +DEFINE_OBJECT_VTABLE(SetObject); + +void Heap::WeakSetCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("WeakSet")); +} + +void Heap::SetCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("Set")); +} + +ReturnedValue WeakSetCtor::construct(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget, bool isWeak) +{ + Scope scope(f); + Scoped<SetObject> a(scope, scope.engine->memoryManager->allocate<SetObject>()); + bool protoSet = false; + if (newTarget) + protoSet = a->setProtoFromNewTarget(newTarget); + if (!protoSet && isWeak) + a->setPrototypeOf(scope.engine->weakSetPrototype()); + a->d()->isWeakSet = isWeak; + + if (argc > 0) { + ScopedValue iterable(scope, argv[0]); + if (!iterable->isUndefined() && !iterable->isNull()) { + ScopedFunctionObject adder(scope, a->get(ScopedString(scope, scope.engine->newString(QString::fromLatin1("add"))))); + if (!adder) + return scope.engine->throwTypeError(); + ScopedObject iter(scope, Runtime::GetIterator::call(scope.engine, iterable, true)); + CHECK_EXCEPTION(); + if (!iter) + return a.asReturnedValue(); + + Value *nextValue = scope.alloc(1); + ScopedValue done(scope); + forever { + done = Runtime::IteratorNext::call(scope.engine, iter, nextValue); + CHECK_EXCEPTION(); + if (done->toBoolean()) + return a.asReturnedValue(); + + adder->call(a, nextValue, 1); + if (scope.engine->hasException) { + ScopedValue falsey(scope, Encode(false)); + return Runtime::IteratorClose::call(scope.engine, iter, falsey); + } + } + } + } + + return a.asReturnedValue(); +} + +ReturnedValue WeakSetCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + return construct(f, argv, argc, newTarget, true); +} + +ReturnedValue WeakSetCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) +{ + Scope scope(f); + return scope.engine->throwTypeError(QString::fromLatin1("Set requires new")); +} + +ReturnedValue SetCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) +{ + return construct(f, argv, argc, newTarget, false); +} + +void WeakSetPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedObject o(scope); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0)); + ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); + defineDefaultProperty(engine->id_constructor(), (o = ctor)); + + defineDefaultProperty(QStringLiteral("add"), method_add, 1); + defineDefaultProperty(QStringLiteral("delete"), method_delete, 1); + defineDefaultProperty(QStringLiteral("has"), method_has, 1); + + ScopedString val(scope, engine->newString(QLatin1String("WeakSet"))); + defineReadonlyConfigurableProperty(engine->symbol_toStringTag(), val); +} + +ReturnedValue WeakSetPrototype::method_add(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if ((!that || !that->d()->isWeakSet) || + (!argc || !argv[0].isObject())) + return scope.engine->throwTypeError(); + + that->d()->esTable->set(argv[0], Value::undefinedValue()); + return that.asReturnedValue(); +} + +ReturnedValue WeakSetPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || !that->d()->isWeakSet) + return scope.engine->throwTypeError(); + if (!argc || !argv[0].isObject()) + return Encode(false); + + return Encode(that->d()->esTable->remove(argv[0])); +} + +ReturnedValue WeakSetPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || !that->d()->isWeakSet) + return scope.engine->throwTypeError(); + if (!argc || !argv[0].isObject()) + return Encode(false); + + return Encode(that->d()->esTable->has(argv[0])); +} + +void SetPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedObject o(scope); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0)); + ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); + ctor->addSymbolSpecies(); + defineDefaultProperty(engine->id_constructor(), (o = ctor)); + + defineDefaultProperty(QStringLiteral("add"), method_add, 1); + defineDefaultProperty(QStringLiteral("clear"), method_clear, 0); + defineDefaultProperty(QStringLiteral("delete"), method_delete, 1); + defineDefaultProperty(QStringLiteral("entries"), method_entries, 0); + defineDefaultProperty(QStringLiteral("forEach"), method_forEach, 1); + defineDefaultProperty(QStringLiteral("has"), method_has, 1); + defineAccessorProperty(QStringLiteral("size"), method_get_size, nullptr); + + // Per the spec, the value for 'keys' is the same as 'values'. + ScopedString valString(scope, scope.engine->newIdentifier(QStringLiteral("values"))); + ScopedFunctionObject valuesFn(scope, FunctionObject::createBuiltinFunction(engine, valString, SetPrototype::method_values, 0)); + defineDefaultProperty(QStringLiteral("keys"), valuesFn); + defineDefaultProperty(QStringLiteral("values"), valuesFn); + + defineDefaultProperty(engine->symbol_iterator(), valuesFn); + + ScopedString val(scope, engine->newString(QLatin1String("Set"))); + defineReadonlyConfigurableProperty(engine->symbol_toStringTag(), val); +} + +void Heap::SetObject::init() +{ + Object::init(); + esTable = new ESTable(); +} + +void Heap::SetObject::destroy() +{ + delete esTable; + esTable = 0; +} + +void Heap::SetObject::removeUnmarkedKeys() +{ + esTable->removeUnmarkedKeys(); +} + +void Heap::SetObject::markObjects(Heap::Base *that, MarkStack *markStack) +{ + SetObject *s = static_cast<SetObject *>(that); + s->esTable->markObjects(markStack, s->isWeakSet); + Object::markObjects(that, markStack); +} + +ReturnedValue SetPrototype::method_add(const FunctionObject *b, const Value *thisObject, const Value *argv, int) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || that->d()->isWeakSet) + return scope.engine->throwTypeError(); + + that->d()->esTable->set(argv[0], Value::undefinedValue()); + return that.asReturnedValue(); +} + +ReturnedValue SetPrototype::method_clear(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || that->d()->isWeakSet) + return scope.engine->throwTypeError(); + + that->d()->esTable->clear(); + return Encode::undefined(); +} + +ReturnedValue SetPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || that->d()->isWeakSet) + return scope.engine->throwTypeError(); + + return Encode(that->d()->esTable->remove(argv[0])); +} + +ReturnedValue SetPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || that->d()->isWeakSet) + return scope.engine->throwTypeError(); + + Scoped<SetIteratorObject> ao(scope, scope.engine->newSetIteratorObject(that)); + ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; + return ao->asReturnedValue(); +} + +ReturnedValue SetPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || that->d()->isWeakSet) + return scope.engine->throwTypeError(); + + ScopedFunctionObject callbackfn(scope, argv[0]); + if (!callbackfn) + return scope.engine->throwTypeError(); + + ScopedValue thisArg(scope, Value::undefinedValue()); + if (argc > 1) + thisArg = ScopedValue(scope, argv[1]); + + Value *arguments = scope.alloc(3); + for (uint i = 0; i < that->d()->esTable->size(); ++i) { + that->d()->esTable->iterate(i, &arguments[0], &arguments[1]); // fill in key (0), value (1) + arguments[1] = arguments[0]; // but for set, we want to return the key twice; value is always undefined. + + arguments[2] = that; + callbackfn->call(thisArg, arguments, 3); + CHECK_EXCEPTION(); + } + return Encode::undefined(); +} + +ReturnedValue SetPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || that->d()->isWeakSet) + return scope.engine->throwTypeError(); + + return Encode(that->d()->esTable->has(argv[0])); +} + +ReturnedValue SetPrototype::method_get_size(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || that->d()->isWeakSet) + return scope.engine->throwTypeError(); + + return Encode(that->d()->esTable->size()); +} + +ReturnedValue SetPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<SetObject> that(scope, thisObject); + if (!that || that->d()->isWeakSet) + return scope.engine->throwTypeError(); + + Scoped<SetIteratorObject> ao(scope, scope.engine->newSetIteratorObject(that)); + ao->d()->iterationKind = IteratorKind::ValueIteratorKind; + return ao->asReturnedValue(); +} diff --git a/src/qml/jsruntime/qv4setobject_p.h b/src/qml/jsruntime/qv4setobject_p.h new file mode 100644 index 0000000000..21584e2132 --- /dev/null +++ b/src/qml/jsruntime/qv4setobject_p.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Crimson AS <info@crimson.no> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4SETOBJECT_P_H +#define QV4SETOBJECT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4objectproto_p.h" +#include "qv4functionobject_p.h" +#include "qv4string_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +class ESTable; + +namespace Heap { + +struct WeakSetCtor : FunctionObject { + void init(QV4::ExecutionContext *scope); +}; + + +struct SetCtor : WeakSetCtor { + void init(QV4::ExecutionContext *scope); +}; + +struct SetObject : Object { + static void markObjects(Heap::Base *that, MarkStack *markStack); + void init(); + void destroy(); + void removeUnmarkedKeys(); + + ESTable *esTable; + SetObject *nextWeakSet; + bool isWeakSet; +}; + +} + + +struct WeakSetCtor: FunctionObject +{ + V4_OBJECT2(WeakSetCtor, FunctionObject) + + static ReturnedValue construct(const FunctionObject *f, const Value *argv, int argc, const Value *, bool weakSet); + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); +}; + +struct SetCtor : WeakSetCtor +{ + V4_OBJECT2(SetCtor, WeakSetCtor) + + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); +}; + +struct SetObject : Object +{ + V4_OBJECT2(SetObject, Object) + V4_PROTOTYPE(setPrototype) + V4_NEEDS_DESTROY +}; + +struct WeakSetPrototype : Object +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_add(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_delete(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_has(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + + +struct SetPrototype : WeakSetPrototype +{ + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_add(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_clear(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_delete(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_entries(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_forEach(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_has(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_size(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_values(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + + +} // namespace QV4 + + +QT_END_NAMESPACE + +#endif // QV4SETOBJECT_P_H diff --git a/src/qml/jsruntime/qv4sparsearray.cpp b/src/qml/jsruntime/qv4sparsearray.cpp index 8f6aa6723c..8930c9a94d 100644 --- a/src/qml/jsruntime/qv4sparsearray.cpp +++ b/src/qml/jsruntime/qv4sparsearray.cpp @@ -89,20 +89,20 @@ const SparseArrayNode *SparseArrayNode::previousNode() const SparseArrayNode *SparseArrayNode::copy(SparseArray *d) const { - SparseArrayNode *n = d->createNode(size_left, 0, false); + SparseArrayNode *n = d->createNode(size_left, nullptr, false); n->value = value; n->setColor(color()); if (left) { n->left = left->copy(d); n->left->setParent(n); } else { - n->left = 0; + n->left = nullptr; } if (right) { n->right = right->copy(d); n->right->setParent(n); } else { - n->right = 0; + n->right = nullptr; } return n; } @@ -119,7 +119,7 @@ void SparseArray::rotateLeft(SparseArrayNode *x) SparseArrayNode *&root = header.left; SparseArrayNode *y = x->right; x->right = y->left; - if (y->left != 0) + if (y->left != nullptr) y->left->setParent(x); y->setParent(x->parent()); if (x == root) @@ -146,7 +146,7 @@ void SparseArray::rotateRight(SparseArrayNode *x) SparseArrayNode *&root = header.left; SparseArrayNode *y = x->left; x->left = y->right; - if (y->right != 0) + if (y->right != nullptr) y->right->setParent(x); y->setParent(x->parent()); if (x == root) @@ -209,7 +209,7 @@ void SparseArray::deleteNode(SparseArrayNode *z) SparseArrayNode *y = z; SparseArrayNode *x; SparseArrayNode *x_parent; - if (y->left == 0) { + if (y->left == nullptr) { x = y->right; if (y == mostLeftNode) { if (x) @@ -217,11 +217,11 @@ void SparseArray::deleteNode(SparseArrayNode *z) else mostLeftNode = y->parent(); } - } else if (y->right == 0) { + } else if (y->right == nullptr) { x = y->left; } else { y = y->right; - while (y->left != 0) + while (y->left != nullptr) y = y->left; x = y->right; } @@ -261,7 +261,7 @@ void SparseArray::deleteNode(SparseArrayNode *z) y->size_left = 0; } if (y->color() != SparseArrayNode::Red) { - while (x != root && (x == 0 || x->color() == SparseArrayNode::Black)) { + while (x != root && (x == nullptr || x->color() == SparseArrayNode::Black)) { if (x == x_parent->left) { SparseArrayNode *w = x_parent->right; if (w->color() == SparseArrayNode::Red) { @@ -270,13 +270,13 @@ void SparseArray::deleteNode(SparseArrayNode *z) rotateLeft(x_parent); w = x_parent->right; } - if ((w->left == 0 || w->left->color() == SparseArrayNode::Black) && - (w->right == 0 || w->right->color() == SparseArrayNode::Black)) { + if ((w->left == nullptr || w->left->color() == SparseArrayNode::Black) && + (w->right == nullptr || w->right->color() == SparseArrayNode::Black)) { w->setColor(SparseArrayNode::Red); x = x_parent; x_parent = x_parent->parent(); } else { - if (w->right == 0 || w->right->color() == SparseArrayNode::Black) { + if (w->right == nullptr || w->right->color() == SparseArrayNode::Black) { if (w->left) w->left->setColor(SparseArrayNode::Black); w->setColor(SparseArrayNode::Red); @@ -298,13 +298,13 @@ void SparseArray::deleteNode(SparseArrayNode *z) rotateRight(x_parent); w = x_parent->left; } - if ((w->right == 0 || w->right->color() == SparseArrayNode::Black) && - (w->left == 0 || w->left->color() == SparseArrayNode::Black)) { + if ((w->right == nullptr || w->right->color() == SparseArrayNode::Black) && + (w->left == nullptr || w->left->color() == SparseArrayNode::Black)) { w->setColor(SparseArrayNode::Red); x = x_parent; x_parent = x_parent->parent(); } else { - if (w->left == 0 || w->left->color() == SparseArrayNode::Black) { + if (w->left == nullptr || w->left->color() == SparseArrayNode::Black) { if (w->right) w->right->setColor(SparseArrayNode::Black); w->setColor(SparseArrayNode::Red); @@ -363,8 +363,8 @@ SparseArrayNode *SparseArray::createNode(uint sl, SparseArrayNode *parent, bool Q_CHECK_PTR(node); node->p = (quintptr)parent; - node->left = 0; - node->right = 0; + node->left = nullptr; + node->right = nullptr; node->size_left = sl; node->value = UINT_MAX; ++numEntries; @@ -395,21 +395,23 @@ void SparseArray::freeTree(SparseArrayNode *root, int alignment) SparseArray::SparseArray() : numEntries(0) { + freeList = Encode(-1); header.p = 0; - header.left = 0; - header.right = 0; + header.left = nullptr; + header.right = nullptr; mostLeftNode = &header; } SparseArray::SparseArray(const SparseArray &other) { header.p = 0; - header.right = 0; + header.right = nullptr; if (other.header.left) { header.left = other.header.left->copy(this); header.left->setParent(&header); recalcMostLeftNode(); } + freeList = other.freeList; } SparseArrayNode *SparseArray::insert(uint akey) diff --git a/src/qml/jsruntime/qv4sparsearray_p.h b/src/qml/jsruntime/qv4sparsearray_p.h index 2e4ac883f2..c1e50c8dcf 100644 --- a/src/qml/jsruntime/qv4sparsearray_p.h +++ b/src/qml/jsruntime/qv4sparsearray_p.h @@ -52,6 +52,7 @@ // #include "qv4global_p.h" +#include "qv4value_p.h" #include <QtCore/qlist.h> //#define Q_MAP_DEBUG @@ -109,7 +110,7 @@ struct SparseArrayNode inline SparseArrayNode *SparseArrayNode::lowerBound(uint akey) { SparseArrayNode *n = this; - SparseArrayNode *last = 0; + SparseArrayNode *last = nullptr; while (n) { if (akey <= n->size_left) { last = n; @@ -126,7 +127,7 @@ inline SparseArrayNode *SparseArrayNode::lowerBound(uint akey) inline SparseArrayNode *SparseArrayNode::upperBound(uint akey) { SparseArrayNode *n = this; - SparseArrayNode *last = 0; + SparseArrayNode *last = nullptr; while (n) { if (akey < n->size_left) { last = n; @@ -150,6 +151,8 @@ struct Q_QML_EXPORT SparseArray } SparseArray(const SparseArray &other); + + Value freeList; private: SparseArray &operator=(const SparseArray &other); @@ -221,7 +224,7 @@ inline SparseArrayNode *SparseArray::findNode(uint akey) const } } - return 0; + return nullptr; } inline uint SparseArray::pop_front() diff --git a/src/qml/jsruntime/qv4stackframe.cpp b/src/qml/jsruntime/qv4stackframe.cpp new file mode 100644 index 0000000000..a716c53aea --- /dev/null +++ b/src/qml/jsruntime/qv4stackframe.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qv4stackframe_p.h" +#include <QtCore/qstring.h> + +using namespace QV4; + +QString CppStackFrame::source() const +{ + return v4Function ? v4Function->sourceFile() : QString(); +} + +QString CppStackFrame::function() const +{ + return v4Function ? v4Function->name()->toQString() : QString(); +} + +int CppStackFrame::lineNumber() const +{ + if (!v4Function) + return -1; + + auto findLine = [](const CompiledData::CodeOffsetToLine &entry, uint offset) { + return entry.codeOffset < offset; + }; + + const QV4::CompiledData::Function *cf = v4Function->compiledFunction; + uint offset = instructionPointer; + const CompiledData::CodeOffsetToLine *lineNumbers = cf->lineNumberTable(); + uint nLineNumbers = cf->nLineNumbers; + const CompiledData::CodeOffsetToLine *line = std::lower_bound(lineNumbers, lineNumbers + nLineNumbers, offset, findLine) - 1; + return line->line; +} + +ReturnedValue CppStackFrame::thisObject() const { + return jsFrame->thisObject.asReturnedValue(); +} + diff --git a/src/qml/jsruntime/qv4stackframe_p.h b/src/qml/jsruntime/qv4stackframe_p.h new file mode 100644 index 0000000000..44cfef9173 --- /dev/null +++ b/src/qml/jsruntime/qv4stackframe_p.h @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4STACKFRAME_H +#define QV4STACKFRAME_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4context_p.h> +#include <private/qv4enginebase_p.h> +#ifndef V4_BOOTSTRAP +#include <private/qv4function_p.h> +#endif + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +struct CallData +{ + enum Offsets { + Function = 0, + Context = 1, + Accumulator = 2, + This = 3, + NewTarget = 4, + Argc = 5, + + LastOffset = Argc, + OffsetCount = LastOffset + 1 + }; + + Value function; + Value context; + Value accumulator; + Value thisObject; + Value newTarget; + Value _argc; + + int argc() const { + Q_ASSERT(_argc.isInteger()); + return _argc.int_32(); + } + + void setArgc(int argc) { + Q_ASSERT(argc >= 0); + _argc.setInt_32(argc); + } + + inline ReturnedValue argument(int i) const { + return i < argc() ? args[i].asReturnedValue() : Value::undefinedValue().asReturnedValue(); + } + + Value args[1]; + + static Q_DECL_CONSTEXPR int HeaderSize() { return offsetof(CallData, args) / sizeof(QV4::Value); } +}; + +Q_STATIC_ASSERT(std::is_standard_layout<CallData>::value); +Q_STATIC_ASSERT(offsetof(CallData, function ) == CallData::Function * sizeof(Value)); +Q_STATIC_ASSERT(offsetof(CallData, context ) == CallData::Context * sizeof(Value)); +Q_STATIC_ASSERT(offsetof(CallData, accumulator) == CallData::Accumulator * sizeof(Value)); +Q_STATIC_ASSERT(offsetof(CallData, thisObject ) == CallData::This * sizeof(Value)); +Q_STATIC_ASSERT(offsetof(CallData, newTarget ) == CallData::NewTarget * sizeof(Value)); +Q_STATIC_ASSERT(offsetof(CallData, _argc ) == CallData::Argc * sizeof(Value)); +Q_STATIC_ASSERT(offsetof(CallData, args ) == 6 * sizeof(Value)); + +struct Q_QML_EXPORT CppStackFrame { + EngineBase *engine; + Value *savedStackTop; + CppStackFrame *parent; + Function *v4Function; + CallData *jsFrame; + const Value *originalArguments; + int originalArgumentsCount; + int instructionPointer; + const char *yield; + const char *unwindHandler; + const char *unwindLabel; + int unwindLevel; + bool yieldIsIterator; + bool callerCanHandleTailCall; + bool pendingTailCall; + bool isTailCalling; + + void init(EngineBase *engine, Function *v4Function, const Value *argv, int argc, bool callerCanHandleTailCall = false) { + this->engine = engine; + + this->v4Function = v4Function; + originalArguments = argv; + originalArgumentsCount = argc; + instructionPointer = 0; + yield = nullptr; + unwindHandler = nullptr; + unwindLabel = nullptr; + unwindLevel = 0; + yieldIsIterator = false; + this->callerCanHandleTailCall = callerCanHandleTailCall; + pendingTailCall = false; + isTailCalling = false; + } + + void push() { + parent = engine->currentStackFrame; + engine->currentStackFrame = this; + savedStackTop = engine->jsStackTop; + } + + void pop() { + engine->currentStackFrame = parent; + engine->jsStackTop = savedStackTop; + } + +#ifndef V4_BOOTSTRAP + static uint requiredJSStackFrameSize(uint nRegisters) { + return CallData::HeaderSize() + nRegisters; + } + static uint requiredJSStackFrameSize(Function *v4Function) { + return CallData::HeaderSize() + v4Function->compiledFunction->nRegisters; + } + uint requiredJSStackFrameSize() const { + return requiredJSStackFrameSize(v4Function); + } + void setupJSFrame(Value *stackSpace, const Value &function, const Heap::ExecutionContext *scope, + const Value &thisObject, const Value &newTarget = Value::undefinedValue()) { + setupJSFrame(stackSpace, function, scope, thisObject, newTarget, + v4Function->nFormals, v4Function->compiledFunction->nRegisters); + } + void setupJSFrame(Value *stackSpace, const Value &function, const Heap::ExecutionContext *scope, + const Value &thisObject, const Value &newTarget, uint nFormals, uint nRegisters) + { + jsFrame = reinterpret_cast<CallData *>(stackSpace); + jsFrame->function = function; + jsFrame->context = scope->asReturnedValue(); + jsFrame->accumulator = Encode::undefined(); + jsFrame->thisObject = thisObject; + jsFrame->newTarget = newTarget; + + uint argc = uint(originalArgumentsCount); + if (argc > nFormals) + argc = nFormals; + jsFrame->setArgc(argc); + + memcpy(jsFrame->args, originalArguments, argc*sizeof(Value)); + Q_STATIC_ASSERT(Encode::undefined() == 0); + memset(jsFrame->args + argc, 0, (nRegisters - argc)*sizeof(Value)); + + if (v4Function && v4Function->compiledFunction) { + const int firstDeadZoneRegister = v4Function->compiledFunction->firstTemporalDeadZoneRegister; + const int registerDeadZoneSize = v4Function->compiledFunction->sizeOfRegisterTemporalDeadZone; + + const Value * tdzEnd = stackSpace + firstDeadZoneRegister + registerDeadZoneSize; + for (Value *v = stackSpace + firstDeadZoneRegister; v < tdzEnd; ++v) + *v = Value::emptyValue().asReturnedValue(); + } + } +#endif + + QString source() const; + QString function() const; + inline QV4::ExecutionContext *context() const { + return static_cast<ExecutionContext *>(&jsFrame->context); + } + int lineNumber() const; + + inline QV4::Heap::CallContext *callContext() const { + Heap::ExecutionContext *ctx = static_cast<ExecutionContext &>(jsFrame->context).d();\ + while (ctx->type != Heap::ExecutionContext::Type_CallContext) + ctx = ctx->outer; + return static_cast<Heap::CallContext *>(ctx); + } + ReturnedValue thisObject() const; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/jsruntime/qv4string.cpp b/src/qml/jsruntime/qv4string.cpp index c0183a46a7..68d65f2e24 100644 --- a/src/qml/jsruntime/qv4string.cpp +++ b/src/qml/jsruntime/qv4string.cpp @@ -52,23 +52,41 @@ using namespace QV4; #ifndef V4_BOOTSTRAP -DEFINE_MANAGED_VTABLE(String); +void Heap::StringOrSymbol::markObjects(Heap::Base *that, MarkStack *markStack) +{ + StringOrSymbol *s = static_cast<StringOrSymbol *>(that); + Heap::StringOrSymbol *id = s->identifier.asStringOrSymbol(); + if (id) + id->mark(markStack); +} -void String::markObjects(Heap::Base *that, MarkStack *markStack) +void Heap::String::markObjects(Heap::Base *that, MarkStack *markStack) { - String::Data *s = static_cast<String::Data *>(that); - if (s->largestSubLength) { - s->left->mark(markStack); - s->right->mark(markStack); + StringOrSymbol::markObjects(that, markStack); + String *s = static_cast<String *>(that); + if (s->subtype < StringType_Complex) + return; + + ComplexString *cs = static_cast<ComplexString *>(s); + if (cs->subtype == StringType_AddedString) { + cs->left->mark(markStack); + cs->right->mark(markStack); + } else { + Q_ASSERT(cs->subtype == StringType_SubString); + cs->left->mark(markStack); } } -bool String::isEqualTo(Managed *t, Managed *o) +DEFINE_MANAGED_VTABLE(StringOrSymbol); +DEFINE_MANAGED_VTABLE(String); + + +bool String::virtualIsEqualTo(Managed *t, Managed *o) { if (t == o) return true; - if (!o->d()->vtable()->isString) + if (!o->vtable()->isString) return false; return static_cast<String *>(t)->isEqualTo(static_cast<String *>(o)); @@ -83,37 +101,46 @@ void Heap::String::init(const QString &t) text = const_cast<QString &>(t).data_ptr(); text->ref.ref(); - identifier = 0; - stringHash = UINT_MAX; - largestSubLength = 0; - len = text->size; } -void Heap::String::init(String *l, String *r) +void Heap::ComplexString::init(String *l, String *r) { Base::init(); - subtype = String::StringType_Unknown; + subtype = String::StringType_AddedString; left = l; right = r; - stringHash = UINT_MAX; - largestSubLength = qMax(l->largestSubLength, r->largestSubLength); - len = l->len + r->len; - Q_ASSERT(largestSubLength <= len); - - if (!l->largestSubLength && l->len > largestSubLength) - largestSubLength = l->len; - if (!r->largestSubLength && r->len > largestSubLength) - largestSubLength = r->len; + len = left->length() + right->length(); + if (left->subtype >= StringType_Complex) + largestSubLength = static_cast<ComplexString *>(left)->largestSubLength; + else + largestSubLength = left->length(); + if (right->subtype >= StringType_Complex) + largestSubLength = qMax(largestSubLength, static_cast<ComplexString *>(right)->largestSubLength); + else + largestSubLength = qMax(largestSubLength, right->length()); // make sure we don't get excessive depth in our strings if (len > 256 && len >= 2*largestSubLength) simplifyString(); } -void Heap::String::destroy() { - if (!largestSubLength) { +void Heap::ComplexString::init(Heap::String *ref, int from, int len) +{ + Q_ASSERT(ref->length() >= from + len); + Base::init(); + + subtype = String::StringType_SubString; + + left = ref; + this->from = from; + this->len = len; +} + +void Heap::StringOrSymbol::destroy() +{ + if (text) { internalClass->engine->memoryManager->changeUnmanagedHeapSizeUsage(qptrdiff(-text->size) * (int)sizeof(QChar)); if (!text->ref.deref()) QStringData::deallocate(text); @@ -125,7 +152,7 @@ uint String::toUInt(bool *ok) const { *ok = true; - if (subtype() == Heap::String::StringType_Unknown) + if (subtype() >= Heap::String::StringType_Unknown) d()->createHashValue(); if (subtype() == Heap::String::StringType_ArrayIndex) return d()->stringHash; @@ -139,17 +166,17 @@ uint String::toUInt(bool *ok) const return UINT_MAX; } -void String::makeIdentifierImpl() const +void String::createPropertyKeyImpl() const { - if (d()->largestSubLength) + if (!d()->text) d()->simplifyString(); - Q_ASSERT(!d()->largestSubLength); - engine()->identifierTable->identifier(this); + Q_ASSERT(d()->text); + engine()->identifierTable->asPropertyKey(this); } void Heap::String::simplifyString() const { - Q_ASSERT(largestSubLength); + Q_ASSERT(!text); int l = length(); QString result(l, Qt::Uninitialized); @@ -157,9 +184,33 @@ void Heap::String::simplifyString() const append(this, ch); text = result.data_ptr(); text->ref.ref(); - identifier = 0; - largestSubLength = 0; + const ComplexString *cs = static_cast<const ComplexString *>(this); + identifier = PropertyKey::invalid(); + cs->left = cs->right = nullptr; + internalClass->engine->memoryManager->changeUnmanagedHeapSizeUsage(qptrdiff(text->size) * (qptrdiff)sizeof(QChar)); + subtype = StringType_Unknown; +} + +bool Heap::String::startsWithUpper() const +{ + if (subtype == StringType_AddedString) + return static_cast<const Heap::ComplexString *>(this)->left->startsWithUpper(); + + const Heap::String *str = this; + int offset = 0; + if (subtype == StringType_SubString) { + const ComplexString *cs = static_cast<const Heap::ComplexString *>(this); + if (!cs->len) + return false; + // simplification here is not ideal, but hopefully not a common case. + if (cs->left->subtype >= Heap::String::StringType_Complex) + cs->left->simplifyString(); + str = cs->left; + offset = cs->from; + } + Q_ASSERT(str->subtype < Heap::String::StringType_Complex); + return str->text->size > offset && QChar::isUpper(str->text->data()[offset]); } void Heap::String::append(const String *data, QChar *ch) @@ -172,27 +223,34 @@ void Heap::String::append(const String *data, QChar *ch) const String *item = worklist.back(); worklist.pop_back(); - if (item->largestSubLength) { - worklist.push_back(item->right); - worklist.push_back(item->left); + if (item->subtype == StringType_AddedString) { + const ComplexString *cs = static_cast<const ComplexString *>(item); + worklist.push_back(cs->right); + worklist.push_back(cs->left); + } else if (item->subtype == StringType_SubString) { + const ComplexString *cs = static_cast<const ComplexString *>(item); + memcpy(ch, cs->left->toQString().constData() + cs->from, cs->len*sizeof(QChar)); + ch += cs->len; } else { - memcpy(ch, item->text->data(), item->text->size * sizeof(QChar)); + memcpy(static_cast<void *>(ch), static_cast<const void *>(item->text->data()), item->text->size * sizeof(QChar)); ch += item->text->size; } } } -void Heap::String::createHashValue() const +void Heap::StringOrSymbol::createHashValue() const { - if (largestSubLength) - simplifyString(); - Q_ASSERT(!largestSubLength); + if (!text) { + Q_ASSERT(internalClass->vtable->isString); + static_cast<const Heap::String *>(this)->simplifyString(); + } + Q_ASSERT(text); const QChar *ch = reinterpret_cast<const QChar *>(text->data()); const QChar *end = ch + text->size; stringHash = QV4::String::calculateHashValue(ch, end, &subtype); } -uint String::getLength(const Managed *m) +qint64 String::virtualGetLength(const Managed *m) { return static_cast<const String *>(m)->d()->length(); } @@ -203,4 +261,3 @@ uint String::toArrayIndex(const QString &str) { return QV4::String::toArrayIndex(str.constData(), str.constData() + str.length()); } - diff --git a/src/qml/jsruntime/qv4string_p.h b/src/qml/jsruntime/qv4string_p.h index 2f34dd6139..fbd4f5f550 100644 --- a/src/qml/jsruntime/qv4string_p.h +++ b/src/qml/jsruntime/qv4string_p.h @@ -60,41 +60,63 @@ QT_BEGIN_NAMESPACE namespace QV4 { struct ExecutionEngine; -struct Identifier; +struct PropertyKey; namespace Heap { -struct Q_QML_PRIVATE_EXPORT String : Base { +struct Q_QML_PRIVATE_EXPORT StringOrSymbol : Base +{ enum StringType { - StringType_Unknown, + StringType_Symbol, StringType_Regular, - StringType_ArrayIndex + StringType_ArrayIndex, + StringType_Unknown, + StringType_AddedString, + StringType_SubString, + StringType_Complex = StringType_AddedString }; -#ifndef V4_BOOTSTRAP - void init(const QString &text); - void init(String *l, String *n); + mutable QStringData *text; + mutable PropertyKey identifier; + mutable uint subtype; + mutable uint stringHash; + + static void markObjects(Heap::Base *that, MarkStack *markStack); void destroy(); - void simplifyString() const; - int length() const { - Q_ASSERT((largestSubLength && - (len == left->len + right->len)) || - len == (uint)text->size); - return len; - } - std::size_t retainedTextSize() const { - return largestSubLength ? 0 : (std::size_t(text->size) * sizeof(QChar)); + + inline QString toQString() const { + if (!text) + return QString(); + QStringDataPtr ptr = { text }; + text->ref.ref(); + return QString(ptr); } void createHashValue() const; inline unsigned hashValue() const { - if (subtype == StringType_Unknown) + if (subtype >= StringType_Unknown) createHashValue(); - Q_ASSERT(!largestSubLength); + Q_ASSERT(subtype < StringType_Complex); return stringHash; } +}; + +struct Q_QML_PRIVATE_EXPORT String : StringOrSymbol { + static void markObjects(Heap::Base *that, MarkStack *markStack); + +#ifndef V4_BOOTSTRAP + const VTable *vtable() const { + return internalClass->vtable; + } + + void init(const QString &text); + void simplifyString() const; + int length() const; + std::size_t retainedTextSize() const { + return subtype >= StringType_Complex ? 0 : (std::size_t(text->size) * sizeof(QChar)); + } inline QString toQString() const { - if (largestSubLength) + if (subtype >= StringType_Complex) simplifyString(); QStringDataPtr ptr = { text }; text->ref.ref(); @@ -105,8 +127,8 @@ struct Q_QML_PRIVATE_EXPORT String : Base { return true; if (hashValue() != other->hashValue()) return false; - Q_ASSERT(!largestSubLength); - if (identifier && identifier == other->identifier) + Q_ASSERT(subtype < StringType_Complex); + if (identifier.isValid() && identifier == other->identifier) return true; if (subtype == Heap::String::StringType_ArrayIndex && other->subtype == Heap::String::StringType_ArrayIndex) return true; @@ -114,32 +136,62 @@ struct Q_QML_PRIVATE_EXPORT String : Base { return toQString() == other->toQString(); } - union { - mutable QStringData *text; - mutable String *left; - }; - union { - mutable Identifier *identifier; - mutable String *right; - }; - mutable uint subtype; - mutable uint stringHash; - mutable uint largestSubLength; - uint len; + bool startsWithUpper() const; + private: static void append(const String *data, QChar *ch); #endif }; -V4_ASSERT_IS_TRIVIAL(String) +Q_STATIC_ASSERT(std::is_trivial< String >::value); + +#ifndef V4_BOOTSTRAP +struct ComplexString : String { + void init(String *l, String *n); + void init(String *ref, int from, int len); + mutable String *left; + mutable String *right; + union { + mutable int largestSubLength; + int from; + }; + int len; +}; +Q_STATIC_ASSERT(std::is_trivial< ComplexString >::value); + +inline +int String::length() const { + return text ? text->size : static_cast<const ComplexString *>(this)->len; +} +#endif } -struct Q_QML_PRIVATE_EXPORT String : public Managed { +struct Q_QML_PRIVATE_EXPORT StringOrSymbol : public Managed { +#ifndef V4_BOOTSTRAP + V4_MANAGED(StringOrSymbol, Managed) + V4_NEEDS_DESTROY + enum { + IsStringOrSymbol = true + }; + +private: + inline void createPropertyKey() const; +public: + PropertyKey propertyKey() const { Q_ASSERT(d()->identifier.isValid()); return d()->identifier; } + PropertyKey toPropertyKey() const; + + + inline QString toQString() const { + return d()->toQString(); + } +#endif +}; + +struct Q_QML_PRIVATE_EXPORT String : public StringOrSymbol { #ifndef V4_BOOTSTRAP - V4_MANAGED(String, Managed) + V4_MANAGED(String, StringOrSymbol) Q_MANAGED_TYPE(String) V4_INTERNALCLASS(String) - V4_NEEDS_DESTROY enum { IsString = true }; @@ -154,7 +206,7 @@ struct Q_QML_PRIVATE_EXPORT String : public Managed { return d()->isEqualTo(other->d()); } - inline bool compare(const String *other) { + inline bool lessThan(const String *other) { return toQString() < other->toQString(); } @@ -165,23 +217,10 @@ struct Q_QML_PRIVATE_EXPORT String : public Managed { inline unsigned hashValue() const { return d()->hashValue(); } - uint asArrayIndex() const { - if (subtype() == Heap::String::StringType_Unknown) - d()->createHashValue(); - Q_ASSERT(!d()->largestSubLength); - if (subtype() == Heap::String::StringType_ArrayIndex) - return d()->stringHash; - return UINT_MAX; - } uint toUInt(bool *ok) const; - void makeIdentifier() const { - if (d()->identifier) - return; - makeIdentifierImpl(); - } - - void makeIdentifierImpl() const; + // slow path + Q_NEVER_INLINE void createPropertyKeyImpl() const; static uint createHashValue(const QChar *ch, int length, uint *subtype) { @@ -195,19 +234,11 @@ struct Q_QML_PRIVATE_EXPORT String : public Managed { return calculateHashValue(ch, end, subtype); } - bool startsWithUpper() const { - const String::Data *l = d(); - while (l->largestSubLength) - l = l->left; - return l->text->size && QChar::isUpper(l->text->data()[0]); - } - - Identifier *identifier() const { return d()->identifier; } + bool startsWithUpper() const { return d()->startsWithUpper(); } protected: - static void markObjects(Heap::Base *that, MarkStack *markStack); - static bool isEqualTo(Managed *that, Managed *o); - static uint getLength(const Managed *m); + static bool virtualIsEqualTo(Managed *that, Managed *o); + static qint64 virtualGetLength(const Managed *m); #endif public: @@ -215,7 +246,7 @@ public: private: static inline uint toUInt(const QChar *ch) { return ch->unicode(); } - static inline uint toUInt(const char *ch) { return *ch; } + static inline uint toUInt(const char *ch) { return static_cast<unsigned char>(*ch); } template <typename T> static inline uint toArrayIndex(const T *ch, const T *end) @@ -247,7 +278,7 @@ public: uint h = toArrayIndex(ch, end); if (h != UINT_MAX) { if (subtype) - *subtype = Heap::String::StringType_ArrayIndex; + *subtype = Heap::StringOrSymbol::StringType_ArrayIndex; return h; } @@ -257,17 +288,45 @@ public: } if (subtype) - *subtype = Heap::String::StringType_Regular; + *subtype = (toUInt(ch) == '@') ? Heap::StringOrSymbol::StringType_Symbol : Heap::StringOrSymbol::StringType_Regular; return h; } }; +#ifndef V4_BOOTSTRAP +struct ComplexString : String { + typedef QV4::Heap::ComplexString Data; + QV4::Heap::ComplexString *d_unchecked() const { return static_cast<QV4::Heap::ComplexString *>(m()); } + QV4::Heap::ComplexString *d() const { + QV4::Heap::ComplexString *dptr = d_unchecked(); + dptr->_checkIsInitialized(); + return dptr; + } +}; + +inline +void StringOrSymbol::createPropertyKey() const { + Q_ASSERT(!d()->identifier.isValid()); + Q_ASSERT(isString()); + static_cast<const String *>(this)->createPropertyKeyImpl(); +} + +inline PropertyKey StringOrSymbol::toPropertyKey() const { + if (!d()->identifier.isValid()) + createPropertyKey(); + return d()->identifier; +} + +template<> +inline const StringOrSymbol *Value::as() const { + return isManaged() && m()->internalClass->vtable->isStringOrSymbol ? static_cast<const String *>(this) : nullptr; +} + template<> inline const String *Value::as() const { - return isManaged() && m()->vtable()->isString ? static_cast<const String *>(this) : 0; + return isManaged() && m()->internalClass->vtable->isString ? static_cast<const String *>(this) : nullptr; } -#ifndef V4_BOOTSTRAP template<> inline ReturnedValue value_convert<String>(ExecutionEngine *e, const Value &v) { diff --git a/src/qml/jsruntime/qv4stringiterator.cpp b/src/qml/jsruntime/qv4stringiterator.cpp new file mode 100644 index 0000000000..62db83ff26 --- /dev/null +++ b/src/qml/jsruntime/qv4stringiterator.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Crimson AS <info@crimson.no> +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qv4iterator_p.h> +#include <private/qv4stringiterator_p.h> +#include <private/qv4symbol_p.h> + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(StringIteratorObject); + +void StringIteratorPrototype::init(ExecutionEngine *e) +{ + defineDefaultProperty(QStringLiteral("next"), method_next, 0); + + Scope scope(e); + ScopedString val(scope, e->newString(QLatin1String("String Iterator"))); + defineReadonlyConfigurableProperty(e->symbol_toStringTag(), val); +} + +ReturnedValue StringIteratorPrototype::method_next(const FunctionObject *b, const Value *that, const Value *, int) +{ + Scope scope(b); + const StringIteratorObject *thisObject = that->as<StringIteratorObject>(); + if (!thisObject) + return scope.engine->throwTypeError(QLatin1String("Not an String Iterator instance")); + + ScopedString s(scope, thisObject->d()->iteratedString); + if (!s) { + QV4::Value undefined = Value::undefinedValue(); + return IteratorPrototype::createIterResultObject(scope.engine, undefined, true); + } + + quint32 index = thisObject->d()->nextIndex; + + QString str = s->toQString(); + quint32 len = str.length(); + + if (index >= len) { + thisObject->d()->iteratedString.set(scope.engine, nullptr); + QV4::Value undefined = Value::undefinedValue(); + return IteratorPrototype::createIterResultObject(scope.engine, undefined, true); + } + + QChar ch = str.at(index); + int num = 1; + if (ch.unicode() >= 0xd800 && ch.unicode() <= 0xdbff && index + 1 != len) { + ch = str.at(index + 1); + if (ch.unicode() >= 0xdc00 && ch.unicode() <= 0xdfff) + num = 2; + } + + thisObject->d()->nextIndex += num; + + ScopedString resultString(scope, scope.engine->newString(s->toQString().mid(index, num))); + return IteratorPrototype::createIterResultObject(scope.engine, resultString, false); +} + diff --git a/src/qml/jsruntime/qv4stringiterator_p.h b/src/qml/jsruntime/qv4stringiterator_p.h new file mode 100644 index 0000000000..672ccc9963 --- /dev/null +++ b/src/qml/jsruntime/qv4stringiterator_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4STRINGITERATOR_P_H +#define QV4STRINGITERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4object_p.h" +#include "qv4string_p.h" + +QT_BEGIN_NAMESPACE + + +namespace QV4 { + +namespace Heap { + +#define StringIteratorObjectMembers(class, Member) \ + Member(class, Pointer, String *, iteratedString) \ + Member(class, NoMark, quint32, nextIndex) + +DECLARE_HEAP_OBJECT(StringIteratorObject, Object) { + DECLARE_MARKOBJECTS(StringIteratorObject); + void init(String *str, QV4::ExecutionEngine *engine) + { + Object::init(); + this->iteratedString.set(engine, str); + this->nextIndex = 0; + } +}; + +} + +struct StringIteratorPrototype : Object +{ + V4_PROTOTYPE(iteratorPrototype) + void init(ExecutionEngine *engine); + + static ReturnedValue method_next(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); +}; + +struct StringIteratorObject : Object +{ + V4_OBJECT2(StringIteratorObject, Object) + Q_MANAGED_TYPE(StringIteratorObject) + V4_PROTOTYPE(stringIteratorPrototype) + + void init(ExecutionEngine *engine); +}; + + +} + +QT_END_NAMESPACE + +#endif // QV4ARRAYITERATOR_P_H + diff --git a/src/qml/jsruntime/qv4stringobject.cpp b/src/qml/jsruntime/qv4stringobject.cpp index 81f5c3566c..8186153ba4 100644 --- a/src/qml/jsruntime/qv4stringobject.cpp +++ b/src/qml/jsruntime/qv4stringobject.cpp @@ -44,18 +44,14 @@ #include "qv4objectproto_p.h" #include <private/qv4mm_p.h> #include "qv4scopedvalue_p.h" +#include "qv4symbol_p.h" #include "qv4alloca_p.h" +#include "qv4jscall_p.h" +#include "qv4stringiterator_p.h" #include <QtCore/QDateTime> #include <QtCore/QDebug> #include <QtCore/QStringList> -#include <private/qqmljsengine_p.h> -#include <private/qqmljslexer_p.h> -#include <private/qqmljsparser_p.h> -#include <private/qqmljsast_p.h> -#include <qv4jsir_p.h> -#include <qv4codegen_p.h> - #include <cassert> #ifndef Q_OS_WIN @@ -78,71 +74,93 @@ void Heap::StringObject::init() Object::init(); Q_ASSERT(vtable() == QV4::StringObject::staticVTable()); string.set(internalClass->engine, internalClass->engine->id_empty()->d()); - setProperty(internalClass->engine, LengthPropertyIndex, Primitive::fromInt32(0)); + setProperty(internalClass->engine, LengthPropertyIndex, Value::fromInt32(0)); } void Heap::StringObject::init(const QV4::String *str) { Object::init(); string.set(internalClass->engine, str->d()); - setProperty(internalClass->engine, LengthPropertyIndex, Primitive::fromInt32(length())); + setProperty(internalClass->engine, LengthPropertyIndex, Value::fromInt32(length())); } Heap::String *Heap::StringObject::getIndex(uint index) const { QString str = string->toQString(); if (index >= (uint)str.length()) - return 0; + return nullptr; return internalClass->engine->newString(str.mid(index, 1)); } uint Heap::StringObject::length() const { - return string->len; + return string->length(); } -bool StringObject::deleteIndexedProperty(Managed *m, uint index) +bool StringObject::virtualDeleteProperty(Managed *m, PropertyKey id) { - ExecutionEngine *v4 = static_cast<StringObject *>(m)->engine(); - Scope scope(v4); - Scoped<StringObject> o(scope, m->as<StringObject>()); - Q_ASSERT(!!o); - - if (index < static_cast<uint>(o->d()->string->toQString().length())) { - if (v4->current->strictMode) - v4->throwTypeError(); - return false; + Q_ASSERT(m->as<StringObject>()); + if (id.isArrayIndex()) { + StringObject *o = static_cast<StringObject *>(m); + uint index = id.asArrayIndex(); + if (index < static_cast<uint>(o->d()->string->toQString().length())) + return false; } - return true; + return Object::virtualDeleteProperty(m, id); } -void StringObject::advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attrs) +struct StringObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator +{ + ~StringObjectOwnPropertyKeyIterator() override = default; + PropertyKey next(const QV4::Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; + +}; + +PropertyKey StringObjectOwnPropertyKeyIterator::next(const QV4::Object *o, Property *pd, PropertyAttributes *attrs) { - name->setM(0); - StringObject *s = static_cast<StringObject *>(m); + const StringObject *s = static_cast<const StringObject *>(o); uint slen = s->d()->string->toQString().length(); - if (it->arrayIndex <= slen) { - while (it->arrayIndex < slen) { - *index = it->arrayIndex; - ++it->arrayIndex; - PropertyAttributes a; - Property pd; - s->getOwnProperty(*index, &a, &pd); - if (!(it->flags & ObjectIterator::EnumerableOnly) || a.isEnumerable()) { - *attrs = a; - p->copy(&pd, a); - return; - } - } + if (arrayIndex < slen) { + uint index = arrayIndex; + ++arrayIndex; + if (attrs) + *attrs = Attr_NotConfigurable|Attr_NotWritable; + if (pd) + pd->value = s->getIndex(index); + return PropertyKey::fromArrayIndex(index); + } else if (arrayIndex == slen) { if (s->arrayData()) { - it->arrayNode = s->sparseBegin(); + arrayNode = s->sparseBegin(); // iterate until we're past the end of the string - while (it->arrayNode && it->arrayNode->key() < slen) - it->arrayNode = it->arrayNode->nextNode(); + while (arrayNode && arrayNode->key() < slen) + arrayNode = arrayNode->nextNode(); } } - return Object::advanceIterator(m, it, name, index, p, attrs); + return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); +} + +OwnPropertyKeyIterator *StringObject::virtualOwnPropertyKeys(const Object *m, Value *target) +{ + *target = *m; + return new StringObjectOwnPropertyKeyIterator; +} + +PropertyAttributes StringObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) +{ + PropertyAttributes attributes = Object::virtualGetOwnProperty(m, id, p); + if (attributes != Attr_Invalid) + return attributes; + + const StringObject *s = static_cast<const StringObject *>(m); + uint slen = s->d()->string->toQString().length(); + uint index = id.asArrayIndex(); + if (index < slen) { + if (p) + p->value = s->getIndex(index); + return Attr_NotConfigurable|Attr_NotWritable; + } + return Object::virtualGetOwnProperty(m, id, p); } DEFINE_OBJECT_VTABLE(StringCtor); @@ -152,24 +170,114 @@ void Heap::StringCtor::init(QV4::ExecutionContext *scope) Heap::FunctionObject::init(scope, QStringLiteral("String")); } -void StringCtor::construct(const Managed *m, Scope &scope, CallData *callData) +ReturnedValue StringCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - ExecutionEngine *v4 = static_cast<const Object *>(m)->engine(); + ExecutionEngine *v4 = static_cast<const Object *>(f)->engine(); + Scope scope(v4); ScopedString value(scope); - if (callData->argc) - value = callData->args[0].toString(v4); + if (argc) + value = argv[0].toString(v4); else value = v4->newString(); - scope.result = Encode(v4->newStringObject(value)); + CHECK_EXCEPTION(); + ReturnedValue o = Encode(v4->newStringObject(value)); + + if (!newTarget) + return o; + ScopedObject obj(scope, o); + obj->setProtoFromNewTarget(newTarget); + return obj->asReturnedValue(); } -void StringCtor::call(const Managed *, Scope &scope, CallData *callData) +ReturnedValue StringCtor::virtualCall(const FunctionObject *m, const Value *, const Value *argv, int argc) { - ExecutionEngine *v4 = scope.engine; - if (callData->argc) - scope.result = callData->args[0].toString(v4); - else - scope.result = v4->newString(); + ExecutionEngine *v4 = m->engine(); + if (!argc) + return v4->newString()->asReturnedValue(); + if (argv[0].isSymbol()) + return v4->newString(argv[0].symbolValue()->descriptiveString())->asReturnedValue(); + return argv[0].toString(v4)->asReturnedValue(); +} + +ReturnedValue StringCtor::method_fromCharCode(const FunctionObject *b, const Value *, const Value *argv, int argc) +{ + QString str(argc, Qt::Uninitialized); + QChar *ch = str.data(); + for (int i = 0, ei = argc; i < ei; ++i) { + *ch = QChar(argv[i].toUInt16()); + ++ch; + } + *ch = 0; + return Encode(b->engine()->newString(str)); +} + + + +ReturnedValue StringCtor::method_fromCodePoint(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + ExecutionEngine *e = f->engine(); + QString result(argc*2, Qt::Uninitialized); // assume worst case + QChar *ch = result.data(); + for (int i = 0; i < argc; ++i) { + double num = argv[i].toNumber(); + if (e->hasException) + return Encode::undefined(); + int cp = static_cast<int>(num); + if (cp != num || cp < 0 || cp > 0x10ffff) + return e->throwRangeError(QStringLiteral("String.fromCodePoint: argument out of range.")); + if (cp > 0xffff) { + *ch = QChar::highSurrogate(cp); + ++ch; + *ch = QChar::lowSurrogate(cp); + } else { + *ch = cp; + } + ++ch; + } + *ch = 0; + result.truncate(ch - result.constData()); + return e->newString(result)->asReturnedValue(); +} + +ReturnedValue StringCtor::method_raw(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + if (!argc) + return scope.engine->throwTypeError(); + + ScopedObject cooked(scope, argv[0].toObject(scope.engine)); + if (!cooked) + return scope.engine->throwTypeError(); + ScopedString rawString(scope, scope.engine->newIdentifier(QStringLiteral("raw"))); + ScopedValue rawValue(scope, cooked->get(rawString)); + ScopedObject raw(scope, rawValue->toObject(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + + ++argv; + --argc; + + QString result; + uint literalSegments = raw->getLength(); + if (!literalSegments) + return scope.engine->id_empty()->asReturnedValue(); + + uint nextIndex = 0; + ScopedValue val(scope); + while (1) { + val = raw->get(nextIndex); + result += val->toQString(); + if (scope.engine->hasException) + return Encode::undefined(); + if (nextIndex + 1 == literalSegments) + return scope.engine->newString(result)->asReturnedValue(); + + if (nextIndex < static_cast<uint>(argc)) + result += argv[nextIndex].toQString(); + if (scope.engine->hasException) + return Encode::undefined(); + ++nextIndex; + } } void StringPrototype::init(ExecutionEngine *engine, Object *ctor) @@ -177,15 +285,24 @@ void StringPrototype::init(ExecutionEngine *engine, Object *ctor) Scope scope(engine); ScopedObject o(scope); + // need to set this once again, as these were not fully defined when creating the string proto + Heap::InternalClass *ic = scope.engine->classes[ExecutionEngine::Class_StringObject]->changePrototype(scope.engine->objectPrototype()->d()); + d()->internalClass.set(scope.engine, ic); + d()->string.set(scope.engine, scope.engine->id_empty()->d()); + setProperty(scope.engine, Heap::StringObject::LengthPropertyIndex, Value::fromInt32(0)); + ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(1)); - ctor->defineDefaultProperty(QStringLiteral("fromCharCode"), method_fromCharCode, 1); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); + ctor->defineDefaultProperty(QStringLiteral("fromCharCode"), StringCtor::method_fromCharCode, 1); + ctor->defineDefaultProperty(QStringLiteral("fromCodePoint"), StringCtor::method_fromCodePoint, 1); + ctor->defineDefaultProperty(QStringLiteral("raw"), StringCtor::method_raw, 1); defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); defineDefaultProperty(engine->id_toString(), method_toString); defineDefaultProperty(engine->id_valueOf(), method_toString); // valueOf and toString are identical defineDefaultProperty(QStringLiteral("charAt"), method_charAt, 1); defineDefaultProperty(QStringLiteral("charCodeAt"), method_charCodeAt, 1); + defineDefaultProperty(QStringLiteral("codePointAt"), method_codePointAt, 1); defineDefaultProperty(QStringLiteral("concat"), method_concat, 1); defineDefaultProperty(QStringLiteral("endsWith"), method_endsWith, 1); defineDefaultProperty(QStringLiteral("indexOf"), method_indexOf, 1); @@ -193,6 +310,9 @@ void StringPrototype::init(ExecutionEngine *engine, Object *ctor) defineDefaultProperty(QStringLiteral("lastIndexOf"), method_lastIndexOf, 1); defineDefaultProperty(QStringLiteral("localeCompare"), method_localeCompare, 1); defineDefaultProperty(QStringLiteral("match"), method_match, 1); + defineDefaultProperty(QStringLiteral("normalize"), method_normalize, 0); + defineDefaultProperty(QStringLiteral("padEnd"), method_padEnd, 1); + defineDefaultProperty(QStringLiteral("padStart"), method_padStart, 1); defineDefaultProperty(QStringLiteral("repeat"), method_repeat, 1); defineDefaultProperty(QStringLiteral("replace"), method_replace, 2); defineDefaultProperty(QStringLiteral("search"), method_search, 1); @@ -206,142 +326,187 @@ void StringPrototype::init(ExecutionEngine *engine, Object *ctor) defineDefaultProperty(QStringLiteral("toUpperCase"), method_toUpperCase); defineDefaultProperty(QStringLiteral("toLocaleUpperCase"), method_toLocaleUpperCase); defineDefaultProperty(QStringLiteral("trim"), method_trim); + defineDefaultProperty(engine->symbol_iterator(), method_iterator); } -static QString getThisString(Scope &scope, CallData *callData) +static Heap::String *thisAsString(ExecutionEngine *v4, const QV4::Value *thisObject) { - ScopedValue t(scope, callData->thisObject); - if (String *s = t->stringValue()) + if (String *s = thisObject->stringValue()) + return s->d(); + if (const StringObject *thisString = thisObject->as<StringObject>()) + return thisString->d()->string; + return thisObject->toString(v4); +} + +static QString getThisString(ExecutionEngine *v4, const QV4::Value *thisObject) +{ + if (String *s = thisObject->stringValue()) return s->toQString(); - if (StringObject *thisString = t->as<StringObject>()) + if (const StringObject *thisString = thisObject->as<StringObject>()) return thisString->d()->string->toQString(); - if (t->isUndefined() || t->isNull()) { - scope.engine->throwTypeError(); + if (thisObject->isUndefined() || thisObject->isNull()) { + v4->throwTypeError(); return QString(); } - return t->toQString(); + return thisObject->toQString(); } -void StringPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - if (callData->thisObject.isString()) - RETURN_RESULT(callData->thisObject); + if (thisObject->isString()) + return thisObject->asReturnedValue(); - StringObject *o = callData->thisObject.as<StringObject>(); + ExecutionEngine *v4 = b->engine(); + const StringObject *o = thisObject->as<StringObject>(); if (!o) - THROW_TYPE_ERROR(); - scope.result = o->d()->string; + return v4->throwTypeError(); + return o->d()->string->asReturnedValue(); } -void StringPrototype::method_charAt(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_charAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - const QString str = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString str = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); int pos = 0; - if (callData->argc > 0) - pos = (int) callData->args[0].toInteger(); + if (argc > 0) + pos = (int) argv[0].toInteger(); QString result; if (pos >= 0 && pos < str.length()) result += str.at(pos); - scope.result = scope.engine->newString(result); + return Encode(v4->newString(result)); } -void StringPrototype::method_charCodeAt(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_charCodeAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - const QString str = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString str = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); int pos = 0; - if (callData->argc > 0) - pos = (int) callData->args[0].toInteger(); + if (argc > 0) + pos = (int) argv[0].toInteger(); if (pos >= 0 && pos < str.length()) RETURN_RESULT(Encode(str.at(pos).unicode())); - scope.result = Encode(qt_qnan()); + return Encode(qt_qnan()); } -void StringPrototype::method_concat(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_codePointAt(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = f->engine(); + QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); + + int index = argc ? argv[0].toInteger() : 0; + if (v4->hasException) + return QV4::Encode::undefined(); + + if (index < 0 || index >= value.size()) + return Encode::undefined(); + + uint first = value.at(index).unicode(); + if (QChar::isHighSurrogate(first) && index + 1 < value.size()) { + uint second = value.at(index + 1).unicode(); + if (QChar::isLowSurrogate(second)) + return Encode(QChar::surrogateToUcs4(first, second)); + } + return Encode(first); +} + +ReturnedValue StringPrototype::method_concat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *v4 = b->engine(); + QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); + Scope scope(v4); ScopedString s(scope); - for (int i = 0; i < callData->argc; ++i) { - s = callData->args[i].toString(scope.engine); - CHECK_EXCEPTION(); + for (int i = 0; i < argc; ++i) { + s = argv[i].toString(scope.engine); + if (v4->hasException) + return QV4::Encode::undefined(); Q_ASSERT(s->isString()); value += s->toQString(); } - scope.result = scope.engine->newString(value); + return Encode(v4->newString(value)); } -void StringPrototype::method_endsWith(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_endsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - QString searchString; - if (callData->argc) { - if (callData->args[0].as<RegExpObject>()) - THROW_TYPE_ERROR(); - searchString = callData->args[0].toQString(); - } + if (argc && argv[0].as<RegExpObject>()) + return v4->throwTypeError(); + QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); + if (v4->hasException) + return Encode::undefined(); int pos = value.length(); - if (callData->argc > 1) - pos = (int) callData->args[1].toInteger(); + if (argc > 1) + pos = (int) argv[1].toInteger(); if (pos == value.length()) RETURN_RESULT(Encode(value.endsWith(searchString))); QStringRef stringToSearch = value.leftRef(pos); - scope.result = Encode(stringToSearch.endsWith(searchString)); + return Encode(stringToSearch.endsWith(searchString)); } -void StringPrototype::method_indexOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - QString searchString; - if (callData->argc) - searchString = callData->args[0].toQString(); + QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); + if (v4->hasException) + return Encode::undefined(); int pos = 0; - if (callData->argc > 1) - pos = (int) callData->args[1].toInteger(); + if (argc > 1) + pos = (int) argv[1].toInteger(); int index = -1; if (! value.isEmpty()) index = value.indexOf(searchString, qMin(qMax(pos, 0), value.length())); - scope.result = Encode(index); + return Encode(index); } -void StringPrototype::method_includes(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - QString searchString; - if (callData->argc) { - if (callData->args[0].as<RegExpObject>()) - THROW_TYPE_ERROR(); - searchString = callData->args[0].toQString(); - } + if (argc && argv[0].as<RegExpObject>()) + return v4->throwTypeError(); + QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); + if (v4->hasException) + return Encode::undefined(); int pos = 0; - if (callData->argc > 1) { - ScopedValue posArg(scope, callData->argument(1)); - pos = (int) posArg->toInteger(); - if (!posArg->isInteger() && posArg->isNumber() && qIsInf(posArg->toNumber())) + if (argc > 1) { + const Value &posArg = argv[1]; + pos = (int) posArg.toInteger(); + if (!posArg.isInteger() && posArg.isNumber() && qIsInf(posArg.toNumber())) pos = value.length(); } @@ -349,20 +514,21 @@ void StringPrototype::method_includes(const BuiltinFunction *, Scope &scope, Cal RETURN_RESULT(Encode(value.contains(searchString))); QStringRef stringToSearch = value.midRef(pos); - scope.result = Encode(stringToSearch.contains(searchString)); + return Encode(stringToSearch.contains(searchString)); } -void StringPrototype::method_lastIndexOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - const QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - QString searchString; - if (callData->argc) - searchString = callData->args[0].toQString(); + QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); + if (v4->hasException) + return Encode::undefined(); - ScopedValue posArg(scope, callData->argument(1)); - double position = RuntimeHelpers::toNumber(posArg); + double position = argc > 1 ? RuntimeHelpers::toNumber(argv[1]) : +qInf(); if (std::isnan(position)) position = +qInf(); else @@ -374,97 +540,181 @@ void StringPrototype::method_lastIndexOf(const BuiltinFunction *, Scope &scope, if (searchString.isNull() && pos == 0) RETURN_RESULT(Encode(-1)); int index = value.lastIndexOf(searchString, pos); - scope.result = Encode(index); + return Encode(index); } -void StringPrototype::method_localeCompare(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_localeCompare(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - const QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - ScopedValue v(scope, callData->argument(0)); - const QString that = v->toQString(); - scope.result = Encode(QString::localeAwareCompare(value, that)); + const QString that = (argc ? argv[0] : Value::undefinedValue()).toQString(); + return Encode(QString::localeAwareCompare(value, that)); } -void StringPrototype::method_match(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_match(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - if (callData->thisObject.isUndefined() || callData->thisObject.isNull()) - THROW_TYPE_ERROR(); + ExecutionEngine *v4 = b->engine(); + if (thisObject->isNullOrUndefined()) + return v4->throwTypeError(); + + Scope scope(v4); + if (argc && !argv[0].isNullOrUndefined()) { + ScopedObject r(scope, argv[0].toObject(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + ScopedValue f(scope, r->get(scope.engine->symbol_match())); + if (!f->isNullOrUndefined()) { + ScopedFunctionObject fo(scope, f); + if (!fo) + return scope.engine->throwTypeError(); + return fo->call(r, thisObject, 1); + } + } - ScopedString s(scope, callData->thisObject.toString(scope.engine)); + ScopedString s(scope, thisObject->toString(v4)); + if (v4->hasException) + return Encode::undefined(); - ScopedValue regexp(scope, callData->argument(0)); - Scoped<RegExpObject> rx(scope, regexp); - if (!rx) { - ScopedCallData callData(scope, 1); - callData->args[0] = regexp; - scope.engine->regExpCtor()->construct(scope, callData); - rx = scope.result.asReturnedValue(); + Scoped<RegExpObject> that(scope, argc ? argv[0] : Value::undefinedValue()); + if (!that) { + // convert args[0] to a regexp + that = RegExpCtor::virtualCallAsConstructor(b, argv, argc, b); + if (v4->hasException) + return Encode::undefined(); } + Q_ASSERT(!!that); - if (!rx) - // ### CHECK - THROW_TYPE_ERROR(); + ScopedFunctionObject match(scope, that->get(scope.engine->symbol_match())); + if (!match) + return scope.engine->throwTypeError(); + return match->call(that, s, 1); +} - bool global = rx->global(); +ReturnedValue StringPrototype::method_normalize(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *v4 = f->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return Encode::undefined(); + + QString::NormalizationForm form = QString::NormalizationForm_C; + if (argc >= 1 && !argv[0].isUndefined()) { + QString f = argv[0].toQString(); + if (v4->hasException) + return Encode::undefined(); + if (f == QLatin1String("NFC")) + form = QString::NormalizationForm_C; + else if (f == QLatin1String("NFD")) + form = QString::NormalizationForm_D; + else if (f == QLatin1String("NFKC")) + form = QString::NormalizationForm_KC; + else if (f == QLatin1String("NFKD")) + form = QString::NormalizationForm_KD; + else + return v4->throwRangeError(QLatin1String("String.prototype.normalize: Invalid normalization form.")); + } + QString normalized = value.normalized(form); + return v4->newString(normalized)->asReturnedValue(); +} - // ### use the standard builtin function, not the one that might be redefined in the proto - ScopedString execString(scope, scope.engine->newString(QStringLiteral("exec"))); - ScopedFunctionObject exec(scope, scope.engine->regExpPrototype()->get(execString)); +ReturnedValue StringPrototype::method_padEnd(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *v4 = f->engine(); + if (thisObject->isNullOrUndefined()) + return v4->throwTypeError(); - ScopedCallData cData(scope, 1); - cData->thisObject = rx; - cData->args[0] = s; - if (!global) { - exec->call(scope, cData); - return; + Scope scope(v4); + ScopedString s(scope, thisAsString(v4, thisObject)); + if (v4->hasException) + return Encode::undefined(); + if (!argc) + return s->asReturnedValue(); + + int maxLen = argv[0].toInteger(); + if (maxLen <= s->d()->length()) + return s->asReturnedValue(); + QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : QString::fromLatin1(" "); + if (v4->hasException) + return Encode::undefined(); + + if (fillString.isEmpty()) + return s->asReturnedValue(); + + QString padded = s->toQString(); + int oldLength = padded.length(); + int toFill = maxLen - oldLength; + padded.resize(maxLen); + QChar *ch = padded.data() + oldLength; + while (toFill) { + int copy = qMin(fillString.length(), toFill); + memcpy(ch, fillString.constData(), copy*sizeof(QChar)); + toFill -= copy; + ch += copy; } + *ch = 0; + + return v4->newString(padded)->asReturnedValue(); +} - ScopedString lastIndex(scope, scope.engine->newString(QStringLiteral("lastIndex"))); - rx->put(lastIndex, ScopedValue(scope, Primitive::fromInt32(0))); - ScopedArrayObject a(scope, scope.engine->newArrayObject()); +ReturnedValue StringPrototype::method_padStart(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + ExecutionEngine *v4 = f->engine(); + if (thisObject->isNullOrUndefined()) + return v4->throwTypeError(); - double previousLastIndex = 0; - uint n = 0; - ScopedValue matchStr(scope); - ScopedValue index(scope); - while (1) { - exec->call(scope, cData); - if (scope.result.isNull()) - break; - assert(scope.result.isObject()); - index = rx->get(lastIndex, 0); - double thisIndex = index->toInteger(); - if (previousLastIndex == thisIndex) { - previousLastIndex = thisIndex + 1; - rx->put(lastIndex, ScopedValue(scope, Primitive::fromDouble(previousLastIndex))); - } else { - previousLastIndex = thisIndex; - } - matchStr = scope.result.objectValue()->getIndexed(0); - a->arraySet(n, matchStr); - ++n; + Scope scope(v4); + ScopedString s(scope, thisAsString(v4, thisObject)); + if (v4->hasException) + return Encode::undefined(); + if (!argc) + return s->asReturnedValue(); + + int maxLen = argv[0].toInteger(); + if (maxLen <= s->d()->length()) + return s->asReturnedValue(); + QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : QString::fromLatin1(" "); + if (v4->hasException) + return Encode::undefined(); + + if (fillString.isEmpty()) + return s->asReturnedValue(); + + QString original = s->toQString(); + int oldLength = original.length(); + int toFill = maxLen - oldLength; + QString padded; + padded.resize(maxLen); + QChar *ch = padded.data(); + while (toFill) { + int copy = qMin(fillString.length(), toFill); + memcpy(ch, fillString.constData(), copy*sizeof(QChar)); + toFill -= copy; + ch += copy; } - if (!n) - scope.result = Encode::null(); - else - scope.result = a; + memcpy(ch, original.constData(), oldLength*sizeof(QChar)); + ch += oldLength; + *ch = 0; + + return v4->newString(padded)->asReturnedValue(); } -void StringPrototype::method_repeat(const BuiltinFunction *, Scope &scope, CallData *callData) + +ReturnedValue StringPrototype::method_repeat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - double repeats = callData->args[0].toInteger(); + double repeats = (argc ? argv[0] : Value::undefinedValue()).toInteger(); - if (repeats < 0 || qIsInf(repeats)) { - scope.result = scope.engine->throwRangeError(QLatin1String("Invalid count value")); - return; - } + if (repeats < 0 || qIsInf(repeats)) + return v4->throwRangeError(QLatin1String("Invalid count value")); - scope.result = scope.engine->newString(value.repeated(int(repeats))); + return Encode(v4->newString(value.repeated(int(repeats)))); } static void appendReplacementString(QString *result, const QString &input, const QString& replaceValue, uint* matchOffsets, int captureCount) @@ -472,54 +722,65 @@ static void appendReplacementString(QString *result, const QString &input, const result->reserve(result->length() + replaceValue.length()); for (int i = 0; i < replaceValue.length(); ++i) { if (replaceValue.at(i) == QLatin1Char('$') && i < replaceValue.length() - 1) { - ushort ch = replaceValue.at(++i).unicode(); + ushort ch = replaceValue.at(i + 1).unicode(); uint substStart = JSC::Yarr::offsetNoMatch; uint substEnd = JSC::Yarr::offsetNoMatch; + int skip = 0; if (ch == '$') { *result += QChar(ch); + ++i; continue; } else if (ch == '&') { substStart = matchOffsets[0]; substEnd = matchOffsets[1]; + skip = 1; } else if (ch == '`') { substStart = 0; substEnd = matchOffsets[0]; + skip = 1; } else if (ch == '\'') { substStart = matchOffsets[1]; substEnd = input.length(); - } else if (ch >= '1' && ch <= '9') { + skip = 1; + } else if (ch >= '0' && ch <= '9') { uint capture = ch - '0'; - Q_ASSERT(capture > 0); - if (capture < static_cast<uint>(captureCount)) { + skip = 1; + if (i < replaceValue.length() - 2) { + ch = replaceValue.at(i + 2).unicode(); + if (ch >= '0' && ch <= '9') { + uint c = capture*10 + ch - '0'; + if (c < static_cast<uint>(captureCount)) { + capture = c; + skip = 2; + } + } + } + if (capture > 0 && capture < static_cast<uint>(captureCount)) { substStart = matchOffsets[capture * 2]; substEnd = matchOffsets[capture * 2 + 1]; - } - } else if (ch == '0' && i < replaceValue.length() - 1) { - int capture = (ch - '0') * 10; - ch = replaceValue.at(++i).unicode(); - if (ch >= '0' && ch <= '9') { - capture += ch - '0'; - if (capture > 0 && capture < captureCount) { - substStart = matchOffsets[capture * 2]; - substEnd = matchOffsets[capture * 2 + 1]; - } + } else { + skip = 0; } } + i += skip; if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch) *result += input.midRef(substStart, substEnd - substStart); + else { + *result += replaceValue.at(i); + } } else { *result += replaceValue.at(i); } } } -void StringPrototype::method_replace(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_replace(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { QString string; - if (StringObject *thisString = callData->thisObject.as<StringObject>()) + if (const StringObject *thisString = thisObject->as<StringObject>()) string = thisString->d()->string->toQString(); else - string = callData->thisObject.toQString(); + string = thisObject->toQString(); int numCaptures = 0; int numStringMatches = 0; @@ -528,7 +789,8 @@ void StringPrototype::method_replace(const BuiltinFunction *, Scope &scope, Call uint _matchOffsets[64]; uint *matchOffsets = _matchOffsets; - ScopedValue searchValue(scope, callData->argument(0)); + Scope scope(b); + ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue()); Scoped<RegExpObject> regExp(scope, searchValue); if (regExp) { uint offset = 0; @@ -551,12 +813,15 @@ void StringPrototype::method_replace(const BuiltinFunction *, Scope &scope, Call break; } nMatchOffsets += re->captureCount() * 2; - if (!regExp->d()->global) + if (!regExp->global()) break; offset = qMax(offset + 1, matchOffsets[oldSize + 1]); } - if (regExp->global()) + if (regExp->global()) { regExp->setLastIndex(0); + if (scope.hasException()) + return Encode::undefined(); + } numStringMatches = nMatchOffsets / (regExp->value()->captureCount() * 2); numCaptures = regExp->value()->captureCount(); } else { @@ -571,33 +836,34 @@ void StringPrototype::method_replace(const BuiltinFunction *, Scope &scope, Call } QString result; - ScopedValue replaceValue(scope, callData->argument(1)); + ScopedValue replacement(scope); + ScopedValue replaceValue(scope, argc > 1 ? argv[1] : Value::undefinedValue()); ScopedFunctionObject searchCallback(scope, replaceValue); if (!!searchCallback) { result.reserve(string.length() + 10*numStringMatches); - ScopedCallData callData(scope, numCaptures + 2); - callData->thisObject = Primitive::undefinedValue(); - int lastEnd = 0; ScopedValue entry(scope); + Value *arguments = scope.alloc(numCaptures + 2); + int lastEnd = 0; for (int i = 0; i < numStringMatches; ++i) { for (int k = 0; k < numCaptures; ++k) { int idx = (i * numCaptures + k) * 2; uint start = matchOffsets[idx]; uint end = matchOffsets[idx + 1]; - entry = Primitive::undefinedValue(); + entry = Value::undefinedValue(); if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch) entry = scope.engine->newString(string.mid(start, end - start)); - callData->args[k] = entry; + arguments[k] = entry; } uint matchStart = matchOffsets[i * numCaptures * 2]; Q_ASSERT(matchStart >= static_cast<uint>(lastEnd)); uint matchEnd = matchOffsets[i * numCaptures * 2 + 1]; - callData->args[numCaptures] = Primitive::fromUInt32(matchStart); - callData->args[numCaptures + 1] = scope.engine->newString(string); + arguments[numCaptures] = Value::fromUInt32(matchStart); + arguments[numCaptures + 1] = scope.engine->newString(string); - searchCallback->call(scope, callData); + Value that = Value::undefinedValue(); + replacement = searchCallback->call(&that, arguments, numCaptures + 2); result += string.midRef(lastEnd, matchStart - lastEnd); - result += scope.result.toQString(); + result += replacement->toQString(); lastEnd = matchEnd; } result += string.midRef(lastEnd); @@ -623,44 +889,47 @@ void StringPrototype::method_replace(const BuiltinFunction *, Scope &scope, Call if (matchOffsets != _matchOffsets) free(matchOffsets); - scope.result = scope.engine->newString(result); + return Encode(scope.engine->newString(result)); } -void StringPrototype::method_search(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_search(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString string = getThisString(scope, callData); - scope.result = callData->argument(0); - CHECK_EXCEPTION(); + Scope scope(b); + QString string = getThisString(scope.engine, thisObject); + if (scope.engine->hasException) + return QV4::Encode::undefined(); - Scoped<RegExpObject> regExp(scope, scope.result.as<RegExpObject>()); + Scoped<RegExpObject> regExp(scope, argc ? argv[0] : Value::undefinedValue()); if (!regExp) { - ScopedCallData callData(scope, 1); - callData->args[0] = scope.result; - scope.engine->regExpCtor()->construct(scope, callData); - CHECK_EXCEPTION(); + regExp = scope.engine->regExpCtor()->callAsConstructor(argv, 1); + if (scope.engine->hasException) + return QV4::Encode::undefined(); - regExp = scope.result.as<RegExpObject>(); Q_ASSERT(regExp); } Scoped<RegExp> re(scope, regExp->value()); Q_ALLOCA_VAR(uint, matchOffsets, regExp->value()->captureCount() * 2 * sizeof(uint)); uint result = re->match(string, /*offset*/0, matchOffsets); if (result == JSC::Yarr::offsetNoMatch) - scope.result = Encode(-1); + return Encode(-1); else - scope.result = Encode(result); + return Encode(result); } -void StringPrototype::method_slice(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - const QString text = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + Scope scope(v4); + ScopedString s(scope, thisAsString(v4, thisObject)); + if (v4->hasException) + return QV4::Encode::undefined(); + Q_ASSERT(s); - const double length = text.length(); + const double length = s->d()->length(); - double start = callData->argc ? callData->args[0].toInteger() : 0; - double end = (callData->argc < 2 || callData->args[1].isUndefined()) - ? length : callData->args[1].toInteger(); + double start = argc ? argv[0].toInteger() : 0; + double end = (argc < 2 || argv[1].isUndefined()) + ? length : argv[1].toInteger(); if (start < 0) start = qMax(length + start, 0.); @@ -676,16 +945,19 @@ void StringPrototype::method_slice(const BuiltinFunction *, Scope &scope, CallDa const int intEnd = int(end); int count = qMax(0, intEnd - intStart); - scope.result = scope.engine->newString(text.mid(intStart, count)); + return Encode(v4->memoryManager->alloc<ComplexString>(s->d(), intStart, count)); } -void StringPrototype::method_split(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_split(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString text = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + QString text = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - ScopedValue separatorValue(scope, callData->argument(0)); - ScopedValue limitValue(scope, callData->argument(1)); + Scope scope(v4); + ScopedValue separatorValue(scope, argc ? argv[0] : Value::undefinedValue()); + ScopedValue limitValue(scope, argc > 1 ? argv[1] : Value::undefinedValue()); ScopedArrayObject array(scope, scope.engine->newArrayObject()); @@ -693,7 +965,7 @@ void StringPrototype::method_split(const BuiltinFunction *, Scope &scope, CallDa if (limitValue->isUndefined()) { ScopedString s(scope, scope.engine->newString(text)); array->push_back(s); - RETURN_RESULT(array); + return array.asReturnedValue(); } RETURN_RESULT(scope.engine->newString(text.left(limitValue->toInteger()))); } @@ -701,12 +973,12 @@ void StringPrototype::method_split(const BuiltinFunction *, Scope &scope, CallDa uint limit = limitValue->isUndefined() ? UINT_MAX : limitValue->toUInt32(); if (limit == 0) - RETURN_RESULT(array); + return array.asReturnedValue(); Scoped<RegExpObject> re(scope, separatorValue); if (re) { if (re->value()->pattern->isEmpty()) { - re = (RegExpObject *)0; + re = (RegExpObject *)nullptr; separatorValue = scope.engine->newString(); } } @@ -742,7 +1014,7 @@ void StringPrototype::method_split(const BuiltinFunction *, Scope &scope, CallDa if (separator.isEmpty()) { for (uint i = 0; i < qMin(limit, uint(text.length())); ++i) array->push_back((s = scope.engine->newString(text.mid(i, 1)))); - RETURN_RESULT(array); + return array.asReturnedValue(); } int start = 0; @@ -756,44 +1028,47 @@ void StringPrototype::method_split(const BuiltinFunction *, Scope &scope, CallDa if (array->getLength() < limit && start != -1) array->push_back((s = scope.engine->newString(text.mid(start)))); } - RETURN_RESULT(array); + return array.asReturnedValue(); } -void StringPrototype::method_startsWith(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_startsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - QString searchString; - if (callData->argc) { - if (callData->args[0].as<RegExpObject>()) - THROW_TYPE_ERROR(); - searchString = callData->args[0].toQString(); - } + if (argc && argv[0].as<RegExpObject>()) + return v4->throwTypeError(); + QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); + if (v4->hasException) + return Encode::undefined(); int pos = 0; - if (callData->argc > 1) - pos = (int) callData->args[1].toInteger(); + if (argc > 1) + pos = (int) argv[1].toInteger(); if (pos == 0) - RETURN_RESULT(Encode(value.startsWith(searchString))); + return Encode(value.startsWith(searchString)); QStringRef stringToSearch = value.midRef(pos); RETURN_RESULT(Encode(stringToSearch.startsWith(searchString))); } -void StringPrototype::method_substr(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_substr(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - const QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); double start = 0; - if (callData->argc > 0) - start = callData->args[0].toInteger(); + if (argc > 0) + start = argv[0].toInteger(); double length = +qInf(); - if (callData->argc > 1) - length = callData->args[1].toInteger(); + if (argc > 1) + length = argv[1].toInteger(); double count = value.length(); if (start < 0) @@ -801,27 +1076,28 @@ void StringPrototype::method_substr(const BuiltinFunction *, Scope &scope, CallD length = qMin(qMax(length, 0.0), count - start); - qint32 x = Primitive::toInt32(start); - qint32 y = Primitive::toInt32(length); - scope.result = scope.engine->newString(value.mid(x, y)); + qint32 x = Value::toInt32(start); + qint32 y = Value::toInt32(length); + return Encode(v4->newString(value.mid(x, y))); } -void StringPrototype::method_substring(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_substring(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); int length = value.length(); double start = 0; double end = length; - if (callData->argc > 0) - start = callData->args[0].toInteger(); + if (argc > 0) + start = argv[0].toInteger(); - ScopedValue endValue(scope, callData->argument(1)); - if (!endValue->isUndefined()) - end = endValue->toInteger(); + if (argc > 1 && !argv[1].isUndefined()) + end = argv[1].toInteger(); if (std::isnan(start) || start < 0) start = 0; @@ -843,50 +1119,45 @@ void StringPrototype::method_substring(const BuiltinFunction *, Scope &scope, Ca qint32 x = (int)start; qint32 y = (int)(end - start); - scope.result = scope.engine->newString(value.mid(x, y)); + return Encode(v4->newString(value.mid(x, y))); } -void StringPrototype::method_toLowerCase(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_toLowerCase(const FunctionObject *b, const Value *thisObject, const Value *, int) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); - scope.result = scope.engine->newString(value.toLower()); + return Encode(v4->newString(value.toLower())); } -void StringPrototype::method_toLocaleLowerCase(const BuiltinFunction *b, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_toLocaleLowerCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - method_toLowerCase(b, scope, callData); + return method_toLowerCase(b, thisObject, argv, argc); } -void StringPrototype::method_toUpperCase(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_toUpperCase(const FunctionObject *b, const Value *thisObject, const Value *, int) { - QString value = getThisString(scope, callData); - CHECK_EXCEPTION(); - - scope.result = scope.engine->newString(value.toUpper()); -} + ExecutionEngine *v4 = b->engine(); + const QString value = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); -void StringPrototype::method_toLocaleUpperCase(const BuiltinFunction *b, Scope &scope, CallData *callData) -{ - return method_toUpperCase(b, scope, callData); + return Encode(v4->newString(value.toUpper())); } -void StringPrototype::method_fromCharCode(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_toLocaleUpperCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - QString str(callData->argc, Qt::Uninitialized); - QChar *ch = str.data(); - for (int i = 0; i < callData->argc; ++i) { - *ch = QChar(callData->args[i].toUInt16()); - ++ch; - } - scope.result = scope.engine->newString(str); + return method_toUpperCase(b, thisObject, argv, argc); } -void StringPrototype::method_trim(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue StringPrototype::method_trim(const FunctionObject *b, const Value *thisObject, const Value *, int) { - QString s = getThisString(scope, callData); - CHECK_EXCEPTION(); + ExecutionEngine *v4 = b->engine(); + QString s = getThisString(v4, thisObject); + if (v4->hasException) + return QV4::Encode::undefined(); const QChar *chars = s.constData(); int start, end; @@ -899,5 +1170,18 @@ void StringPrototype::method_trim(const BuiltinFunction *, Scope &scope, CallDat break; } - scope.result = scope.engine->newString(QString(chars + start, end - start + 1)); + return Encode(v4->newString(QString(chars + start, end - start + 1))); +} + + + +ReturnedValue StringPrototype::method_iterator(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + ScopedString s(scope, thisObject->toString(scope.engine)); + if (!s || thisObject->isNullOrUndefined()) + return scope.engine->throwTypeError(); + + Scoped<StringIteratorObject> si(scope, scope.engine->memoryManager->allocate<StringIteratorObject>(s->d(), scope.engine)); + return si->asReturnedValue(); } diff --git a/src/qml/jsruntime/qv4stringobject_p.h b/src/qml/jsruntime/qv4stringobject_p.h index ce046f4844..794ee91575 100644 --- a/src/qml/jsruntime/qv4stringobject_p.h +++ b/src/qml/jsruntime/qv4stringobject_p.h @@ -64,12 +64,14 @@ namespace Heap { Member(class, Pointer, String *, string) DECLARE_HEAP_OBJECT(StringObject, Object) { - DECLARE_MARK_TABLE(StringObject); + DECLARE_MARKOBJECTS(StringObject); enum { LengthPropertyIndex = 0 }; + void init(bool /*don't init*/) + { Object::init(); } void init(); void init(const QV4::String *string); @@ -96,18 +98,23 @@ struct StringObject: Object { return d()->length(); } - static bool deleteIndexedProperty(Managed *m, uint index); - + using Object::getOwnProperty; protected: - static void advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attrs); + static bool virtualDeleteProperty(Managed *m, PropertyKey id); + static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); + static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); }; struct StringCtor: FunctionObject { V4_OBJECT2(StringCtor, FunctionObject) - static void construct(const Managed *m, Scope &scope, CallData *callData); - static void call(const Managed *, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_fromCharCode(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_fromCodePoint(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_raw(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; struct StringPrototype: StringObject @@ -115,30 +122,34 @@ struct StringPrototype: StringObject V4_PROTOTYPE(objectPrototype) void init(ExecutionEngine *engine, Object *ctor); - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_charAt(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_charCodeAt(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_concat(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_endsWith(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_indexOf(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_includes(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_lastIndexOf(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_localeCompare(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_match(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_repeat(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_replace(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_search(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_slice(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_split(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_startsWith(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_substr(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_substring(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toLowerCase(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toLocaleLowerCase(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toUpperCase(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toLocaleUpperCase(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_fromCharCode(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_trim(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_charAt(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_charCodeAt(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_codePointAt(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_concat(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_endsWith(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_indexOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_includes(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_lastIndexOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_localeCompare(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_match(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_normalize(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_padEnd(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_padStart(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_repeat(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_replace(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_search(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_slice(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_split(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_startsWith(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_substr(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_substring(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLowerCase(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleLowerCase(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toUpperCase(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleUpperCase(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_trim(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_iterator(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; } diff --git a/src/qml/jsruntime/qv4symbol.cpp b/src/qml/jsruntime/qv4symbol.cpp new file mode 100644 index 0000000000..004a9938e2 --- /dev/null +++ b/src/qml/jsruntime/qv4symbol.cpp @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qv4symbol_p.h> +#include <qv4functionobject_p.h> +#include <qv4identifiertable_p.h> + +using namespace QV4; + +DEFINE_OBJECT_VTABLE(SymbolCtor); +DEFINE_MANAGED_VTABLE(Symbol); +DEFINE_OBJECT_VTABLE(SymbolObject); + +void Heap::Symbol::init(const QString &s) +{ + Q_ASSERT(s.at(0) == QLatin1Char('@')); + identifier = PropertyKey::fromStringOrSymbol(this); + QString desc(s); + text = desc.data_ptr(); + text->ref.ref(); +} + +void Heap::SymbolCtor::init(QV4::ExecutionContext *scope) +{ + Heap::FunctionObject::init(scope, QStringLiteral("Symbol")); +} + +void Heap::SymbolObject::init(const QV4::Symbol *s) +{ + Object::init(); + symbol.set(internalClass->engine, s->d()); +} + +ReturnedValue QV4::SymbolCtor::virtualCall(const QV4::FunctionObject *f, const QV4::Value *, const QV4::Value *argv, int argc) +{ + Scope scope(f); + QString desc = QChar::fromLatin1('@'); + if (argc && !argv[0].isUndefined()) { + ScopedString s(scope, argv[0].toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + desc += s->toQString(); + } + return Symbol::create(scope.engine, desc)->asReturnedValue(); +} + +ReturnedValue SymbolCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *, int, const Value *) +{ + return f->engine()->throwTypeError(QStringLiteral("Symbol can't be used together with |new|.")); +} + +ReturnedValue SymbolCtor::method_for(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + Scope scope(f); + ScopedValue k(scope, argc ? argv[0]: Value::undefinedValue()); + ScopedString key(scope, k->toString(scope.engine)); + if (scope.hasException()) + return Encode::undefined(); + QString desc = QLatin1Char('@') + key->toQString(); + return scope.engine->identifierTable->insertSymbol(desc)->asReturnedValue(); +} + +ReturnedValue SymbolCtor::method_keyFor(const FunctionObject *f, const Value *, const Value *argv, int argc) +{ + ExecutionEngine *e = f->engine(); + if (!argc || !argv[0].isSymbol()) + return e->throwTypeError(QLatin1String("Symbol.keyFor: Argument is not a symbol.")); + const Symbol &arg = static_cast<const Symbol &>(argv[0]); + Heap::Symbol *s = e->identifierTable->symbolForId(arg.propertyKey()); + Q_ASSERT(!s || s == arg.d()); + if (s) + return e->newString(arg.toQString().mid((1)))->asReturnedValue(); + return Encode::undefined(); +} + +void SymbolPrototype::init(ExecutionEngine *engine, Object *ctor) +{ + Scope scope(engine); + ScopedValue v(scope); + ctor->defineReadonlyProperty(engine->id_prototype(), (v = this)); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0)); + + ctor->defineDefaultProperty(QStringLiteral("for"), SymbolCtor::method_for, 1); + ctor->defineDefaultProperty(QStringLiteral("keyFor"), SymbolCtor::method_keyFor, 1); + ctor->defineReadonlyProperty(QStringLiteral("hasInstance"), *engine->symbol_hasInstance()); + ctor->defineReadonlyProperty(QStringLiteral("isConcatSpreadable"), *engine->symbol_isConcatSpreadable()); + ctor->defineReadonlyProperty(QStringLiteral("iterator"), *engine->symbol_iterator()); + ctor->defineReadonlyProperty(QStringLiteral("match"), *engine->symbol_match()); + ctor->defineReadonlyProperty(QStringLiteral("replace"), *engine->symbol_replace()); + ctor->defineReadonlyProperty(QStringLiteral("search"), *engine->symbol_search()); + ctor->defineReadonlyProperty(QStringLiteral("species"), *engine->symbol_species()); + ctor->defineReadonlyProperty(QStringLiteral("split"), *engine->symbol_split()); + ctor->defineReadonlyProperty(QStringLiteral("toPrimitive"), *engine->symbol_toPrimitive()); + ctor->defineReadonlyProperty(QStringLiteral("toStringTag"), *engine->symbol_toStringTag()); + ctor->defineReadonlyProperty(QStringLiteral("unscopables"), *engine->symbol_unscopables()); + + defineDefaultProperty(QStringLiteral("constructor"), (v = ctor)); + defineDefaultProperty(QStringLiteral("toString"), method_toString); + defineDefaultProperty(QStringLiteral("valueOf"), method_valueOf); + defineDefaultProperty(engine->symbol_toPrimitive(), method_symbolToPrimitive, 1, Attr_ReadOnly_ButConfigurable); + + v = engine->newString(QStringLiteral("Symbol")); + defineReadonlyConfigurableProperty(engine->symbol_toStringTag(), v); + +} + +ReturnedValue SymbolPrototype::method_toString(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + Scoped<Symbol> s(scope, thisObject->as<Symbol>()); + if (!s) { + if (const SymbolObject *o = thisObject->as<SymbolObject>()) + s = o->d()->symbol; + else + return scope.engine->throwTypeError(); + } + return scope.engine->newString(s->descriptiveString())->asReturnedValue(); +} + +ReturnedValue SymbolPrototype::method_valueOf(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + Scope scope(f); + Scoped<Symbol> s(scope, thisObject->as<Symbol>()); + if (!s) { + if (const SymbolObject *o = thisObject->as<SymbolObject>()) + s = o->d()->symbol; + else + return scope.engine->throwTypeError(); + } + return s->asReturnedValue(); +} + +ReturnedValue SymbolPrototype::method_symbolToPrimitive(const FunctionObject *f, const Value *thisObject, const Value *, int) +{ + if (thisObject->isSymbol()) + return thisObject->asReturnedValue(); + if (const SymbolObject *o = thisObject->as<SymbolObject>()) + return o->d()->symbol->asReturnedValue(); + return f->engine()->throwTypeError(); +} + +Heap::Symbol *Symbol::create(ExecutionEngine *e, const QString &s) +{ + Q_ASSERT(s.at(0) == QLatin1Char('@')); + return e->memoryManager->alloc<Symbol>(s); +} + +QString Symbol::descriptiveString() const +{ + return QLatin1String("Symbol(") + toQString().midRef(1) + QLatin1String(")"); +} diff --git a/src/qml/jsruntime/qv4symbol_p.h b/src/qml/jsruntime/qv4symbol_p.h new file mode 100644 index 0000000000..c7e12b512b --- /dev/null +++ b/src/qml/jsruntime/qv4symbol_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4_SYMBOL_H +#define QV4_SYMBOL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4string_p.h" +#include "qv4functionobject_p.h" + +QT_BEGIN_NAMESPACE + + +namespace QV4 { + +namespace Heap { + +struct SymbolCtor : FunctionObject { + void init(QV4::ExecutionContext *scope); +}; + +struct Symbol : StringOrSymbol { + void init(const QString &s); +}; + +#define SymbolObjectMembers(class, Member) \ + Member(class, Pointer, Symbol *, symbol) + +DECLARE_HEAP_OBJECT(SymbolObject, Object) { + DECLARE_MARKOBJECTS(SymbolObject); + void init(const QV4::Symbol *s); +}; + +} + +struct SymbolCtor : FunctionObject +{ + V4_OBJECT2(SymbolCtor, FunctionObject) + + static ReturnedValue virtualCall(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *, const Value *argv, int argc, const Value *newTarget); + static ReturnedValue method_for(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_keyFor(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + +struct SymbolPrototype : Object +{ + V4_PROTOTYPE(objectPrototype) + void init(ExecutionEngine *engine, Object *ctor); + + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_valueOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_symbolToPrimitive(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); +}; + +struct Symbol : StringOrSymbol +{ + V4_MANAGED(Symbol, StringOrSymbol) + Q_MANAGED_TYPE(Symbol) + V4_INTERNALCLASS(Symbol) + V4_NEEDS_DESTROY + + static Heap::Symbol *create(ExecutionEngine *e, const QString &s); + + QString descriptiveString() const; +}; + +struct SymbolObject : Object +{ + V4_OBJECT2(SymbolObject, Object) + Q_MANAGED_TYPE(SymbolObject) + V4_INTERNALCLASS(SymbolObject) + V4_PROTOTYPE(symbolPrototype) +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index fe27d7c2d2..d83f021450 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -36,169 +36,241 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ + #include "qv4typedarray_p.h" +#include "qv4arrayiterator_p.h" #include "qv4arraybuffer_p.h" #include "qv4string_p.h" +#include "qv4jscall_p.h" +#include "qv4symbol_p.h" +#include "qv4runtime_p.h" +#include <QtCore/qatomic.h> #include <cmath> using namespace QV4; +DEFINE_OBJECT_VTABLE(IntrinsicTypedArrayCtor); +DEFINE_OBJECT_VTABLE(IntrinsicTypedArrayPrototype); DEFINE_OBJECT_VTABLE(TypedArrayCtor); DEFINE_OBJECT_VTABLE(TypedArrayPrototype); DEFINE_OBJECT_VTABLE(TypedArray); -Q_STATIC_ASSERT((int)ExecutionEngine::NTypedArrayTypes == (int)Heap::TypedArray::NTypes); +Q_STATIC_ASSERT((int)ExecutionEngine::NTypedArrayTypes == (int)NTypedArrayTypes); -ReturnedValue Int8ArrayRead(const char *data, int index) +static inline int toInt32(Value v) { - return Encode((int)(signed char)data[index]); + Q_ASSERT(v.isNumber()); + if (v.isInteger()) + return v.integerValue(); + return Double::toInt32(v.doubleValue()); } -void Int8ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +static inline double toDouble(Value v) { - signed char v = (signed char)value.toUInt32(); - if (e->hasException) - return; - data[index] = v; + Q_ASSERT(v.isNumber()); + if (v.isInteger()) + return v.integerValue(); + return v.doubleValue(); } -ReturnedValue UInt8ArrayRead(const char *data, int index) -{ - return Encode((int)(unsigned char)data[index]); +struct ClampedUInt8 { + quint8 c; +}; + +template <typename T> +ReturnedValue typeToValue(T t) { + return Encode(t); +} + +template <> +ReturnedValue typeToValue(ClampedUInt8 t) { + return Encode(t.c); } -void UInt8ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +T valueToType(Value value) { - unsigned char v = (unsigned char)value.toUInt32(); - if (e->hasException) - return; - data[index] = v; + Q_ASSERT(value.isNumber()); + int n = toInt32(value); + return static_cast<T>(n); } -void UInt8ClampedArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <> +ClampedUInt8 valueToType(Value value) { - if (value.isInteger()) { - data[index] = (char)(unsigned char)qBound(0, value.integerValue(), 255); - return; - } - double d = value.toNumber(); - if (e->hasException) - return; + Q_ASSERT(value.isNumber()); + if (value.isInteger()) + return { static_cast<quint8>(qBound(0, value.integerValue(), 255)) }; + Q_ASSERT(value.isDouble()); + double d = value.doubleValue(); // ### is there a way to optimise this? - if (d <= 0 || std::isnan(d)) { - data[index] = 0; - return; - } - if (d >= 255) { - data[index] = (char)(255); - return; - } + if (d <= 0 || std::isnan(d)) + return { 0 }; + if (d >= 255) + return { 255 }; double f = std::floor(d); - if (f + 0.5 < d) { - data[index] = (unsigned char)(f + 1); - return; - } - if (d < f + 0.5) { - data[index] = (unsigned char)(f); - return; - } - if (int(f) % 2) { + if (f + 0.5 < d) + return { (quint8)(f + 1) }; + if (d < f + 0.5) + return { (quint8)(f) }; + if (int(f) % 2) // odd number - data[index] = (unsigned char)(f + 1); - return; - } - data[index] = (unsigned char)(f); + return { (quint8)(f + 1) }; + return { (quint8)(f) }; +} + +template <> +float valueToType(Value value) +{ + Q_ASSERT(value.isNumber()); + double d = toDouble(value); + return static_cast<float>(d); +} + +template <> +double valueToType(Value value) +{ + Q_ASSERT(value.isNumber()); + return toDouble(value); } -ReturnedValue Int16ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue read(const char *data) { + return typeToValue(*reinterpret_cast<const T *>(data)); +} +template <typename T> +void write(char *data, Value value) { - return Encode((int)*(const short *)(data + index)); + *reinterpret_cast<T *>(data) = valueToType<T>(value); } -void Int16ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicAdd(char *data, Value v) { - short v = (short)value.toInt32(); - if (e->hasException) - return; - *(short *)(data + index) = v; + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndAddOrdered(*mem, value); + return typeToValue(value); } -ReturnedValue UInt16ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue atomicAnd(char *data, Value v) { - return Encode((int)*(const unsigned short *)(data + index)); + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndAndOrdered(*mem, value); + return typeToValue(value); } -void UInt16ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicExchange(char *data, Value v) { - unsigned short v = (unsigned short)value.toInt32(); - if (e->hasException) - return; - *(unsigned short *)(data + index) = v; + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndStoreOrdered(*mem, value); + return typeToValue(value); } -ReturnedValue Int32ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue atomicOr(char *data, Value v) { - return Encode(*(const int *)(data + index)); + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndOrOrdered(*mem, value); + return typeToValue(value); } -void Int32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicSub(char *data, Value v) { - int v = (int)value.toInt32(); - if (e->hasException) - return; - *(int *)(data + index) = v; + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndSubOrdered(*mem, value); + return typeToValue(value); } -ReturnedValue UInt32ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue atomicXor(char *data, Value v) { - return Encode(*(const unsigned int *)(data + index)); + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndXorOrdered(*mem, value); + return typeToValue(value); } -void UInt32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicCompareExchange(char *data, Value expected, Value v) { - unsigned int v = (unsigned int)value.toUInt32(); - if (e->hasException) - return; - *(unsigned int *)(data + index) = v; + T value = valueToType<T>(v); + T exp = valueToType<T>(expected); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + T old; + QAtomicOps<T>::testAndSetOrdered(*mem, exp, value, &old); + return typeToValue(old); } -ReturnedValue Float32ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue atomicLoad(char *data) { - return Encode(*(const float *)(data + index)); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + T val = QAtomicOps<T>::load(*mem); + return typeToValue(val); } -void Float32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicStore(char *data, Value v) { - float v = value.toNumber(); - if (e->hasException) - return; - *(float *)(data + index) = v; + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + QAtomicOps<T>::store(*mem, value); + return typeToValue(value); } -ReturnedValue Float64ArrayRead(const char *data, int index) + +template<typename T> +constexpr TypedArrayOperations TypedArrayOperations::create(const char *name) { - return Encode(*(const double *)(data + index)); + return { sizeof(T), + name, + ::read<T>, + ::write<T>, + { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }, + nullptr, + nullptr, + nullptr + }; } -void Float64ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template<typename T> +constexpr TypedArrayOperations TypedArrayOperations::createWithAtomics(const char *name) { - double v = value.toNumber(); - if (e->hasException) - return; - *(double *)(data + index) = v; + return { sizeof(T), + name, + ::read<T>, + ::write<T>, + { ::atomicAdd<T>, ::atomicAnd<T>, ::atomicExchange<T>, ::atomicOr<T>, ::atomicSub<T>, ::atomicXor<T> }, + ::atomicCompareExchange<T>, + ::atomicLoad<T>, + ::atomicStore<T> + }; } -const TypedArrayOperations operations[Heap::TypedArray::NTypes] = { - { 1, "Int8Array", Int8ArrayRead, Int8ArrayWrite }, - { 1, "Uint8Array", UInt8ArrayRead, UInt8ArrayWrite }, - { 1, "Uint8ClampedArray", UInt8ArrayRead, UInt8ClampedArrayWrite }, - { 2, "Int16Array", Int16ArrayRead, Int16ArrayWrite }, - { 2, "Uint16Array", UInt16ArrayRead, UInt16ArrayWrite }, - { 4, "Int32Array", Int32ArrayRead, Int32ArrayWrite }, - { 4, "Uint32Array", UInt32ArrayRead, UInt32ArrayWrite }, - { 4, "Float32Array", Float32ArrayRead, Float32ArrayWrite }, - { 8, "Float64Array", Float64ArrayRead, Float64ArrayWrite }, +const TypedArrayOperations operations[NTypedArrayTypes] = { +#ifdef Q_ATOMIC_INT8_IS_SUPPORTED + TypedArrayOperations::createWithAtomics<qint8>("Int8Array"), + TypedArrayOperations::createWithAtomics<quint8>("Uint8Array"), +#else + TypedArrayOperations::create<qint8>("Int8Array"), + TypedArrayOperations::create<quint8>("Uint8Array"), +#endif + TypedArrayOperations::createWithAtomics<qint16>("Int16Array"), + TypedArrayOperations::createWithAtomics<quint16>("Uint16Array"), + TypedArrayOperations::createWithAtomics<qint32>("Int32Array"), + TypedArrayOperations::createWithAtomics<quint32>("Uint32Array"), + TypedArrayOperations::create<ClampedUInt8>("Uint8ClampedArray"), + TypedArrayOperations::create<float>("Float32Array"), + TypedArrayOperations::create<double>("Float64Array") }; @@ -208,49 +280,58 @@ void Heap::TypedArrayCtor::init(QV4::ExecutionContext *scope, TypedArray::Type t type = t; } -void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callData) +ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - Scoped<TypedArrayCtor> that(scope, static_cast<const TypedArrayCtor *>(m)); + Scope scope(f->engine()); + const TypedArrayCtor *that = static_cast<const TypedArrayCtor *>(f); + + auto updateProto = [=](Scope &scope, Scoped<TypedArray> &a) { + if (newTarget->heapObject() != f->heapObject() && newTarget->isFunctionObject()) { + const FunctionObject *nt = static_cast<const FunctionObject *>(newTarget); + ScopedObject o(scope, nt->protoProperty()); + if (o) + a->setPrototypeOf(o); + } + }; - if (!callData->argc || !callData->args[0].isObject()) { + if (!argc || !argv[0].isObject()) { // ECMA 6 22.2.1.1 - double l = callData->argc ? callData->args[0].toNumber() : 0; - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + qint64 l = argc ? argv[0].toIndex() : 0; + if (scope.engine->hasException) + return Encode::undefined(); + // ### lift UINT_MAX restriction + if (l < 0 || l > UINT_MAX) + return scope.engine->throwRangeError(QLatin1String("Index out of range.")); uint len = (uint)l; if (l != len) scope.engine->throwRangeError(QStringLiteral("Non integer length for typed array.")); uint byteLength = len * operations[that->d()->type].bytesPerElement; Scoped<ArrayBuffer> buffer(scope, scope.engine->newArrayBuffer(byteLength)); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + if (scope.engine->hasException) + return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, buffer->d()); array->d()->byteLength = byteLength; array->d()->byteOffset = 0; - scope.result = array.asReturnedValue(); - return; + updateProto(scope, array); + return array.asReturnedValue(); } - Scoped<TypedArray> typedArray(scope, callData->argument(0)); + Scoped<TypedArray> typedArray(scope, argc ? argv[0] : Value::undefinedValue()); if (!!typedArray) { // ECMA 6 22.2.1.2 Scoped<ArrayBuffer> buffer(scope, typedArray->d()->buffer); + if (!buffer || buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); uint srcElementSize = typedArray->d()->type->bytesPerElement; uint destElementSize = operations[that->d()->type].bytesPerElement; uint byteLength = typedArray->d()->byteLength; uint destByteLength = byteLength*destElementSize/srcElementSize; Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(destByteLength)); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + if (scope.engine->hasException) + return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, newBuffer->d()); @@ -266,48 +347,46 @@ void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callDat } else { // not same size, we need to loop uint l = typedArray->length(); - TypedArrayRead read = typedArray->d()->type->read; - TypedArrayWrite write =array->d()->type->write; + TypedArrayOperations::Read read = typedArray->d()->type->read; + TypedArrayOperations::Write write =array->d()->type->write; for (uint i = 0; i < l; ++i) { - Primitive val; - val.setRawValue(read(src, i*srcElementSize)); - write(scope.engine, dest, i*destElementSize, val); + Value val; + val.setRawValue(read(src + i*srcElementSize)); + write(dest + i*destElementSize, val); } } - scope.result = array.asReturnedValue(); - return; + updateProto(scope, array); + return array.asReturnedValue(); } - Scoped<ArrayBuffer> buffer(scope, callData->argument(0)); + Scoped<ArrayBuffer> buffer(scope, argc ? argv[0] : Value::undefinedValue()); if (!!buffer) { // ECMA 6 22.2.1.4 - double dbyteOffset = callData->argc > 1 ? callData->args[1].toInteger() : 0; + double dbyteOffset = argc > 1 ? argv[1].toInteger() : 0; + + if (buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + uint byteOffset = (uint)dbyteOffset; uint elementSize = operations[that->d()->type].bytesPerElement; - if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->byteLength()) { - scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset")); - return; - } + if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->byteLength()) + return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset")); uint byteLength; - if (callData->argc < 3 || callData->args[2].isUndefined()) { + if (argc < 3 || argv[2].isUndefined()) { byteLength = buffer->byteLength() - byteOffset; - if (buffer->byteLength() < byteOffset || byteLength % elementSize) { - scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); - return; - } + if (buffer->byteLength() < byteOffset || byteLength % elementSize) + return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); } else { - double l = qBound(0., callData->args[2].toInteger(), (double)UINT_MAX); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + double l = qBound(0., argv[2].toInteger(), (double)UINT_MAX); + if (scope.engine->hasException) + return Encode::undefined(); + if (buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); l *= elementSize; - if (buffer->byteLength() - byteOffset < l) { - scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); - return; - } + if (buffer->byteLength() - byteOffset < l) + return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); byteLength = (uint)l; } @@ -315,25 +394,25 @@ void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callDat array->d()->buffer.set(scope.engine, buffer->d()); array->d()->byteLength = byteLength; array->d()->byteOffset = byteOffset; - scope.result = array.asReturnedValue(); - return; + + updateProto(scope, array); + return array.asReturnedValue(); } // ECMA 6 22.2.1.3 - ScopedObject o(scope, callData->argument(0)); + ScopedObject o(scope, argc ? argv[0] : Value::undefinedValue()); uint l = (uint) qBound(0., ScopedValue(scope, o->get(scope.engine->id_length()))->toInteger(), (double)UINT_MAX); - if (scope.engine->hasException) { - scope.result = scope.engine->throwTypeError(); - return; - } + if (scope.engine->hasException) + return scope.engine->throwTypeError(); uint elementSize = operations[that->d()->type].bytesPerElement; - Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(l * elementSize)); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + size_t bufferSize; + if (mul_overflow(size_t(l), size_t(elementSize), &bufferSize)) + return scope.engine->throwRangeError(QLatin1String("new TypedArray: invalid length")); + Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(bufferSize)); + if (scope.engine->hasException) + return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, newBuffer->d()); @@ -344,172 +423,1017 @@ void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callDat char *b = newBuffer->d()->data->data(); ScopedValue val(scope); while (idx < l) { - val = o->getIndexed(idx); - array->d()->type->write(scope.engine, b, 0, val); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + val = o->get(idx); + val = val->convertedToNumber(); + if (scope.engine->hasException) + return Encode::undefined(); + array->d()->type->write(b, val); + if (scope.engine->hasException) + return Encode::undefined(); ++idx; b += elementSize; } - - scope.result = array.asReturnedValue(); + updateProto(scope, array); + return array.asReturnedValue(); } -void TypedArrayCtor::call(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue TypedArrayCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) { - construct(that, scope, callData); + return f->engine()->throwTypeError(QStringLiteral("calling a TypedArray constructor without new is invalid")); } void Heap::TypedArray::init(Type t) { Object::init(); - type = operations + t; - arrayType = t; + type = operations + static_cast<int>(t); + arrayType = static_cast<int>(t); } Heap::TypedArray *TypedArray::create(ExecutionEngine *e, Heap::TypedArray::Type t) { - QV4::InternalClass *ic = e->internalClasses[EngineBase::Class_Empty]->changeVTable(staticVTable()); - ic = ic->changePrototype(e->typedArrayPrototype[t].d()); - return e->memoryManager->allocObject<TypedArray>(ic, e->typedArrayPrototype + t, t); + Scope scope(e); + Scoped<InternalClass> ic(scope, e->newInternalClass(staticVTable(), e->typedArrayPrototype + static_cast<int>(t))); + return e->memoryManager->allocObject<TypedArray>(ic->d(), t); } -ReturnedValue TypedArray::getIndexed(const Managed *m, uint index, bool *hasProperty) +ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualGet(m, id, receiver, hasProperty); + // fall through, with index == UINT_MAX it'll do the right thing. + Scope scope(static_cast<const Object *>(m)->engine()); Scoped<TypedArray> a(scope, static_cast<const TypedArray *>(m)); + if (a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); - uint bytesPerElement = a->d()->type->bytesPerElement; - uint byteOffset = a->d()->byteOffset + index * bytesPerElement; - if (byteOffset + bytesPerElement > (uint)a->d()->buffer->byteLength()) { + if (index >= a->length()) { if (hasProperty) *hasProperty = false; return Encode::undefined(); } + + uint bytesPerElement = a->d()->type->bytesPerElement; + uint byteOffset = a->d()->byteOffset + index * bytesPerElement; + Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); + if (hasProperty) *hasProperty = true; - return a->d()->type->read(a->d()->buffer->data->data(), byteOffset); + return a->d()->type->read(a->d()->buffer->data->data() + byteOffset); +} + +bool TypedArray::virtualHasProperty(const Managed *m, PropertyKey id) +{ + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualHasProperty(m, id); + // fall through, with index == UINT_MAX it'll do the right thing. + + const TypedArray *a = static_cast<const TypedArray *>(m); + if (a->d()->buffer->isDetachedBuffer()) { + a->engine()->throwTypeError(); + return false; + } + if (index >= a->length()) + return false; + return true; } -bool TypedArray::putIndexed(Managed *m, uint index, const Value &value) +PropertyAttributes TypedArray::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualGetOwnProperty(m, id, p); + // fall through, with index == UINT_MAX it'll do the right thing. + + bool hasProperty = false; + ReturnedValue v = virtualGet(m, id, m, &hasProperty); + if (p) + p->value = v; + return hasProperty ? Attr_NotConfigurable : PropertyAttributes(); +} + +bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) +{ + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualPut(m, id, value, receiver); + // fall through, with index == UINT_MAX it'll do the right thing. + ExecutionEngine *v4 = static_cast<Object *>(m)->engine(); if (v4->hasException) return false; Scope scope(v4); Scoped<TypedArray> a(scope, static_cast<TypedArray *>(m)); + if (a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + if (index >= a->length()) + return false; uint bytesPerElement = a->d()->type->bytesPerElement; uint byteOffset = a->d()->byteOffset + index * bytesPerElement; - if (byteOffset + bytesPerElement > (uint)a->d()->buffer->byteLength()) - goto reject; + Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); - a->d()->type->write(scope.engine, a->d()->buffer->data->data(), byteOffset, value); + Value v = Value::fromReturnedValue(value.convertedToNumber()); + if (scope.hasException() || a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + a->d()->type->write(a->d()->buffer->data->data() + byteOffset, v); return true; +} -reject: - if (scope.engine->current->strictMode) - scope.engine->throwTypeError(); - return false; +bool TypedArray::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) +{ + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualDefineOwnProperty(m, id, p, attrs); + // fall through, with index == UINT_MAX it'll do the right thing. + + TypedArray *a = static_cast<TypedArray *>(m); + if (index >= a->length() || attrs.isAccessor()) + return false; + + if (attrs.hasConfigurable() && attrs.isConfigurable()) + return false; + if (attrs.hasEnumerable() && !attrs.isEnumerable()) + return false; + if (attrs.hasWritable() && !attrs.isWritable()) + return false; + if (!p->value.isEmpty()) { + ExecutionEngine *engine = a->engine(); + + Value v = Value::fromReturnedValue(p->value.convertedToNumber()); + if (engine->hasException || a->d()->buffer->isDetachedBuffer()) + return engine->throwTypeError(); + uint bytesPerElement = a->d()->type->bytesPerElement; + uint byteOffset = a->d()->byteOffset + index * bytesPerElement; + Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); + a->d()->type->write(a->d()->buffer->data->data() + byteOffset, v); + } + return true; +} + +struct TypedArrayOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator +{ + ~TypedArrayOwnPropertyKeyIterator() override = default; + PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; + +}; + +PropertyKey TypedArrayOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) +{ + const TypedArray *a = static_cast<const TypedArray *>(o); + if (arrayIndex < a->length()) { + if (attrs) + *attrs = Attr_NotConfigurable; + PropertyKey id = PropertyKey::fromArrayIndex(arrayIndex); + if (pd) { + bool hasProperty = false; + pd->value = TypedArray::virtualGet(a, id, a, &hasProperty); + } + ++arrayIndex; + return id; + } + + arrayIndex = UINT_MAX; + return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); +} + +OwnPropertyKeyIterator *TypedArray::virtualOwnPropertyKeys(const Object *m, Value *target) +{ + *target = *m; + return new TypedArrayOwnPropertyKeyIterator(); } void TypedArrayPrototype::init(ExecutionEngine *engine, TypedArrayCtor *ctor) { Scope scope(engine); ScopedObject o(scope); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(3)); - ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); - ctor->defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Primitive::fromInt32(operations[ctor->d()->type].bytesPerElement)); + + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(3)); + ctor->defineReadonlyProperty(engine->id_prototype(), *this); + ctor->defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast<int>(ctor->d()->type)].bytesPerElement)); + ctor->setPrototypeOf(engine->intrinsicTypedArrayCtor()); + + setPrototypeOf(engine->intrinsicTypedArrayPrototype()); defineDefaultProperty(engine->id_constructor(), (o = ctor)); - defineAccessorProperty(QStringLiteral("buffer"), method_get_buffer, 0); - defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, 0); - defineAccessorProperty(QStringLiteral("byteOffset"), method_get_byteOffset, 0); - defineAccessorProperty(QStringLiteral("length"), method_get_length, 0); - defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Primitive::fromInt32(operations[ctor->d()->type].bytesPerElement)); + defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast<int>(ctor->d()->type)].bytesPerElement)); +} - defineDefaultProperty(QStringLiteral("set"), method_set, 1); - defineDefaultProperty(QStringLiteral("subarray"), method_subarray, 0); +ReturnedValue IntrinsicTypedArrayPrototype::method_get_buffer(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + ExecutionEngine *v4 = b->engine(); + const TypedArray *v = thisObject->as<TypedArray>(); + if (!v) + return v4->throwTypeError(); + + return v->d()->buffer->asReturnedValue(); } -void TypedArrayPrototype::method_get_buffer(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteLength(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<TypedArray> v(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + const TypedArray *v = thisObject->as<TypedArray>(); if (!v) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); + + if (v->d()->buffer->isDetachedBuffer()) + return Encode(0); - scope.result = v->d()->buffer; + return Encode(v->d()->byteLength); } -void TypedArrayPrototype::method_get_byteLength(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteOffset(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<TypedArray> v(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + const TypedArray *v = thisObject->as<TypedArray>(); if (!v) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); - scope.result = Encode(v->d()->byteLength); + if (v->d()->buffer->isDetachedBuffer()) + return Encode(0); + + return Encode(v->d()->byteOffset); } -void TypedArrayPrototype::method_get_byteOffset(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_get_length(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<TypedArray> v(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + const TypedArray *v = thisObject->as<TypedArray>(); if (!v) + return v4->throwTypeError(); + + if (v->d()->buffer->isDetachedBuffer()) + return Encode(0); + + return Encode(v->d()->byteLength/v->d()->type->bytesPerElement); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_copyWithin(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + Scoped<TypedArray> O(scope, thisObject); + if (!O || O->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + if (!argc) + return O->asReturnedValue(); + + qint64 len = static_cast<uint>(O->length()); + + qint64 to = static_cast<qint64>(argv[0].toInteger()); + if (to < 0) + to = qMax(len + to, 0ll); + else + to = qMin(to, len); + + qint64 from = (argc > 1) ? static_cast<qint64>(argv[1].toInteger()) : 0ll; + if (from < 0) + from = qMax(len + from, 0ll); + else + from = qMin(from, len); + + double fend = argv[2].toInteger(); + if (fend > len) + fend = len; + qint64 end = (argc > 2 && !argv[2].isUndefined()) ? static_cast<qint64>(fend) : len; + if (end < 0) + end = qMax(len + end, 0ll); + else + end = qMin(end, len); + + qint64 count = qMin(end - from, len - to); + + if (count <= 0) + return O->asReturnedValue(); + + if (O->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + if (from != to) { + int elementSize = O->d()->type->bytesPerElement; + char *data = O->d()->buffer->data->data() + O->d()->byteOffset; + memmove(data + to*elementSize, data + from*elementSize, count*elementSize); + } + + return O->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); + ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; + return ao->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_every(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + ScopedValue r(scope); + Value *arguments = scope.alloc(3); - scope.result = Encode(v->d()->byteOffset); + const char *data = v->d()->buffer->data->data(); + uint bytesPerElement = v->d()->type->bytesPerElement; + uint byteOffset = v->d()->byteOffset; + + bool ok = true; + for (uint k = 0; ok && k < len; ++k) { + if (v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + arguments[0] = v->d()->type->read(data + byteOffset + k * bytesPerElement); + + arguments[1] = Value::fromDouble(k); + arguments[2] = v; + r = callback->call(that, arguments, 3); + ok = r->toBoolean(); + } + return Encode(ok); } -void TypedArrayPrototype::method_get_length(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<TypedArray> v(scope, callData->thisObject); - if (!v) + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + double dlen = len; + double relativeStart = argc > 1 ? argv[1].toInteger() : 0.; + double relativeEnd = len; + if (argc > 2 && !argv[2].isUndefined()) + relativeEnd = argv[2].toInteger(); + + uint k = 0; + uint fin = 0; + + if (relativeStart < 0) { + k = static_cast<uint>(std::max(len+relativeStart, 0.)); + } else { + k = static_cast<uint>(std::min(relativeStart, dlen)); + } + + if (relativeEnd < 0) { + fin = static_cast<uint>(std::max(len + relativeEnd, 0.)); + } else { + fin = static_cast<uint>(std::min(relativeEnd, dlen)); + } + + double val = argc ? argv[0].toNumber() : std::numeric_limits<double>::quiet_NaN(); + Value value = Value::fromDouble(val); + if (scope.hasException() || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + char *data = v->d()->buffer->data->data(); + uint bytesPerElement = v->d()->type->bytesPerElement; + uint byteOffset = v->d()->byteOffset; + + while (k < fin) { + v->d()->type->write(data + byteOffset + k * bytesPerElement, value); + k++; + } + + return v.asReturnedValue(); +} + +static TypedArray *typedArraySpeciesCreate(Scope &scope, const TypedArray *instance, uint len) +{ + const FunctionObject *constructor = instance->speciesConstructor(scope, scope.engine->typedArrayCtors + instance->d()->arrayType); + if (!constructor) { + scope.engine->throwTypeError(); + return nullptr; + } + + Value *arguments = scope.alloc(1); + arguments[0] = Encode(len); + Scoped<TypedArray> a(scope, constructor->callAsConstructor(arguments, 1)); + if (!a || a->d()->buffer->isDetachedBuffer() || a->length() < len) { + scope.engine->throwTypeError(); + return nullptr; + } + return a; +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_filter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue selected(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); + Value *list = arguments; + + uint to = 0; + for (uint k = 0; k < len; ++k) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool exists; + arguments[0] = instance->get(k, &exists); + if (!exists) + continue; + + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + selected = callback->call(that, arguments, 3); + if (selected->toBoolean()) { + ++arguments; + scope.alloc(1); + ++to; + } + } + + TypedArray *a = typedArraySpeciesCreate(scope, instance, to); + if (!a) + return Encode::undefined(); - scope.result = Encode(v->d()->byteLength/v->d()->type->bytesPerElement); + for (uint i = 0; i < to; ++i) + a->put(i, list[i]); + + return a->asReturnedValue(); } -void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_find(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<TypedArray> a(scope, callData->thisObject); + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + if (!argc || !argv[0].isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue result(scope); + Value *arguments = scope.alloc(3); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + + for (uint k = 0; k < len; ++k) { + if (v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + arguments[0] = v->get(k); + CHECK_EXCEPTION(); + + arguments[1] = Value::fromDouble(k); + arguments[2] = v; + result = callback->call(that, arguments, 3); + + CHECK_EXCEPTION(); + if (result->toBoolean()) + return arguments[0].asReturnedValue(); + } + + RETURN_UNDEFINED(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_findIndex(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + if (!argc || !argv[0].isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue result(scope); + Value *arguments = scope.alloc(3); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + + for (uint k = 0; k < len; ++k) { + if (v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + arguments[0] = v->get(k); + CHECK_EXCEPTION(); + + arguments[1] = Value::fromDouble(k); + arguments[2] = v; + result = callback->call(that, arguments, 3); + + CHECK_EXCEPTION(); + if (result->toBoolean()) + return Encode(k); + } + + return Encode(-1); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + if (!argc || !argv->isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); + + for (uint k = 0; k < len; ++k) { + if (v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool exists; + arguments[0] = v->get(k, &exists); + if (!exists) + continue; + + arguments[1] = Value::fromDouble(k); + arguments[2] = v; + callback->call(that, arguments, 3); + } + RETURN_UNDEFINED(); +} + + +ReturnedValue IntrinsicTypedArrayPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + if (len == 0) { + return Encode(false); + } + + double n = 0; + if (argc > 1 && !argv[1].isUndefined()) { + n = argv[1].toInteger(); + } + + double k = 0; + if (n >= 0) { + k = n; + } else { + k = len + n; + if (k < 0) { + k = 0; + } + } + + while (k < len) { + ScopedValue val(scope, v->get(k)); + if (val->sameValueZero(argv[0])) { + return Encode(true); + } + k++; + } + + return Encode(false); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + if (!len) + return Encode(-1); + + ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue()); + uint fromIndex = 0; + + if (argc >= 2) { + double f = argv[1].toInteger(); + CHECK_EXCEPTION(); + if (f >= len) + return Encode(-1); + if (f < 0) + f = qMax(len + f, 0.); + fromIndex = (uint) f; + } + + if (v->isStringObject()) { + ScopedValue value(scope); + for (uint k = fromIndex; k < len; ++k) { + bool exists; + value = v->get(k, &exists); + if (exists && RuntimeHelpers::strictEqual(value, searchValue)) + return Encode(k); + } + return Encode(-1); + } + + ScopedValue value(scope); + + for (uint i = fromIndex; i < len; ++i) { + bool exists; + value = v->get(i, &exists); + CHECK_EXCEPTION(); + if (exists && RuntimeHelpers::strictEqual(value, searchValue)) + return Encode(i); + } + return Encode(-1); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_join(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + ScopedValue arg(scope, argc ? argv[0] : Value::undefinedValue()); + + QString r4; + if (arg->isUndefined()) + r4 = QStringLiteral(","); + else + r4 = arg->toQString(); + + const quint32 r2 = len; + + if (!r2) + return Encode(scope.engine->newString()); + + QString R; + + // + // crazy! + // + ScopedString name(scope, scope.engine->newString(QStringLiteral("0"))); + ScopedValue r6(scope, v->get(name)); + if (!r6->isNullOrUndefined()) + R = r6->toQString(); + + ScopedValue r12(scope); + for (quint32 k = 1; k < r2; ++k) { + R += r4; + + name = Value::fromDouble(k).toString(scope.engine); + r12 = v->get(name); + CHECK_EXCEPTION(); + + if (!r12->isNullOrUndefined()) + R += r12->toQString(); + } + + return Encode(scope.engine->newString(R)); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_keys(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); + ao->d()->iterationKind = IteratorKind::KeyIteratorKind; + return ao->asReturnedValue(); +} + + +ReturnedValue IntrinsicTypedArrayPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + if (!len) + return Encode(-1); + + ScopedValue searchValue(scope); + uint fromIndex = len; + + if (argc >= 1) + searchValue = argv[0]; + else + searchValue = Value::undefinedValue(); + + if (argc >= 2) { + double f = argv[1].toInteger(); + CHECK_EXCEPTION(); + if (f > 0) + f = qMin(f, (double)(len - 1)); + else if (f < 0) { + f = len + f; + if (f < 0) + return Encode(-1); + } + fromIndex = (uint) f + 1; + } + + ScopedValue value(scope); + for (uint k = fromIndex; k > 0;) { + --k; + bool exists; + value = instance->get(k, &exists); + if (exists && RuntimeHelpers::strictEqual(value, searchValue)) + return Encode(k); + } + return Encode(-1); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_map(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + TypedArray *a = typedArraySpeciesCreate(scope, instance, len); if (!a) + return Encode::undefined(); + + ScopedValue v(scope); + ScopedValue mapped(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); + + for (uint k = 0; k < len; ++k) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + arguments[0] = instance->get(k); + + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + mapped = callback->call(that, arguments, 3); + a->put(k, mapped); + } + return a->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_reduce(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + uint k = 0; + ScopedValue acc(scope); + ScopedValue v(scope); + + if (argc > 1) { + acc = argv[1]; + } else { + bool kPresent = false; + while (k < len && !kPresent) { + v = instance->get(k, &kPresent); + if (kPresent) + acc = v; + ++k; + } + if (!kPresent) + THROW_TYPE_ERROR(); + } + + Value *arguments = scope.alloc(4); + + while (k < len) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool kPresent; + v = instance->get(k, &kPresent); + if (kPresent) { + arguments[0] = acc; + arguments[1] = v; + arguments[2] = Value::fromDouble(k); + arguments[3] = instance; + acc = callback->call(nullptr, arguments, 4); + } + ++k; + } + return acc->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + if (len == 0) { + if (argc == 1) + THROW_TYPE_ERROR(); + return argv[1].asReturnedValue(); + } + + uint k = len; + ScopedValue acc(scope); + ScopedValue v(scope); + if (argc > 1) { + acc = argv[1]; + } else { + bool kPresent = false; + while (k > 0 && !kPresent) { + v = instance->get(k - 1, &kPresent); + if (kPresent) + acc = v; + --k; + } + if (!kPresent) + THROW_TYPE_ERROR(); + } + + Value *arguments = scope.alloc(4); + + while (k > 0) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool kPresent; + v = instance->get(k - 1, &kPresent); + if (kPresent) { + arguments[0] = acc; + arguments[1] = v; + arguments[2] = Value::fromDouble(k - 1); + arguments[3] = instance; + acc = callback->call(nullptr, arguments, 4); + } + --k; + } + return acc->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_reverse(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint length = instance->length(); + + int lo = 0, hi = length - 1; + + ScopedValue lval(scope); + ScopedValue hval(scope); + for (; lo < hi; ++lo, --hi) { + bool loExists, hiExists; + lval = instance->get(lo, &loExists); + hval = instance->get(hi, &hiExists); + Q_ASSERT(hiExists && loExists); + bool ok; + ok = instance->put(lo, hval); + Q_ASSERT(ok); + ok = instance->put(hi, lval); + Q_ASSERT(ok); + } + return instance->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_some(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + ScopedValue result(scope); + Value *arguments = scope.alloc(3); + + for (uint k = 0; k < len; ++k) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool exists; + arguments[0] = instance->get(k, &exists); + if (!exists) + continue; + + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + result = callback->call(that, arguments, 3); + if (result->toBoolean()) + return Encode(true); + } + return Encode(false); +} + + +ReturnedValue IntrinsicTypedArrayPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); + ao->d()->iterationKind = IteratorKind::ValueIteratorKind; + return ao->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> a(scope, *thisObject); + if (!a) + return scope.engine->throwTypeError(); Scoped<ArrayBuffer> buffer(scope, a->d()->buffer); - if (!buffer) - scope.engine->throwTypeError(); - double doffset = callData->argc >= 2 ? callData->args[1].toInteger() : 0; + double doffset = argc >= 2 ? argv[1].toInteger() : 0; if (scope.engine->hasException) RETURN_UNDEFINED(); + if (!buffer || buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); if (doffset < 0 || doffset >= UINT_MAX) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); uint offset = (uint)doffset; uint elementSize = a->d()->type->bytesPerElement; - Scoped<TypedArray> srcTypedArray(scope, callData->args[0]); + Scoped<TypedArray> srcTypedArray(scope, argv[0]); if (!srcTypedArray) { // src is a regular object - ScopedObject o(scope, callData->args[0].toObject(scope.engine)); + ScopedObject o(scope, argv[0].toObject(scope.engine)); if (scope.engine->hasException || !o) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); double len = ScopedValue(scope, o->get(scope.engine->id_length()))->toNumber(); uint l = (uint)len; if (scope.engine->hasException || l != len) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); if (offset + l > a->length()) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); uint idx = 0; + if (buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); char *b = buffer->d()->data->data() + a->d()->byteOffset + offset*elementSize; ScopedValue val(scope); while (idx < l) { - val = o->getIndexed(idx); - a->d()->type->write(scope.engine, b, 0, val); + val = o->get(idx); + if (scope.hasException()) + return Encode::undefined(); + val = val->convertedToNumber(); + if (scope.hasException() || buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + a->d()->type->write(b, val); if (scope.engine->hasException) RETURN_UNDEFINED(); ++idx; @@ -520,8 +1444,8 @@ void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, Call // src is a typed array Scoped<ArrayBuffer> srcBuffer(scope, srcTypedArray->d()->buffer); - if (!srcBuffer) - THROW_TYPE_ERROR(); + if (!srcBuffer || srcBuffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); uint l = srcTypedArray->length(); if (offset + l > a->length()) @@ -535,7 +1459,7 @@ void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, Call RETURN_UNDEFINED(); } - char *srcCopy = 0; + char *srcCopy = nullptr; if (buffer->d() == srcBuffer->d()) { // same buffer, need to take a temporary copy, to not run into problems srcCopy = new char[srcTypedArray->d()->byteLength]; @@ -545,12 +1469,12 @@ void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, Call // typed arrays of different kind, need to manually loop uint srcElementSize = srcTypedArray->d()->type->bytesPerElement; - TypedArrayRead read = srcTypedArray->d()->type->read; - TypedArrayWrite write = a->d()->type->write; + TypedArrayOperations::Read read = srcTypedArray->d()->type->read; + TypedArrayOperations::Write write = a->d()->type->write; for (uint i = 0; i < l; ++i) { - Primitive val; - val.setRawValue(read(src, i*srcElementSize)); - write(scope.engine, dest, i*elementSize, val); + Value val; + val.setRawValue(read(src + i*srcElementSize)); + write(dest + i*elementSize, val); } if (srcCopy) @@ -559,24 +1483,71 @@ void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, Call RETURN_UNDEFINED(); } -void TypedArrayPrototype::method_subarray(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<TypedArray> a(scope, callData->thisObject); + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + double s = (argc ? argv[0] : Value::undefinedValue()).toInteger(); + uint start; + if (s < 0) + start = (uint)qMax(len + s, 0.); + else if (s > len) + start = len; + else + start = (uint) s; + uint end = len; + if (argc > 1 && !argv[1].isUndefined()) { + double e = argv[1].toInteger(); + if (e < 0) + end = (uint)qMax(len + e, 0.); + else if (e > len) + end = len; + else + end = (uint) e; + } + uint count = start > end ? 0 : end - start; + TypedArray *a = typedArraySpeciesCreate(scope, instance, count); if (!a) - THROW_TYPE_ERROR(); + return Encode::undefined(); + + ScopedValue v(scope); + uint n = 0; + for (uint i = start; i < end; ++i) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + v = instance->get(i); + if (a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + a->put(n, v); + ++n; + } + return a->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_subarray(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(builtin); + Scoped<TypedArray> a(scope, *thisObject); + + if (!a) + return scope.engine->throwTypeError(); Scoped<ArrayBuffer> buffer(scope, a->d()->buffer); - if (!buffer) - THROW_TYPE_ERROR(); + Q_ASSERT(buffer); int len = a->length(); - double b = callData->argc > 0 ? callData->args[0].toInteger() : 0; + double b = argc > 0 ? argv[0].toInteger() : 0; if (b < 0) b = len + b; uint begin = (uint)qBound(0., b, (double)len); - double e = callData->argc < 2 || callData->args[1].isUndefined() ? len : callData->args[1].toInteger(); + double e = argc < 2 || argv[1].isUndefined() ? len : argv[1].toInteger(); if (e < 0) e = len + e; uint end = (uint)qBound(0., e, (double)len); @@ -588,13 +1559,142 @@ void TypedArrayPrototype::method_subarray(const BuiltinFunction *, Scope &scope, int newLen = end - begin; - ScopedFunctionObject constructor(scope, a->get(scope.engine->id_constructor())); + ScopedFunctionObject constructor(scope, a->speciesConstructor(scope, scope.engine->typedArrayCtors + a->d()->arrayType)); if (!constructor) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); + + Value *arguments = scope.alloc(3); + arguments[0] = buffer; + arguments[1] = Encode(a->d()->byteOffset + begin*a->d()->type->bytesPerElement); + arguments[2] = Encode(newLen); + a = constructor->callAsConstructor(arguments, 3); + if (!a || a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + return a->asReturnedValue(); +} - ScopedCallData cData(scope, 3); - cData->args[0] = buffer; - cData->args[1] = Encode(a->d()->byteOffset + begin*a->d()->type->bytesPerElement); - cData->args[2] = Encode(newLen); - constructor->construct(scope, cData); +ReturnedValue IntrinsicTypedArrayPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + const QString separator = QStringLiteral(","); + + QString R; + + ScopedValue v(scope); + ScopedString s(scope); + + for (uint k = 0; k < len; ++k) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + if (k) + R += separator; + + v = instance->get(k); + v = Runtime::CallElement::call(scope.engine, v, *scope.engine->id_toLocaleString(), nullptr, 0); + s = v->toString(scope.engine); + if (scope.hasException()) + return Encode::undefined(); + + R += s->toQString(); + } + return scope.engine->newString(R)->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_get_toStringTag(const FunctionObject *, const Value *thisObject, const Value *, int) +{ + const TypedArray *a = thisObject->as<TypedArray>(); + if (!a) + return Encode::undefined(); + + return a->engine()->newString(QString::fromLatin1(a->d()->type->name))->asReturnedValue(); +} + +static bool validateTypedArray(const Object *o) +{ + const TypedArray *a = o->as<TypedArray>(); + if (!a) + return false; + if (a->d()->buffer->isDetachedBuffer()) + return false; + return true; +} + +ReturnedValue IntrinsicTypedArrayCtor::method_of(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + int len = argc; + const Value *items = argv; + const FunctionObject *C = thisObject->as<FunctionObject>(); + if (!C || !C->isConstructor()) + return scope.engine->throwTypeError(); + + Value lenValue = Value::fromInt32(len); + ScopedObject newObj(scope, C->callAsConstructor(&lenValue, 1)); + if (scope.hasException()) + return Encode::undefined(); + if (!::validateTypedArray(newObj)) + return scope.engine->throwTypeError(); + TypedArray *a = newObj->as<TypedArray>(); + Q_ASSERT(a); + if (a->length() < static_cast<uint>(len)) + return scope.engine->throwTypeError(); + + for (int k = 0; k < len; ++k) { + newObj->put(PropertyKey::fromArrayIndex(k), items[k]); + } + return newObj->asReturnedValue(); +} + +void IntrinsicTypedArrayPrototype::init(ExecutionEngine *engine, IntrinsicTypedArrayCtor *ctor) +{ + Scope scope(engine); + ctor->defineReadonlyProperty(engine->id_prototype(), *this); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0)); + ScopedString s(scope, engine->newString(QStringLiteral("TypedArray"))); + ctor->defineReadonlyConfigurableProperty(engine->id_name(), s); + s = scope.engine->newString(QStringLiteral("of")); + ctor->defineDefaultProperty(s, IntrinsicTypedArrayCtor::method_of); + ctor->addSymbolSpecies(); + + defineAccessorProperty(QStringLiteral("buffer"), method_get_buffer, nullptr); + defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, nullptr); + defineAccessorProperty(QStringLiteral("byteOffset"), method_get_byteOffset, nullptr); + defineAccessorProperty(QStringLiteral("length"), method_get_length, nullptr); + + defineDefaultProperty(QStringLiteral("copyWithin"), method_copyWithin, 2); + defineDefaultProperty(QStringLiteral("entries"), method_entries, 0); + defineDefaultProperty(QStringLiteral("every"), method_every, 1); + defineDefaultProperty(QStringLiteral("fill"), method_fill, 1); + defineDefaultProperty(QStringLiteral("filter"), method_filter, 1); + defineDefaultProperty(QStringLiteral("find"), method_find, 1); + defineDefaultProperty(QStringLiteral("findIndex"), method_findIndex, 1); + defineDefaultProperty(QStringLiteral("forEach"), method_forEach, 1); + defineDefaultProperty(QStringLiteral("includes"), method_includes, 1); + defineDefaultProperty(QStringLiteral("indexOf"), method_indexOf, 1); + defineDefaultProperty(QStringLiteral("join"), method_join, 1); + defineDefaultProperty(QStringLiteral("keys"), method_keys, 0); + defineDefaultProperty(QStringLiteral("lastIndexOf"), method_lastIndexOf, 1); + defineDefaultProperty(QStringLiteral("map"), method_map, 1); + defineDefaultProperty(QStringLiteral("reduce"), method_reduce, 1); + defineDefaultProperty(QStringLiteral("reduceRight"), method_reduceRight, 1); + defineDefaultProperty(QStringLiteral("reverse"), method_reverse, 0); + defineDefaultProperty(QStringLiteral("some"), method_some, 1); + defineDefaultProperty(QStringLiteral("set"), method_set, 1); + defineDefaultProperty(QStringLiteral("slice"), method_slice, 2); + defineDefaultProperty(QStringLiteral("subarray"), method_subarray, 2); + defineDefaultProperty(engine->id_toLocaleString(), method_toLocaleString, 0); + ScopedObject f(scope, engine->arrayPrototype()->get(engine->id_toString())); + defineDefaultProperty(engine->id_toString(), f); + + ScopedString valuesString(scope, engine->newIdentifier(QStringLiteral("values"))); + ScopedObject values(scope, FunctionObject::createBuiltinFunction(engine, valuesString, method_values, 0)); + defineDefaultProperty(QStringLiteral("values"), values); + defineDefaultProperty(engine->symbol_iterator(), values); + + defineAccessorProperty(engine->symbol_toStringTag(), method_get_toStringTag, nullptr); } diff --git a/src/qml/jsruntime/qv4typedarray_p.h b/src/qml/jsruntime/qv4typedarray_p.h index a472dfa607..64792f23a2 100644 --- a/src/qml/jsruntime/qv4typedarray_p.h +++ b/src/qml/jsruntime/qv4typedarray_p.h @@ -60,14 +60,50 @@ namespace QV4 { struct ArrayBuffer; -typedef ReturnedValue (*TypedArrayRead)(const char *data, int index); -typedef void (*TypedArrayWrite)(ExecutionEngine *engine, char *data, int index, const Value &value); +enum TypedArrayType { + Int8Array, + UInt8Array, + Int16Array, + UInt16Array, + Int32Array, + UInt32Array, + UInt8ClampedArray, + Float32Array, + Float64Array, + NTypedArrayTypes +}; + +enum AtomicModifyOps { + AtomicAdd, + AtomicAnd, + AtomicExchange, + AtomicOr, + AtomicSub, + AtomicXor, + NAtomicModifyOps +}; struct TypedArrayOperations { + typedef ReturnedValue (*Read)(const char *data); + typedef void (*Write)(char *data, Value value); + typedef ReturnedValue (*AtomicModify)(char *data, Value value); + typedef ReturnedValue (*AtomicCompareExchange)(char *data, Value expected, Value v); + typedef ReturnedValue (*AtomicLoad)(char *data); + typedef ReturnedValue (*AtomicStore)(char *data, Value value); + + template<typename T> + static constexpr TypedArrayOperations create(const char *name); + template<typename T> + static constexpr TypedArrayOperations createWithAtomics(const char *name); + int bytesPerElement; const char *name; - TypedArrayRead read; - TypedArrayWrite write; + Read read; + Write write; + AtomicModify atomicModifyOps[AtomicModifyOps::NAtomicModifyOps]; + AtomicCompareExchange atomicCompareExchange; + AtomicLoad atomicLoad; + AtomicStore atomicStore; }; namespace Heap { @@ -80,29 +116,24 @@ namespace Heap { Member(class, NoMark, uint, arrayType) DECLARE_HEAP_OBJECT(TypedArray, Object) { - DECLARE_MARK_TABLE(TypedArray); - enum Type { - Int8Array, - UInt8Array, - UInt8ClampedArray, - Int16Array, - UInt16Array, - Int32Array, - UInt32Array, - Float32Array, - Float64Array, - NTypes - }; + DECLARE_MARKOBJECTS(TypedArray); + using Type = TypedArrayType; void init(Type t); }; +struct IntrinsicTypedArrayCtor : FunctionObject { +}; + struct TypedArrayCtor : FunctionObject { void init(QV4::ExecutionContext *scope, TypedArray::Type t); TypedArray::Type type; }; +struct IntrinsicTypedArrayPrototype : Object { +}; + struct TypedArrayPrototype : Object { inline void init(TypedArray::Type t); TypedArray::Type type; @@ -132,19 +163,73 @@ struct Q_QML_PRIVATE_EXPORT TypedArray : Object Heap::TypedArray::Type arrayType() const { return static_cast<Heap::TypedArray::Type>(d()->arrayType); } + using Object::get; + + static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static bool virtualHasProperty(const Managed *m, PropertyKey id); + static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); + static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); + static bool virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs); + static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); - static ReturnedValue getIndexed(const Managed *m, uint index, bool *hasProperty); - static bool putIndexed(Managed *m, uint index, const Value &value); +}; + +struct IntrinsicTypedArrayCtor: FunctionObject +{ + V4_OBJECT2(IntrinsicTypedArrayCtor, FunctionObject) + + static constexpr VTable::Call virtualCall = nullptr; + + static ReturnedValue method_of(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; struct TypedArrayCtor: FunctionObject { V4_OBJECT2(TypedArrayCtor, FunctionObject) - static void construct(const Managed *m, Scope &scope, CallData *callData); - static void call(const Managed *that, Scope &scope, CallData *callData); + static ReturnedValue virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *); + static ReturnedValue virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); }; +struct IntrinsicTypedArrayPrototype : Object +{ + V4_OBJECT2(IntrinsicTypedArrayPrototype, Object) + V4_PROTOTYPE(objectPrototype) + + void init(ExecutionEngine *engine, IntrinsicTypedArrayCtor *ctor); + + static ReturnedValue method_get_buffer(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_byteLength(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_byteOffset(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_get_length(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_copyWithin(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_entries(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_every(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_fill(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_filter(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_find(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_findIndex(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_forEach(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_includes(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_indexOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_join(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_keys(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_lastIndexOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_map(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_reduce(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_reduceRight(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_reverse(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_some(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_values(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_set(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_slice(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_subarray(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toLocaleString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + + static ReturnedValue method_get_toStringTag(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + +}; struct TypedArrayPrototype : Object { @@ -152,14 +237,6 @@ struct TypedArrayPrototype : Object V4_PROTOTYPE(objectPrototype) void init(ExecutionEngine *engine, TypedArrayCtor *ctor); - - static void method_get_buffer(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_byteLength(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_byteOffset(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_get_length(const BuiltinFunction *, Scope &scope, CallData *callData); - - static void method_set(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_subarray(const BuiltinFunction *, Scope &scope, CallData *callData); }; inline void diff --git a/src/qml/jsruntime/qv4util_p.h b/src/qml/jsruntime/qv4util_p.h index 2669a3e4bf..073832937d 100644 --- a/src/qml/jsruntime/qv4util_p.h +++ b/src/qml/jsruntime/qv4util_p.h @@ -59,26 +59,6 @@ QT_BEGIN_NAMESPACE namespace QV4 { -template <typename T> -struct TemporaryAssignment -{ - TemporaryAssignment(T &var, const T& temporaryValue) - : variable(var) - , savedValue(var) - { - variable = temporaryValue; - } - ~TemporaryAssignment() - { - variable = savedValue; - } - T &variable; - T savedValue; -private: - TemporaryAssignment(const TemporaryAssignment<T>&); - TemporaryAssignment operator=(const TemporaryAssignment<T>&); -}; - #if !defined(BROKEN_STD_VECTOR_BOOL_OR_BROKEN_STD_FIND) // Sanity: class BitVector diff --git a/src/qml/jsruntime/qv4value.cpp b/src/qml/jsruntime/qv4value.cpp index f41442df7a..cbc153bb86 100644 --- a/src/qml/jsruntime/qv4value.cpp +++ b/src/qml/jsruntime/qv4value.cpp @@ -39,7 +39,9 @@ #include <qv4engine_p.h> #include <qv4runtime_p.h> #include <qv4string_p.h> +#include <qv4propertykey_p.h> #ifndef V4_BOOTSTRAP +#include <qv4symbol_p.h> #include <qv4object_p.h> #include <qv4objectproto_p.h> #include <private/qv4mm_p.h> @@ -75,39 +77,29 @@ int Value::toUInt16() const return (unsigned short)number; } -bool Value::toBoolean() const +bool Value::toBooleanImpl(Value val) { - if (isInteger() || isBoolean()) - return static_cast<bool>(int_32()); - - if (isUndefined() || isNull()) - return false; - - if (isManaged()) { + if (val.isManagedOrUndefined()) { + Heap::Base *b = val.m(); + if (!b) + return false; #ifdef V4_BOOTSTRAP Q_UNIMPLEMENTED(); #else - if (String *s = stringValue()) - return s->toQString().length() > 0; + if (b->internalClass->vtable->isString) + return static_cast<Heap::String *>(b)->length() > 0; #endif return true; } // double - return doubleValue() && !std::isnan(doubleValue()); -} - -double Value::toInteger() const -{ - if (integerCompatible()) - return int_32(); - - return Primitive::toInteger(toNumber()); + double d = val.doubleValue(); + return d && !std::isnan(d); } -double Value::toNumberImpl() const +double Value::toNumberImpl(Value val) { - switch (type()) { + switch (val.type()) { case QV4::Value::Undefined_Type: return std::numeric_limits<double>::quiet_NaN(); case QV4::Value::Managed_Type: @@ -115,12 +107,18 @@ double Value::toNumberImpl() const Q_UNIMPLEMENTED(); Q_FALLTHROUGH(); #else - if (String *s = stringValue()) + if (String *s = val.stringValue()) return RuntimeHelpers::stringToNumber(s->toQString()); + if (val.isSymbol()) { + Managed &m = static_cast<Managed &>(val); + m.engine()->throwTypeError(); + return 0; + } { - Q_ASSERT(isObject()); - Scope scope(objectValue()->engine()); - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, NUMBER_HINT)); + Q_ASSERT(val.isObject()); + Scope scope(val.objectValue()->engine()); + ScopedValue protectThis(scope, val); + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(val, NUMBER_HINT)); if (scope.engine->hasException) return 0; return prim->toNumber(); @@ -129,7 +127,7 @@ double Value::toNumberImpl() const case QV4::Value::Null_Type: case QV4::Value::Boolean_Type: case QV4::Value::Integer_Type: - return int_32(); + return val.int_32(); default: // double Q_UNREACHABLE(); } @@ -154,6 +152,8 @@ QString Value::toQStringNoThrow() const case Value::Managed_Type: if (String *s = stringValue()) return s->toQString(); + if (Symbol *s = symbolValue()) + return s->descriptiveString(); { Q_ASSERT(isObject()); Scope scope(objectValue()->engine()); @@ -206,9 +206,12 @@ QString Value::toQString() const else return QStringLiteral("false"); case Value::Managed_Type: - if (String *s = stringValue()) + if (String *s = stringValue()) { return s->toQString(); - { + } else if (isSymbol()) { + static_cast<const Managed *>(this)->engine()->throwTypeError(); + return QString(); + } else { Q_ASSERT(isObject()); Scope scope(objectValue()->engine()); ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); @@ -226,6 +229,25 @@ QString Value::toQString() const } } // switch } + +QV4::PropertyKey Value::toPropertyKey(ExecutionEngine *e) const +{ + if (isInteger() && int_32() >= 0) + return PropertyKey::fromArrayIndex(static_cast<uint>(int_32())); + if (isStringOrSymbol()) { + Scope scope(e); + ScopedStringOrSymbol s(scope, this); + return s->toPropertyKey(); + } + Scope scope(e); + ScopedValue v(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + if (!v->isStringOrSymbol()) + v = v->toString(e); + if (e->hasException) + return PropertyKey::invalid(); + ScopedStringOrSymbol s(scope, v); + return s->toPropertyKey(); +} #endif // V4_BOOTSTRAP bool Value::sameValue(Value other) const { @@ -236,83 +258,42 @@ bool Value::sameValue(Value other) const { if (s && os) return s->isEqualTo(os); if (isInteger() && other.isDouble()) - return int_32() ? (double(int_32()) == other.doubleValue()) : (other._val == 0); + return int_32() ? (double(int_32()) == other.doubleValue()) + : (other.doubleValue() == 0 && !std::signbit(other.doubleValue())); if (isDouble() && other.isInteger()) - return other.int_32() ? (doubleValue() == double(other.int_32())) : (_val == 0); + return other.int_32() ? (doubleValue() == double(other.int_32())) + : (doubleValue() == 0 && !std::signbit(doubleValue())); return false; } - -int Primitive::toInt32(double number) -{ - const double D32 = 4294967296.0; - const double D31 = D32 / 2.0; - - if ((number >= -D31 && number < D31)) - return static_cast<int>(number); - - - if (!std::isfinite(number)) - return 0; - - double d = ::floor(::fabs(number)); - if (std::signbit(number)) - d = -d; - - number = ::fmod(d , D32); - - if (number < -D31) - number += D32; - else if (number >= D31) - number -= D32; - - return int(number); -} - -unsigned int Primitive::toUInt32(double number) -{ - const double D32 = 4294967296.0; - if ((number >= 0 && number < D32)) - return static_cast<uint>(number); - - if (!std::isfinite(number)) - return +0; - - double d = ::floor(::fabs(number)); - if (std::signbit(number)) - d = -d; - - number = ::fmod(d , D32); - - if (number < 0) - number += D32; - - return unsigned(number); -} - -double Primitive::toInteger(double number) -{ - if (std::isnan(number)) - return +0; - else if (! number || std::isinf(number)) - return number; - const double v = floor(fabs(number)); - return std::signbit(number) ? -v : v; +bool Value::sameValueZero(Value other) const { + if (_val == other._val) + return true; + String *s = stringValue(); + String *os = other.stringValue(); + if (s && os) + return s->isEqualTo(os); + if (isInteger() && other.isDouble()) + return double(int_32()) == other.doubleValue(); + if (isDouble() && other.isInteger()) + return other.int_32() == doubleValue(); + if (isDouble() && other.isDouble()) { + if (doubleValue() == 0 && other.doubleValue() == 0) { + return true; + } + } + return false; } #ifndef V4_BOOTSTRAP -Heap::String *Value::toString(ExecutionEngine *e) const +Heap::String *Value::toString(ExecutionEngine *e, Value val) { - if (String *s = stringValue()) - return s->d(); - return RuntimeHelpers::convertToString(e, *this); + return RuntimeHelpers::convertToString(e, val); } -Heap::Object *Value::toObject(ExecutionEngine *e) const +Heap::Object *Value::toObject(ExecutionEngine *e, Value val) { - if (Object *o = objectValue()) - return o->d(); - return RuntimeHelpers::convertToObject(e, *this); + return RuntimeHelpers::convertToObject(e, val); } uint Value::asArrayLength(bool *ok) const diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index 50cecb6598..b1cae8796f 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -51,14 +51,14 @@ // #include <limits.h> +#include <cmath> #include <QtCore/QString> #include "qv4global_p.h" #include <private/qv4heap_p.h> +#include <private/qv4internalclass_p.h> -#if QT_POINTER_SIZE == 8 -#define QV4_USE_64_BIT_VALUE_ENCODING -#endif +#include <private/qnumeric_p.h> QT_BEGIN_NAMESPACE @@ -70,11 +70,8 @@ namespace Heap { struct Q_QML_PRIVATE_EXPORT Value { -private: /* - We use two different ways of encoding JS values. One for 32bit and one for 64bit systems. - - In both cases, we use 8 bytes for a value and a different variant of NaN boxing. A Double + We use 8 bytes for a value and a different variant of NaN boxing. A Double NaN (actually -qNaN) is indicated by a number that has the top 13 bits set, and for a signalling NaN it is the top 14 bits. The other values are usually set to 0 by the processor, and are thus free for us to store other data. We keep pointers in there for @@ -83,18 +80,14 @@ private: only have 48 bits of addressable memory. (Note: we do leave the lower 49 bits available for pointers.) - On 32bit, we store doubles as doubles. All other values, have the high 32bits set to a value - that will make the number a NaN. The Masks below are used for encoding the other types. - - On 64 bit, we xor Doubles with (0xffff8000 << 32). That has the effect that no doubles will + We xor Doubles with (0xffff8000 << 32). That has the effect that no doubles will get encoded with bits 63-49 all set to 0. We then use bit 48 to distinguish between managed/undefined (0), or Null/Int/Bool/Empty (1). So, storing a 49 bit pointer will leave the top 15 bits 0, which is exactly the 'natural' representation of pointers. If bit 49 is set, bit 48 indicates Empty (0) or integer-convertible (1). Then the 3 bit below that are used to encode Null/Int/Bool. - On both 32bit and 64bit, Undefined is encoded as a managed pointer with value 0. This is - the same as a nullptr. + Undefined is encoded as a managed pointer with value 0. This is the same as a nullptr. Specific bit-sequences: 0 = always 0 @@ -102,8 +95,6 @@ private: x = stored value a,b,c,d = specific bit values, see notes - 64bit: - 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210 | 66665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000 | JS Value ------------------------------------------------------------------------+-------------- @@ -112,9 +103,9 @@ private: a0000000 0000bc00 00000000 00000000 00000000 00000000 00000000 00000000 | NaN/Inf dddddddd ddddddxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | double 00000000 00000010 00000000 00000000 00000000 00000000 00000000 00000000 | empty (non-sparse array hole) - 00000000 00000011 10000000 00000000 00000000 00000000 00000000 00000000 | Null - 00000000 00000011 01000000 00000000 00000000 00000000 00000000 0000000x | Bool - 00000000 00000011 00100000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int + 00000000 00000010 10000000 00000000 00000000 00000000 00000000 00000000 | Null + 00000000 00000011 00000000 00000000 00000000 00000000 00000000 0000000x | Bool + 00000000 00000011 10000000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int Notes: - a: xor-ed signbit, always 1 for NaN @@ -128,39 +119,15 @@ private: - Null, Bool, and Int have bit 48 set, indicating integer-convertible - xoring _val with NaNEncodeMask will convert to a double in "natural" representation, where any non double results in a NaN - - 32bit: - - 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210 | - 66665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000 | JS Value - ------------------------------------------------------------------------+-------------- - 01111111 11111100 00000000 00000000 00000000 00000000 00000000 00000000 | Undefined - 01111111 11111100 00000000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Managed (heap pointer) - a1111111 1111bc00 00000000 00000000 00000000 00000000 00000000 00000000 | NaN/Inf - xddddddd ddddddxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | double - 01111111 11111110 00000000 00000000 00000000 00000000 00000000 00000000 | empty (non-sparse array hole) - 01111111 11111111 10000000 00000000 00000000 00000000 00000000 00000000 | Null - 01111111 11111111 01000000 00000000 00000000 00000000 00000000 0000000x | Bool - 01111111 11111111 00100000 00000000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | Int - - Notes: - - the upper 32 bits are the tag, the lower 32 bits the value - - Undefined has a nullptr in the value, Managed has a non-nullptr stored in the value - - a: sign bit, always 0 for NaN - - b,c: 00=inf, 01 = sNaN, 10 = qNaN, 11 = boxed value - - d: stored double value, as long as not *all* of them are 1, because that's a boxed value - (see above) - - empty, Null, Bool, and Int have bit 63 set to 0, bits 62-50 set to 1 (same as undefined - and managed), and bit 49 set to 1 (where undefined and managed have it set to 0) - - Null, Bool, and Int have bit 48 set, indicating integer-convertible + - on 32bit we can use the fact that addresses are 32bits wide, so the tag part (bits 32 to + 63) are zero. No need to shift. */ quint64 _val; -public: - QML_NEARLY_ALWAYS_INLINE quint64 &rawValueRef() { return _val; } - QML_NEARLY_ALWAYS_INLINE quint64 rawValue() const { return _val; } - QML_NEARLY_ALWAYS_INLINE void setRawValue(quint64 raw) { _val = raw; } + QML_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR quint64 &rawValueRef() { return _val; } + QML_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR quint64 rawValue() const { return _val; } + QML_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setRawValue(quint64 raw) { _val = raw; } #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN static inline int valueOffset() { return 0; } @@ -169,11 +136,13 @@ public: static inline int valueOffset() { return 4; } static inline int tagOffset() { return 0; } #endif - QML_NEARLY_ALWAYS_INLINE void setTagValue(quint32 tag, quint32 value) { _val = quint64(tag) << 32 | value; } - QML_NEARLY_ALWAYS_INLINE quint32 value() const { return _val & quint64(~quint32(0)); } - QML_NEARLY_ALWAYS_INLINE quint32 tag() const { return _val >> 32; } + static inline constexpr quint64 tagValue(quint32 tag, quint32 value) { return quint64(tag) << 32 | value; } + QML_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setTagValue(quint32 tag, quint32 value) { _val = quint64(tag) << 32 | value; } + QML_NEARLY_ALWAYS_INLINE constexpr quint32 value() const { return _val & quint64(~quint32(0)); } + QML_NEARLY_ALWAYS_INLINE constexpr quint32 tag() const { return _val >> 32; } + QML_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setTag(quint32 tag) { setTagValue(tag, value()); } -#if defined(QV4_USE_64_BIT_VALUE_ENCODING) +#if QT_POINTER_SIZE == 8 QML_NEARLY_ALWAYS_INLINE Heap::Base *m() const { Heap::Base *b; @@ -184,7 +153,7 @@ public: { memcpy(&_val, &b, 8); } -#else // !QV4_USE_64_BIT_VALUE_ENCODING +#elif QT_POINTER_SIZE == 4 QML_NEARLY_ALWAYS_INLINE Heap::Base *m() const { Q_STATIC_ASSERT(sizeof(Heap::Base*) == sizeof(quint32)); @@ -199,57 +168,56 @@ public: memcpy(&v, &b, 4); setTagValue(Managed_Type_Internal, v); } +#else +# error "unsupported pointer size" #endif - QML_NEARLY_ALWAYS_INLINE int int_32() const + QML_NEARLY_ALWAYS_INLINE constexpr int int_32() const { return int(value()); } - QML_NEARLY_ALWAYS_INLINE void setInt_32(int i) + QML_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setInt_32(int i) { setTagValue(quint32(ValueTypeInternal::Integer), quint32(i)); } QML_NEARLY_ALWAYS_INLINE uint uint_32() const { return value(); } - QML_NEARLY_ALWAYS_INLINE void setEmpty() + QML_NEARLY_ALWAYS_INLINE Q_DECL_RELAXED_CONSTEXPR void setEmpty() { - setTagValue(quint32(ValueTypeInternal::Empty), value()); - } - - QML_NEARLY_ALWAYS_INLINE void setEmpty(int i) - { - setTagValue(quint32(ValueTypeInternal::Empty), quint32(i)); - } - - QML_NEARLY_ALWAYS_INLINE void setEmpty(quint32 i) - { - setTagValue(quint32(ValueTypeInternal::Empty), i); - } - - QML_NEARLY_ALWAYS_INLINE quint32 emptyValue() - { - Q_ASSERT(isEmpty()); - return quint32(value()); - } + setTagValue(quint32(ValueTypeInternal::Empty), 0); + } + + // ### Fix for 32 bit (easiest solution is to set highest bit to 1 for mananged/undefined/integercompatible + // and use negative numbers here + enum QuickType { + QT_ManagedOrUndefined = 0, + QT_ManagedOrUndefined1 = 1, + QT_ManagedOrUndefined2 = 2, + QT_ManagedOrUndefined3 = 3, + QT_Empty = 4, + QT_Null = 5, + QT_Bool = 6, + QT_Int = 7 + // all other values are doubles + }; enum Type { - Undefined_Type, - Managed_Type, - Empty_Type, - Integer_Type, - Boolean_Type, - Null_Type, - Double_Type + Undefined_Type = 0, + Managed_Type = 1, + Empty_Type = 4, + Null_Type = 5, + Boolean_Type = 6, + Integer_Type = 7, + Double_Type = 8 }; inline Type type() const { - if (isUndefined()) return Undefined_Type; - if (isManaged()) return Managed_Type; - if (isEmpty()) return Empty_Type; - if (isInteger()) return Integer_Type; - if (isBoolean()) return Boolean_Type; - if (isNull()) return Null_Type; - Q_ASSERT(isDouble()); return Double_Type; + int t = quickType(); + if (t < QT_Empty) + return _val ? Managed_Type : Undefined_Type; + if (t > QT_Int) + return Double_Type; + return static_cast<Type>(t); } // Shared between 32-bit and 64-bit encoding @@ -258,23 +226,23 @@ public: }; // Used only by 64-bit encoding - static const quint64 NaNEncodeMask = 0xfffc000000000000ll; + static const quint64 NaNEncodeMask = 0xfffc000000000000ull; enum { IsDouble_Shift = 64-14, IsManagedOrUndefined_Shift = 64-15, - IsIntegerConvertible_Shift = 64-16, - IsDoubleTag_Shift = IsDouble_Shift - Tag_Shift, - Managed_Type_Internal_64 = 0 + IsIntegerConvertible_Shift = 64-15, + IsIntegerOrBool_Shift = 64-16, + QuickType_Shift = 64 - 17, + IsPositiveIntShift = 31 }; static const quint64 Immediate_Mask_64 = 0x00020000u; // bit 49 enum class ValueTypeInternal_64 { - Empty = Immediate_Mask_64| 0, - ConvertibleToInt = Immediate_Mask_64| 0x10000u, // bit 48 - Null = ConvertibleToInt | 0x08000u, - Boolean = ConvertibleToInt | 0x04000u, - Integer = ConvertibleToInt | 0x02000u + Empty = Immediate_Mask_64 | 0, + Null = Immediate_Mask_64 | 0x08000u, + Boolean = Immediate_Mask_64 | 0x10000u, + Integer = Immediate_Mask_64 | 0x18000u }; // Used only by 32-bit encoding @@ -285,50 +253,58 @@ public: static const quint64 Immediate_Mask_32 = NotDouble_Mask | 0x00020000u | SilentNaNBit; enum class ValueTypeInternal_32 { - Empty = Immediate_Mask_32| 0, - ConvertibleToInt = Immediate_Mask_32| 0x10000u, // bit 48 - Null = ConvertibleToInt | 0x08000u, - Boolean = ConvertibleToInt | 0x04000u, - Integer = ConvertibleToInt | 0x02000u + Empty = Immediate_Mask_32 | 0, + Null = Immediate_Mask_32 | 0x08000u, + Boolean = Immediate_Mask_32 | 0x10000u, + Integer = Immediate_Mask_32 | 0x18000u }; enum { - Managed_Type_Internal_32 = NotDouble_Mask + Managed_Type_Internal = 0 }; -#ifdef QV4_USE_64_BIT_VALUE_ENCODING - enum { - Managed_Type_Internal = Managed_Type_Internal_64 - }; - static const quint64 Immediate_Mask = Immediate_Mask_64; using ValueTypeInternal = ValueTypeInternal_64; -#else - enum { - Managed_Type_Internal = Managed_Type_Internal_32 - }; - static const quint64 Immediate_Mask = Immediate_Mask_32; - using ValueTypeInternal = ValueTypeInternal_32; -#endif + enum { NaN_Mask = 0x7ff80000, }; + inline quint64 quickType() const { return (_val >> QuickType_Shift); } + // used internally in property inline bool isEmpty() const { return tag() == quint32(ValueTypeInternal::Empty); } inline bool isNull() const { return tag() == quint32(ValueTypeInternal::Null); } inline bool isBoolean() const { return tag() == quint32(ValueTypeInternal::Boolean); } inline bool isInteger() const { return tag() == quint32(ValueTypeInternal::Integer); } inline bool isNullOrUndefined() const { return isNull() || isUndefined(); } - inline bool isNumber() const { return isDouble() || isInteger(); } + inline bool isNumber() const { return quickType() >= QT_Int; } -#ifdef QV4_USE_64_BIT_VALUE_ENCODING inline bool isUndefined() const { return _val == 0; } inline bool isDouble() const { return (_val >> IsDouble_Shift); } - inline bool isManaged() const { return !isUndefined() && ((_val >> IsManagedOrUndefined_Shift) == 0); } - inline bool isManagedOrUndefined() const { return ((_val >> IsManagedOrUndefined_Shift) == 0); } + inline bool isManaged() const + { +#if QT_POINTER_SIZE == 4 + return value() && tag() == Managed_Type_Internal; +#else + return _val && ((_val >> IsManagedOrUndefined_Shift) == 0); +#endif + } + inline bool isManagedOrUndefined() const + { +#if QT_POINTER_SIZE == 4 + return tag() == Managed_Type_Internal; +#else + return ((_val >> IsManagedOrUndefined_Shift) == 0); +#endif + } + + inline bool isIntOrBool() const { + return (_val >> IsIntegerOrBool_Shift) == 3; + } inline bool integerCompatible() const { - return (_val >> IsIntegerConvertible_Shift) == 3; + Q_ASSERT(!isEmpty()); + return (_val >> IsIntegerConvertible_Shift) == 1; } static inline bool integerCompatible(Value a, Value b) { return a.integerCompatible() && b.integerCompatible(); @@ -337,52 +313,51 @@ public: return a.isDouble() && b.isDouble(); } inline bool isNaN() const { return (tag() & 0x7ffc0000 ) == 0x00040000; } + + inline bool isPositiveInt() const { +#if QT_POINTER_SIZE == 4 + return isInteger() && int_32() >= 0; #else - inline bool isUndefined() const { return tag() == Managed_Type_Internal && value() == 0; } - inline bool isDouble() const { return (tag() & NotDouble_Mask) != NotDouble_Mask; } - inline bool isManaged() const { return tag() == Managed_Type_Internal && !isUndefined(); } - inline bool isManagedOrUndefined() const { return tag() == Managed_Type_Internal; } - inline bool integerCompatible() const { return (tag() & quint32(ValueTypeInternal::ConvertibleToInt)) == quint32(ValueTypeInternal::ConvertibleToInt); } - static inline bool integerCompatible(Value a, Value b) { - return ((a.tag() & b.tag()) & quint32(ValueTypeInternal::ConvertibleToInt)) == quint32(ValueTypeInternal::ConvertibleToInt); - } - static inline bool bothDouble(Value a, Value b) { - return ((a.tag() | b.tag()) & NotDouble_Mask) != NotDouble_Mask; - } - inline bool isNaN() const { return (tag() & QV4::Value::NotDouble_Mask) == QV4::Value::NaN_Mask; } + return (_val >> IsPositiveIntShift) == (quint64(ValueTypeInternal::Integer) << 1); #endif + } + QML_NEARLY_ALWAYS_INLINE double doubleValue() const { Q_ASSERT(isDouble()); double d; - quint64 v = _val; -#ifdef QV4_USE_64_BIT_VALUE_ENCODING - v ^= NaNEncodeMask; -#endif - memcpy(&d, &v, 8); + Value v = *this; + v._val ^= NaNEncodeMask; + memcpy(&d, &v._val, 8); return d; } QML_NEARLY_ALWAYS_INLINE void setDouble(double d) { + if (qt_is_nan(d)) + d = qt_qnan(); memcpy(&_val, &d, 8); -#ifdef QV4_USE_64_BIT_VALUE_ENCODING _val ^= NaNEncodeMask; -#endif Q_ASSERT(isDouble()); } inline bool isString() const; + inline bool isStringOrSymbol() const; + inline bool isSymbol() const; inline bool isObject() const; + inline bool isFunctionObject() const; inline bool isInt32() { if (tag() == quint32(ValueTypeInternal::Integer)) return true; if (isDouble()) { double d = doubleValue(); - int i = (int)d; - if (i == d) { - setInt_32(i); + if (isInt32(d)) { + setInt_32(int(d)); return true; } } return false; } + QML_NEARLY_ALWAYS_INLINE static bool isInt32(double d) { + int i = int(d); + return (i == d && !(d == 0 && std::signbit(d))); + } double asDouble() const { if (tag() == quint32(ValueTypeInternal::Integer)) return int_32(); @@ -399,7 +374,17 @@ public: QML_NEARLY_ALWAYS_INLINE String *stringValue() const { if (!isString()) return nullptr; - return reinterpret_cast<String*>(const_cast<Value *>(this)); + return reinterpret_cast<String *>(const_cast<Value *>(this)); + } + QML_NEARLY_ALWAYS_INLINE StringOrSymbol *stringOrSymbolValue() const { + if (!isStringOrSymbol()) + return nullptr; + return reinterpret_cast<StringOrSymbol *>(const_cast<Value *>(this)); + } + QML_NEARLY_ALWAYS_INLINE Symbol *symbolValue() const { + if (!isSymbol()) + return nullptr; + return reinterpret_cast<Symbol *>(const_cast<Value *>(this)); } QML_NEARLY_ALWAYS_INLINE Object *objectValue() const { if (!isObject()) @@ -425,15 +410,37 @@ public: int toUInt16() const; inline int toInt32() const; inline unsigned int toUInt32() const; + qint64 toLength() const; + inline qint64 toIndex() const; + + bool toBoolean() const { + if (integerCompatible()) + return static_cast<bool>(int_32()); - bool toBoolean() const; + return toBooleanImpl(*this); + } + static bool toBooleanImpl(Value val); double toInteger() const; + inline ReturnedValue convertedToNumber() const; inline double toNumber() const; - double toNumberImpl() const; + static double toNumberImpl(Value v); + double toNumberImpl() const { return toNumberImpl(*this); } QString toQStringNoThrow() const; QString toQString() const; - Heap::String *toString(ExecutionEngine *e) const; - Heap::Object *toObject(ExecutionEngine *e) const; + Heap::String *toString(ExecutionEngine *e) const { + if (isString()) + return reinterpret_cast<Heap::String *>(m()); + return toString(e, *this); + } + QV4::PropertyKey toPropertyKey(ExecutionEngine *e) const; + + static Heap::String *toString(ExecutionEngine *e, Value val); + Heap::Object *toObject(ExecutionEngine *e) const { + if (isObject()) + return reinterpret_cast<Heap::Object *>(m()); + return toObject(e, *this); + } + static Heap::Object *toObject(ExecutionEngine *e, Value val); inline bool isPrimitive() const; inline bool tryIntegerConversion() { @@ -446,19 +453,19 @@ public: template <typename T> const T *as() const { if (!isManaged()) - return 0; + return nullptr; - Q_ASSERT(m()->vtable()); + Q_ASSERT(m()->internalClass->vtable); #if !defined(QT_NO_QOBJECT_CHECK) static_cast<const T *>(this)->qt_check_for_QMANAGED_macro(static_cast<const T *>(this)); #endif - const VTable *vt = m()->vtable(); + const VTable *vt = m()->internalClass->vtable; while (vt) { if (vt == T::staticVTable()) return static_cast<const T *>(this); vt = vt->parent; } - return 0; + return nullptr; } template <typename T> T *as() { @@ -475,25 +482,37 @@ public: return static_cast<const T *>(managed()); } - inline uint asArrayIndex() const; - inline bool asArrayIndex(uint &idx) const; #ifndef V4_BOOTSTRAP uint asArrayLength(bool *ok) const; #endif - ReturnedValue asReturnedValue() const { return _val; } + ReturnedValue *data_ptr() { return &_val; } + constexpr ReturnedValue asReturnedValue() const { return _val; } static Value fromReturnedValue(ReturnedValue val) { Value v; v._val = val; return v; } - // Section 9.12 + // As per ES specs bool sameValue(Value other) const; + bool sameValueZero(Value other) const; inline void mark(MarkStack *markStack); + inline static constexpr Value emptyValue() { return { tagValue(quint32(ValueTypeInternal::Empty), 0) }; } + static inline constexpr Value fromBoolean(bool b) { return { tagValue(quint32(ValueTypeInternal::Boolean), b) }; } + static inline constexpr Value fromInt32(int i) { return { tagValue(quint32(ValueTypeInternal::Integer), quint32(i)) }; } + inline static constexpr Value undefinedValue() { return { 0 }; } + static inline constexpr Value nullValue() { return { tagValue(quint32(ValueTypeInternal::Null), 0) }; } + static inline Value fromDouble(double d); + static inline Value fromUInt32(uint i); + + static double toInteger(double d); + static int toInt32(double d); + static unsigned int toUInt32(double d); + Value &operator =(const ScopedValue &v); Value &operator=(ReturnedValue v) { _val = v; return *this; } Value &operator=(Managed *m) { if (!m) { - setM(0); + setM(nullptr); } else { _val = reinterpret_cast<Value *>(m)->_val; } @@ -507,17 +526,44 @@ public: template<typename T> Value &operator=(const Scoped<T> &t); }; -V4_ASSERT_IS_TRIVIAL(Value) +Q_STATIC_ASSERT(std::is_trivial< Value >::value); + +inline void Value::mark(MarkStack *markStack) +{ + Heap::Base *o = heapObject(); + if (o) + o->mark(markStack); +} inline bool Value::isString() const { Heap::Base *b = heapObject(); - return b && b->vtable()->isString; + return b && b->internalClass->vtable->isString; +} + +bool Value::isStringOrSymbol() const +{ + Heap::Base *b = heapObject(); + return b && b->internalClass->vtable->isStringOrSymbol; +} + +bool Value::isSymbol() const +{ + Heap::Base *b = heapObject(); + return b && b->internalClass->vtable->isStringOrSymbol && !b->internalClass->vtable->isString; } + inline bool Value::isObject() const + { Heap::Base *b = heapObject(); - return b && b->vtable()->isObject; + return b && b->internalClass->vtable->isObject; +} + +inline bool Value::isFunctionObject() const +{ + Heap::Base *b = heapObject(); + return b && b->internalClass->vtable->isFunctionObject; } inline bool Value::isPrimitive() const @@ -534,42 +580,14 @@ inline double Value::toNumber() const return toNumberImpl(); } - -#ifndef V4_BOOTSTRAP -inline uint Value::asArrayIndex() const +inline ReturnedValue Value::convertedToNumber() const { -#ifdef QV4_USE_64_BIT_VALUE_ENCODING - if (!isNumber()) - return UINT_MAX; - if (isInteger()) - return int_32() >= 0 ? (uint)int_32() : UINT_MAX; -#else - if (isInteger() && int_32() >= 0) - return (uint)int_32(); - if (!isDouble()) - return UINT_MAX; -#endif - double d = doubleValue(); - uint idx = (uint)d; - if (idx != d) - return UINT_MAX; - return idx; -} - -inline bool Value::asArrayIndex(uint &idx) const -{ - if (!isDouble()) { - if (isInteger() && int_32() >= 0) { - idx = (uint)int_32(); - return true; - } - return false; - } - double d = doubleValue(); - idx = (uint)d; - return (idx == d && idx != UINT_MAX); + if (isInteger() || isDouble()) + return asReturnedValue(); + Value v; + v.setDouble(toNumberImpl()); + return v.asReturnedValue(); } -#endif inline ReturnedValue Heap::Base::asReturnedValue() const @@ -577,122 +595,146 @@ ReturnedValue Heap::Base::asReturnedValue() const return Value::fromHeapObject(const_cast<Heap::Base *>(this)).asReturnedValue(); } - - -struct Q_QML_PRIVATE_EXPORT Primitive : public Value +inline Value Value::fromDouble(double d) { - inline static Primitive emptyValue(); - inline static Primitive emptyValue(uint v); - static inline Primitive fromBoolean(bool b); - static inline Primitive fromInt32(int i); - inline static Primitive undefinedValue(); - static inline Primitive nullValue(); - static inline Primitive fromDouble(double d); - static inline Primitive fromUInt32(uint i); - - using Value::toInt32; - using Value::toUInt32; - - static double toInteger(double fromNumber); - static int toInt32(double value); - static unsigned int toUInt32(double value); -}; - -inline Primitive Primitive::undefinedValue() -{ - Primitive v; - v.setM(Q_NULLPTR); + Value v; + v.setDouble(d); return v; } -inline Primitive Primitive::emptyValue() +inline Value Value::fromUInt32(uint i) { - Primitive v; - v.setEmpty(0); + Value v; + if (i < INT_MAX) { + v.setTagValue(quint32(ValueTypeInternal::Integer), i); + } else { + v.setDouble(i); + } return v; } -inline Primitive Primitive::emptyValue(uint e) -{ - Primitive v; - v.setEmpty(e); - return v; -} +struct Double { + quint64 d; -inline Primitive Primitive::nullValue() -{ - Primitive v; - v.setTagValue(quint32(ValueTypeInternal::Null), 0); - return v; -} + Double(double dbl) { + memcpy(&d, &dbl, sizeof(double)); + } -inline Primitive Primitive::fromBoolean(bool b) -{ - Primitive v; - v.setTagValue(quint32(ValueTypeInternal::Boolean), b); - return v; -} + int sign() const { + return (d >> 63) ? -1 : 1; + } + + bool isDenormal() const { + return static_cast<int>((d << 1) >> 53) == 0; + } + + int exponent() const { + return static_cast<int>((d << 1) >> 53) - 1023; + } + + quint64 significant() const { + quint64 m = (d << 12) >> 12; + if (!isDenormal()) + m |= (static_cast<quint64>(1) << 52); + return m; + } -inline Primitive Primitive::fromDouble(double d) + static int toInt32(double d) { + int i = static_cast<int>(d); + if (i == d) + return i; + return Double(d).toInt32(); + } + + int toInt32() { + int e = exponent() - 52; + if (e < 0) { + if (e <= -53) + return 0; + return sign() * static_cast<int>(significant() >> -e); + } else { + if (e > 31) + return 0; + return sign() * (static_cast<int>(significant()) << e); + } + } +}; + +inline double Value::toInteger(double d) { - Primitive v; - v.setDouble(d); - return v; + if (std::isnan(d)) + return +0; + else if (!d || std::isinf(d)) + return d; + return d >= 0 ? std::floor(d) : std::ceil(d); } -inline Primitive Primitive::fromInt32(int i) +inline int Value::toInt32(double value) { - Primitive v; - v.setInt_32(i); - return v; + return Double::toInt32(value); } -inline Primitive Primitive::fromUInt32(uint i) +inline unsigned int Value::toUInt32(double d) { - Primitive v; - if (i < INT_MAX) { - v.setTagValue(quint32(ValueTypeInternal::Integer), i); - } else { - v.setDouble(i); - } - return v; + return static_cast<uint>(toInt32(d)); } +// For source compat with older code in other modules +using Primitive = Value; + struct Encode { - static ReturnedValue undefined() { - return Primitive::undefinedValue().rawValue(); + static constexpr ReturnedValue undefined() { + return Value::undefinedValue().asReturnedValue(); } - static ReturnedValue null() { - return Primitive::nullValue().rawValue(); + static constexpr ReturnedValue null() { + return Value::nullValue().asReturnedValue(); } - Encode(bool b) { - val = Primitive::fromBoolean(b).rawValue(); + explicit constexpr Encode(bool b) + : val(Value::fromBoolean(b).asReturnedValue()) + { } - Encode(double d) { - val = Primitive::fromDouble(d).rawValue(); + explicit Encode(double d) { + val = Value::fromDouble(d).asReturnedValue(); } - Encode(int i) { - val = Primitive::fromInt32(i).rawValue(); + explicit constexpr Encode(int i) + : val(Value::fromInt32(i).asReturnedValue()) + { + } + explicit Encode(uint i) { + val = Value::fromUInt32(i).asReturnedValue(); } - Encode(uint i) { - val = Primitive::fromUInt32(i).rawValue(); + explicit constexpr Encode(ReturnedValue v) + : val(v) + { } - Encode(ReturnedValue v) { - val = v; + constexpr Encode(Value v) + : val(v.asReturnedValue()) + { } - Encode(Heap::Base *o) { - Q_ASSERT(o); + explicit Encode(Heap::Base *o) { val = Value::fromHeapObject(o).asReturnedValue(); } - operator ReturnedValue() const { + explicit Encode(Value *o) { + Q_ASSERT(o); + val = o->asReturnedValue(); + } + + static ReturnedValue smallestNumber(double d) { + if (Value::isInt32(d)) + return Encode(static_cast<int>(d)); + else + return Encode(d); + } + + constexpr operator ReturnedValue() const { return val; } quint64 val; private: - Encode(void *); + explicit Encode(void *); }; template<typename T> @@ -700,24 +742,141 @@ ReturnedValue value_convert(ExecutionEngine *e, const Value &v); inline int Value::toInt32() const { - if (isInteger()) + if (Q_LIKELY(integerCompatible())) return int_32(); - double d = isNumber() ? doubleValue() : toNumberImpl(); - - const double D32 = 4294967296.0; - const double D31 = D32 / 2.0; - if ((d >= -D31 && d < D31)) - return static_cast<int>(d); + if (Q_LIKELY(isDouble())) + return Double::toInt32(doubleValue()); - return Primitive::toInt32(d); + return Double::toInt32(toNumberImpl()); } inline unsigned int Value::toUInt32() const { - return (unsigned int)toInt32(); + return static_cast<unsigned int>(toInt32()); } +inline qint64 Value::toLength() const +{ + if (Q_LIKELY(integerCompatible())) + return int_32() < 0 ? 0 : int_32(); + double i = Value::toInteger(isDouble() ? doubleValue() : toNumberImpl()); + if (i <= 0) + return 0; + if (i > (static_cast<qint64>(1) << 53) - 1) + return (static_cast<qint64>(1) << 53) - 1; + return static_cast<qint64>(i); +} + +inline qint64 Value::toIndex() const +{ + qint64 idx; + if (Q_LIKELY(integerCompatible())) { + idx = int_32(); + } else { + idx = static_cast<qint64>(Value::toInteger(isDouble() ? doubleValue() : toNumberImpl())); + } + if (idx > (static_cast<qint64>(1) << 53) - 1) + idx = -1; + return idx; +} + +inline double Value::toInteger() const +{ + if (integerCompatible()) + return int_32(); + + return Value::toInteger(isDouble() ? doubleValue() : toNumberImpl()); +} + + +template <size_t o> +struct HeapValue : Value { + static Q_CONSTEXPR size_t offset = o; + Heap::Base *base() { + Heap::Base *base = reinterpret_cast<Heap::Base *>(this) - (offset/sizeof(Heap::Base)); + Q_ASSERT(base->inUse()); + return base; + } + + void set(EngineBase *e, const Value &newVal) { + WriteBarrier::write(e, base(), data_ptr(), newVal.asReturnedValue()); + } + void set(EngineBase *e, Heap::Base *b) { + WriteBarrier::write(e, base(), data_ptr(), b->asReturnedValue()); + } +}; + +template <size_t o> +struct ValueArray { + static Q_CONSTEXPR size_t offset = o; + uint size; + uint alloc; + Value values[1]; + + Heap::Base *base() { + Heap::Base *base = reinterpret_cast<Heap::Base *>(this) - (offset/sizeof(Heap::Base)); + Q_ASSERT(base->inUse()); + return base; + } + + void set(EngineBase *e, uint index, Value v) { + WriteBarrier::write(e, base(), values[index].data_ptr(), v.asReturnedValue()); + } + void set(EngineBase *e, uint index, Heap::Base *b) { + WriteBarrier::write(e, base(), values[index].data_ptr(), Value::fromHeapObject(b).asReturnedValue()); + } + inline const Value &operator[] (uint index) const { + Q_ASSERT(index < alloc); + return values[index]; + } + inline const Value *data() const { + return values; + } + + void insertData(EngineBase *e, uint index, Value v) { + for (uint i = size - 1; i > index; --i) { + values[i] = values[i - 1]; + } + set(e, index, v); + } + void removeData(EngineBase *e, uint index, int n = 1) { + Q_UNUSED(e); + for (uint i = index; i < size - n; ++i) { + values[i] = values[i + n]; + } + } + + void mark(MarkStack *markStack) { + Value *v = values; + const Value *end = v + alloc; + if (alloc > 32*1024) { + // drain from time to time to avoid overflows in the js stack + Heap::Base **currentBase = markStack->top; + while (v < end) { + v->mark(markStack); + ++v; + if (markStack->top >= currentBase + 32*1024) { + Heap::Base **oldBase = markStack->base; + markStack->base = currentBase; + markStack->drain(); + markStack->base = oldBase; + } + } + } else { + while (v < end) { + v->mark(markStack); + ++v; + } + } + } +}; + +// It's really important that the offset of values in this structure is +// constant across all architecture, otherwise JIT cross-compiled code will +// have wrong offsets between host and target. +Q_STATIC_ASSERT(offsetof(ValueArray<0>, values) == 8); + } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4variantobject.cpp b/src/qml/jsruntime/qv4variantobject.cpp index f2ff5d307e..e4d8bcaafc 100644 --- a/src/qml/jsruntime/qv4variantobject.cpp +++ b/src/qml/jsruntime/qv4variantobject.cpp @@ -70,7 +70,7 @@ bool VariantObject::Data::isScarce() const return t == QVariant::Pixmap || t == QVariant::Image; } -bool VariantObject::isEqualTo(Managed *m, Managed *other) +bool VariantObject::virtualIsEqualTo(Managed *m, Managed *other) { Q_ASSERT(m->as<QV4::VariantObject>()); QV4::VariantObject *lv = static_cast<QV4::VariantObject *>(m); @@ -113,17 +113,17 @@ void VariantPrototype::init() defineDefaultProperty(engine()->id_toString(), method_toString, 0); } -void VariantPrototype::method_preserve(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue VariantPrototype::method_preserve(const FunctionObject *, const Value *thisObject, const Value *, int) { - Scoped<VariantObject> o(scope, callData->thisObject.as<QV4::VariantObject>()); + const VariantObject *o = thisObject->as<QV4::VariantObject>(); if (o && o->d()->isScarce()) o->d()->addVmePropertyReference(); RETURN_UNDEFINED(); } -void VariantPrototype::method_destroy(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue VariantPrototype::method_destroy(const FunctionObject *, const Value *thisObject, const Value *, int) { - Scoped<VariantObject> o(scope, callData->thisObject.as<QV4::VariantObject>()); + const VariantObject *o = thisObject->as<QV4::VariantObject>(); if (o) { if (o->d()->isScarce()) o->d()->addVmePropertyReference(); @@ -132,49 +132,48 @@ void VariantPrototype::method_destroy(const BuiltinFunction *, Scope &scope, Cal RETURN_UNDEFINED(); } -void VariantPrototype::method_toString(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue VariantPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<VariantObject> o(scope, callData->thisObject.as<QV4::VariantObject>()); + ExecutionEngine *v4 = b->engine(); + const VariantObject *o = thisObject->as<QV4::VariantObject>(); if (!o) RETURN_UNDEFINED(); - QString result = o->d()->data().toString(); - if (result.isEmpty() && !o->d()->data().canConvert(QVariant::String)) { - result = QLatin1String("QVariant(") - + QLatin1String(o->d()->data().typeName()) - + QLatin1Char(')'); + const QVariant variant = o->d()->data(); + QString result = variant.toString(); + if (result.isEmpty() && !variant.canConvert(QVariant::String)) { + QDebug dbg(&result); + dbg << variant; + // QDebug appends a space, we're not interested in continuing the stream so we chop it off. + // Can't use nospace() because it would affect the debug-stream operator of the variant. + result.chop(1); } - scope.result = scope.engine->newString(result); + return Encode(v4->newString(result)); } -void VariantPrototype::method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue VariantPrototype::method_valueOf(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<VariantObject> o(scope, callData->thisObject.as<QV4::VariantObject>()); + const VariantObject *o = thisObject->as<QV4::VariantObject>(); if (o) { QVariant v = o->d()->data(); switch (v.type()) { case QVariant::Invalid: - scope.result = Encode::undefined(); - return; + return Encode::undefined(); case QVariant::String: - scope.result = scope.engine->newString(v.toString()); - return; + return Encode(b->engine()->newString(v.toString())); case QVariant::Int: - scope.result = Encode(v.toInt()); - return; + return Encode(v.toInt()); case QVariant::Double: case QVariant::UInt: - scope.result = Encode(v.toDouble()); - return; + return Encode(v.toDouble()); case QVariant::Bool: - scope.result = Encode(v.toBool()); - return; + return Encode(v.toBool()); default: if (QMetaType::typeFlags(v.userType()) & QMetaType::IsEnumeration) RETURN_RESULT(Encode(v.toInt())); break; } } - scope.result = callData->thisObject; + return thisObject->asReturnedValue(); } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4variantobject_p.h b/src/qml/jsruntime/qv4variantobject_p.h index a7c6fa320c..78e0a5373a 100644 --- a/src/qml/jsruntime/qv4variantobject_p.h +++ b/src/qml/jsruntime/qv4variantobject_p.h @@ -99,7 +99,8 @@ struct VariantObject : Object void addVmePropertyReference() const; void removeVmePropertyReference() const; - static bool isEqualTo(Managed *m, Managed *other); +protected: + static bool virtualIsEqualTo(Managed *m, Managed *other); }; struct VariantPrototype : VariantObject @@ -108,10 +109,10 @@ public: V4_PROTOTYPE(objectPrototype) void init(); - static void method_preserve(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_destroy(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_toString(const BuiltinFunction *, Scope &scope, CallData *callData); - static void method_valueOf(const BuiltinFunction *, Scope &scope, CallData *callData); + static ReturnedValue method_preserve(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_destroy(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue method_valueOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); }; } diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 7293630924..3098837e1c 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -50,20 +50,22 @@ #include <private/qv4math_p.h> #include <private/qv4scopedvalue_p.h> #include <private/qv4lookup_p.h> +#include <private/qv4regexp_p.h> +#include <private/qv4regexpobject_p.h> #include <private/qv4string_p.h> +#include <private/qv4profiling_p.h> +#include <private/qv4jscall_p.h> +#include <private/qv4generatorobject_p.h> +#include <private/qqmljavascriptexpression_p.h> #include <iostream> #include "qv4alloca_p.h" -#undef DO_TRACE_INSTR // define to enable instruction tracing +#include <private/qv4baselinejit_p.h> -#ifdef DO_TRACE_INSTR -# define TRACE_INSTR(I) qDebug("executing a %s\n", #I); -# define TRACE(n, str, ...) { char buf[4096]; snprintf(buf, 4096, str, __VA_ARGS__); qDebug(" %s : %s", #n, buf); } -#else -# define TRACE_INSTR(I) -# define TRACE(n, str, ...) -#endif // DO_TRACE_INSTR +#undef COUNT_INSTRUCTIONS + +enum { ShowWhenDeoptimiationHappens = 0 }; extern "C" { @@ -142,9 +144,9 @@ Q_QML_EXPORT int qt_v4DebuggerHook(const char *json); } // extern "C" -#ifndef QT_NO_QML_DEBUGGER +#if QT_CONFIG(qml_debug) static int qt_v4BreakpointCount = 0; -static bool qt_v4IsDebugging = true; +static bool qt_v4IsDebugging = false; static bool qt_v4IsStepping = false; class Breakpoint @@ -167,14 +169,6 @@ public: static QVector<Breakpoint> qt_v4Breakpoints; static Breakpoint qt_v4LastStop; -static QV4::Function *qt_v4ExtractFunction(QV4::ExecutionContext *context) -{ - if (QV4::Function *function = context->getFunction()) - return function; - else - return context->engine()->globalCode; -} - static void qt_v4TriggerBreakpoint(const Breakpoint &bp, QV4::Function *function) { qt_v4LastStop = bp; @@ -221,6 +215,7 @@ int qt_v4DebuggerHook(const char *json) bp.fullName = ob.value(QLatin1String("fullName")).toString(); bp.condition = ob.value(QLatin1String("condition")).toString(); qt_v4Breakpoints.append(bp); + qt_v4IsDebugging = true; return bp.bpNumber; } @@ -229,6 +224,7 @@ int qt_v4DebuggerHook(const char *json) QString fullName = ob.value(QLatin1String("fullName")).toString(); if (qt_v4Breakpoints.last().matches(fullName, lineNumber)) { qt_v4Breakpoints.removeLast(); + qt_v4IsDebugging = !qt_v4Breakpoints.isEmpty(); return Success; } for (int i = 0; i + 1 < qt_v4Breakpoints.size(); ++i) { @@ -249,13 +245,13 @@ int qt_v4DebuggerHook(const char *json) return -NoSuchCommand; // Failure. } -static void qt_v4CheckForBreak(QV4::ExecutionContext *context) +Q_NEVER_INLINE static void qt_v4CheckForBreak(QV4::CppStackFrame *frame) { if (!qt_v4IsStepping && !qt_v4Breakpoints.size()) return; - const int lineNumber = context->d()->lineNumber; - QV4::Function *function = qt_v4ExtractFunction(context); + const int lineNumber = frame->lineNumber(); + QV4::Function *function = frame->v4Function; QString engineName = function->sourceFile(); if (engineName.isEmpty()) @@ -285,691 +281,1242 @@ static void qt_v4CheckForBreak(QV4::ExecutionContext *context) } } -#endif // QT_NO_QML_DEBUGGER +Q_NEVER_INLINE static void debug_slowPath(QV4::ExecutionEngine *engine) +{ + QV4::Debugging::Debugger *debugger = engine->debugger(); + if (debugger && debugger->pauseAtNextOpportunity()) + debugger->maybeBreakAtInstruction(); + if (qt_v4IsDebugging) + qt_v4CheckForBreak(engine->currentStackFrame); +} + +#endif // QT_CONFIG(qml_debug) // End of debugger interface using namespace QV4; using namespace QV4::Moth; -#define MOTH_BEGIN_INSTR_COMMON(I) { \ - const InstrMeta<(int)Instr::I>::DataType &instr = InstrMeta<(int)Instr::I>::data(*genericInstr); \ - code += InstrMeta<(int)Instr::I>::Size; \ - Q_UNUSED(instr); \ - TRACE_INSTR(I) - -#ifdef MOTH_THREADED_INTERPRETER +#ifdef COUNT_INSTRUCTIONS +static struct InstrCount { + InstrCount() { + fprintf(stderr, "Counting instructions...\n"); + for (int i = 0; i < MOTH_NUM_INSTRUCTIONS(); ++i) + hits[i] = 0; + } + ~InstrCount() { + fprintf(stderr, "Instruction count:\n"); +#define BLAH(I) \ + fprintf(stderr, "%llu : %s\n", hits[int(Instr::Type::I)], #I); + FOR_EACH_MOTH_INSTR(BLAH) + #undef BLAH + } + quint64 hits[MOTH_NUM_INSTRUCTIONS()]; + void hit(Instr::Type i) { hits[int(i)]++; } +} instrCount; +#endif // COUNT_INSTRUCTIONS + +#define MOTH_BEGIN_INSTR_COMMON(instr) \ + { \ + INSTR_##instr(MOTH_DECODE) + +#ifdef COUNT_INSTRUCTIONS +# define MOTH_BEGIN_INSTR(instr) \ + MOTH_BEGIN_INSTR_COMMON(instr) \ + instrCount.hit(Instr::Type::instr); +#else // !COUNT_INSTRUCTIONS +# define MOTH_BEGIN_INSTR(instr) \ + MOTH_BEGIN_INSTR_COMMON(instr) +#endif // COUNT_INSTRUCTIONS + +#ifdef MOTH_COMPUTED_GOTO +#define MOTH_END_INSTR(instr) \ + MOTH_DISPATCH_SINGLE() \ + } +#else // !MOTH_COMPUTED_GOTO +#define MOTH_END_INSTR(instr) \ + continue; \ + } +#endif -# define MOTH_BEGIN_INSTR(I) op_##I: \ - MOTH_BEGIN_INSTR_COMMON(I) +#define STACK_VALUE(temp) stack[temp] -# define MOTH_END_INSTR(I) } \ - genericInstr = reinterpret_cast<const Instr *>(code); \ - goto *jumpTable[genericInstr->common.instructionType]; \ +// qv4scopedvalue_p.h also defines a CHECK_EXCEPTION macro +#ifdef CHECK_EXCEPTION +#undef CHECK_EXCEPTION +#endif +#define CHECK_EXCEPTION \ + if (engine->hasException) \ + goto handleUnwind +static inline void traceJumpTakesTruePath(bool truePathTaken, Function *f, int slot) +{ +#if QT_CONFIG(qml_tracing) + quint8 *traceInfo = f->traceInfo(slot); + Q_ASSERT(traceInfo); + *traceInfo |= truePathTaken ? quint8(ObservedTraceValues::TruePathTaken) + : quint8(ObservedTraceValues::FalsePathTaken); #else - -# define MOTH_BEGIN_INSTR(I) \ - case Instr::I: \ - MOTH_BEGIN_INSTR_COMMON(I) - -# define MOTH_END_INSTR(I) } \ - continue; - + Q_UNUSED(truePathTaken); + Q_UNUSED(f); + Q_UNUSED(slot); #endif +} -#ifdef DO_TRACE_INSTR -Param traceParam(const Param ¶m) +static inline void traceValue(ReturnedValue acc, Function *f, int slot) { - if (param.isConstant()) { - qDebug(" constant\n"); - } else if (param.isArgument()) { - qDebug(" argument %d@%d\n", param.index, param.scope); - } else if (param.isLocal()) { - qDebug(" local %d\n", param.index); - } else if (param.isTemp()) { - qDebug(" temp %d\n", param.index); - } else if (param.isScopedLocal()) { - qDebug(" temp %d@%d\n", param.index, param.scope); - } else { - Q_ASSERT(!"INVALID"); +#if QT_CONFIG(qml_tracing) + quint8 *traceInfo = f->traceInfo(slot); + Q_ASSERT(traceInfo); + switch (Primitive::fromReturnedValue(acc).type()) { + case QV4::Value::Integer_Type: + *traceInfo |= quint8(ObservedTraceValues::Integer); + break; + case QV4::Value::Boolean_Type: + *traceInfo |= quint8(ObservedTraceValues::Boolean); + break; + case QV4::Value::Double_Type: + *traceInfo |= quint8(ObservedTraceValues::Double); + break; + default: + *traceInfo |= quint8(ObservedTraceValues::Other); + break; } - return param; -} -# define VALUE(param) (*VALUEPTR(param)) -# define VALUEPTR(param) (scopes[traceParam(param).scope].values + param.index) #else -# define VALUE(param) (*VALUEPTR(param)) -# define VALUEPTR(param) (scopes[param.scope].values + param.index) + Q_UNUSED(acc); + Q_UNUSED(f); + Q_UNUSED(slot); #endif - -// ### add write barrier here -#define STOREVALUE(param, value) { \ - QV4::ReturnedValue tmp = (value); \ - if (engine->hasException) \ - goto catchException; \ - if (Q_LIKELY(!engine->writeBarrierActive || !scopes[param.scope].base)) { \ - VALUE(param) = tmp; \ - } else { \ - QV4::WriteBarrier::write(engine, scopes[param.scope].base, VALUEPTR(param), QV4::Value::fromReturnedValue(tmp)); \ - } \ } -// qv4scopedvalue_p.h also defines a CHECK_EXCEPTION macro -#ifdef CHECK_EXCEPTION -#undef CHECK_EXCEPTION +static inline void traceDoubleValue(Function *f, int slot) +{ +#if QT_CONFIG(qml_tracing) + quint8 *traceInfo = f->traceInfo(slot); + Q_ASSERT(traceInfo); + *traceInfo |= quint8(ObservedTraceValues::Double); +#else + Q_UNUSED(f); + Q_UNUSED(slot); #endif -#define CHECK_EXCEPTION \ - if (engine->hasException) \ - goto catchException +} -QV4::ReturnedValue VME::run(ExecutionEngine *engine, const uchar *code) +static inline void traceOtherValue(Function *f, int slot) { -#ifdef DO_TRACE_INSTR - qDebug("Starting VME with context=%p and code=%p", context, code); -#endif // DO_TRACE_INSTR - - qt_v4ResolvePendingBreakpointsHook(); - -#ifdef MOTH_THREADED_INTERPRETER -#define MOTH_INSTR_ADDR(I, FMT) &&op_##I, - static void *jumpTable[] = { - FOR_EACH_MOTH_INSTR(MOTH_INSTR_ADDR) - }; -#undef MOTH_INSTR_ADDR +#if QT_CONFIG(qml_tracing) + quint8 *traceInfo = f->traceInfo(slot); + Q_ASSERT(traceInfo); + *traceInfo |= quint8(ObservedTraceValues::Other); +#else + Q_UNUSED(f); + Q_UNUSED(slot); #endif +} + +static inline Heap::CallContext *getScope(QV4::Value *stack, int level) +{ + Heap::ExecutionContext *scope = static_cast<ExecutionContext &>(stack[CallData::Context]).d(); + while (level > 0) { + --level; + scope = scope->outer; + } + Q_ASSERT(scope); + return static_cast<Heap::CallContext *>(scope); +} - QV4::Value *stack = 0; - unsigned stackSize = 0; +static inline const QV4::Value &constant(Function *function, int index) +{ + return function->compilationUnit->constants[index]; +} - const uchar *exceptionHandler = 0; +static bool compareEqualInt(QV4::Value &accumulator, QV4::Value lhs, int rhs) +{ + redo: + switch (lhs.quickType()) { + case QV4::Value::QT_ManagedOrUndefined: + if (lhs.isUndefined()) + return false; + Q_FALLTHROUGH(); + case QV4::Value::QT_ManagedOrUndefined1: + case QV4::Value::QT_ManagedOrUndefined2: + case QV4::Value::QT_ManagedOrUndefined3: + // LHS: Managed + if (lhs.m()->internalClass->vtable->isString) + return RuntimeHelpers::stringToNumber(static_cast<String &>(lhs).toQString()) == rhs; + accumulator = lhs; + lhs = QV4::Value::fromReturnedValue(RuntimeHelpers::objectDefaultValue(&static_cast<QV4::Object &>(accumulator), PREFERREDTYPE_HINT)); + goto redo; + case QV4::Value::QT_Empty: + Q_UNREACHABLE(); + case QV4::Value::QT_Null: + return false; + case QV4::Value::QT_Bool: + case QV4::Value::QT_Int: + return lhs.int_32() == rhs; + default: // double + return lhs.doubleValue() == rhs; + } +} - QV4::Scope scope(engine); - engine->current->lineNumber = -1; +#define STORE_IP() frame->instructionPointer = int(code - function->codeData); +#define STORE_ACC() accumulator = acc; +#define ACC Value::fromReturnedValue(acc) +#define VALUE_TO_INT(i, val) \ + int i; \ + do { \ + if (Q_LIKELY(val.integerCompatible())) { \ + i = val.int_32(); \ + } else { \ + double d; \ + if (val.isDouble()) \ + d = val.doubleValue(); \ + else { \ + STORE_ACC(); \ + d = val.toNumberImpl(); \ + CHECK_EXCEPTION; \ + } \ + i = Double::toInt32(d); \ + } \ + } while (false) + +ReturnedValue VME::exec(CppStackFrame *frame, ExecutionEngine *engine) +{ + qt_v4ResolvePendingBreakpointsHook(); + CHECK_STACK_LIMITS(engine); -#ifdef DO_TRACE_INSTR - qDebug("Starting VME with context=%p and code=%p", context, code); -#endif // DO_TRACE_INSTR + Function *function = frame->v4Function; + Profiling::FunctionCallProfiler profiler(engine, function); // start execution profiling + QV4::Debugging::Debugger *debugger = engine->debugger(); - // setup lookup scopes - int scopeDepth = 0; - { - QV4::Heap::ExecutionContext *scope = engine->current; - while (scope) { - ++scopeDepth; - scope = scope->outer; +#ifdef V4_ENABLE_JIT + if (debugger == nullptr) { + if (function->jittedCode == nullptr) { + if (engine->canJIT(function)) { +#if QT_CONFIG(qml_tracing) + if (function->tracingEnabled()) + runTracingJit(function); + else +#endif + QV4::JIT::BaselineJIT(function).generate(); + } else { + ++function->interpreterCallCount; + } } } +#endif // V4_ENABLE_JIT - struct Scopes { - QV4::Value *values; - QV4::Heap::Base *base; // non 0 if a write barrier is required - }; - Q_ALLOCA_VAR(Scopes, scopes, sizeof(Scopes)*(2 + 2*scopeDepth)); - { - scopes[0] = { const_cast<QV4::Value *>(static_cast<CompiledData::CompilationUnit*>(engine->current->compilationUnit)->constants), 0 }; - // stack gets setup in push instruction - scopes[1] = { 0, 0 }; - QV4::Heap::ExecutionContext *scope = engine->current; - int i = 0; - while (scope) { - if (scope->type == QV4::Heap::ExecutionContext::Type_SimpleCallContext) { - QV4::Heap::SimpleCallContext *cc = static_cast<QV4::Heap::SimpleCallContext *>(scope); - scopes[2*i + 2] = { cc->callData->args, 0 }; - scopes[2*i + 3] = { 0, 0 }; - } else if (scope->type == QV4::Heap::ExecutionContext::Type_CallContext) { - QV4::Heap::CallContext *cc = static_cast<QV4::Heap::CallContext *>(scope); - scopes[2*i + 2] = { cc->callData->args, cc }; - scopes[2*i + 3] = { cc->locals.values, cc }; - } else { - scopes[2*i + 2] = { 0, 0 }; - scopes[2*i + 3] = { 0, 0 }; + // interpreter + if (debugger) + debugger->enteringFunction(); + + ReturnedValue result; + if (function->jittedCode != nullptr && debugger == nullptr) { + result = function->jittedCode(frame, engine); + if (QV4::Value::fromReturnedValue(result).isEmpty()) { // de-optimize! + if (ShowWhenDeoptimiationHappens) { + // This is debug code, which is disabled by default, and completely removed by the + // compiler. + fprintf(stderr, "*********************** DEOPT! %s ***********************\n" + "*** deopt IP: %d, line: %d\n", + function->name()->toQString().toUtf8().constData(), + frame->instructionPointer, + frame->lineNumber()); } - ++i; - scope = scope->outer; + delete function->codeRef; + function->codeRef = nullptr; + function->jittedCode = nullptr; + function->interpreterCallCount = 0; // reset to restart tracing: apparently we didn't have enough info before + result = interpret(frame, engine, function->codeData + frame->instructionPointer); } + } else { + // interpreter + result = interpret(frame, engine, function->codeData); } + if (debugger) + debugger->leavingFunction(result); + + return result; +} + +QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, const char *code) +{ + QV4::Function *function = frame->v4Function; + QV4::Value &accumulator = frame->jsFrame->accumulator; + QV4::ReturnedValue acc = accumulator.asReturnedValue(); + Value *stack = reinterpret_cast<Value *>(frame->jsFrame); + + if (function->tracingEnabled()) { + for (int i = 0; i < int(function->nFormals); ++i) + traceValue(frame->jsFrame->argument(i), function, i); + } + + MOTH_JUMP_TABLE; for (;;) { - const Instr *genericInstr = reinterpret_cast<const Instr *>(code); -#ifdef MOTH_THREADED_INTERPRETER - goto *jumpTable[genericInstr->common.instructionType]; -#else - switch (genericInstr->common.instructionType) { -#endif + MOTH_DISPATCH() + Q_UNREACHABLE(); // only reached when the dispatch doesn't jump somewhere + + MOTH_BEGIN_INSTR(LoadConst) + acc = constant(function, index).asReturnedValue(); + MOTH_END_INSTR(LoadConst) + + MOTH_BEGIN_INSTR(LoadNull) + acc = Encode::null(); + MOTH_END_INSTR(LoadNull) + + MOTH_BEGIN_INSTR(LoadZero) + acc = Encode(static_cast<int>(0)); + MOTH_END_INSTR(LoadZero) - MOTH_BEGIN_INSTR(Move) - VALUE(instr.result) = VALUE(instr.source); - MOTH_END_INSTR(Move) + MOTH_BEGIN_INSTR(LoadTrue) + acc = Encode(true); + MOTH_END_INSTR(LoadTrue) + + MOTH_BEGIN_INSTR(LoadFalse) + acc = Encode(false); + MOTH_END_INSTR(LoadFalse) + + MOTH_BEGIN_INSTR(LoadUndefined) + acc = Encode::undefined(); + MOTH_END_INSTR(LoadUndefined) + + MOTH_BEGIN_INSTR(LoadInt) + acc = Encode(value); + MOTH_END_INSTR(LoadInt) MOTH_BEGIN_INSTR(MoveConst) - VALUE(instr.result) = instr.source; + STACK_VALUE(destTemp) = constant(function, constIndex); MOTH_END_INSTR(MoveConst) - MOTH_BEGIN_INSTR(SwapTemps) - qSwap(VALUE(instr.left), VALUE(instr.right)); - MOTH_END_INSTR(MoveTemp) + MOTH_BEGIN_INSTR(LoadReg) + acc = STACK_VALUE(reg).asReturnedValue(); + MOTH_END_INSTR(LoadReg) + + MOTH_BEGIN_INSTR(StoreReg) + STACK_VALUE(reg) = acc; + MOTH_END_INSTR(StoreReg) + + MOTH_BEGIN_INSTR(MoveReg) + STACK_VALUE(destReg) = STACK_VALUE(srcReg); + MOTH_END_INSTR(MoveReg) + + MOTH_BEGIN_INSTR(LoadImport) + acc = function->compilationUnit->imports[index]->asReturnedValue(); + MOTH_END_INSTR(LoadImport) + + MOTH_BEGIN_INSTR(LoadLocal) + auto cc = static_cast<Heap::CallContext *>(stack[CallData::Context].m()); + Q_ASSERT(cc->type != QV4::Heap::CallContext::Type_GlobalContext); + acc = cc->locals[index].asReturnedValue(); + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(LoadLocal) + + MOTH_BEGIN_INSTR(StoreLocal) + CHECK_EXCEPTION; + auto cc = static_cast<Heap::CallContext *>(stack[CallData::Context].m()); + Q_ASSERT(cc->type != QV4::Heap::CallContext::Type_GlobalContext); + QV4::WriteBarrier::write(engine, cc, cc->locals.values[index].data_ptr(), acc); + MOTH_END_INSTR(StoreLocal) + + MOTH_BEGIN_INSTR(LoadScopedLocal) + auto cc = getScope(stack, scope); + acc = cc->locals[index].asReturnedValue(); + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(LoadScopedLocal) + + MOTH_BEGIN_INSTR(StoreScopedLocal) + CHECK_EXCEPTION; + auto cc = getScope(stack, scope); + QV4::WriteBarrier::write(engine, cc, cc->locals.values[index].data_ptr(), acc); + MOTH_END_INSTR(StoreScopedLocal) MOTH_BEGIN_INSTR(LoadRuntimeString) -// TRACE(value, "%s", instr.value.toString(context)->toQString().toUtf8().constData()); - VALUE(instr.result) = engine->current->compilationUnit->runtimeStrings[instr.stringId]; + acc = function->compilationUnit->runtimeStrings[stringId]->asReturnedValue(); MOTH_END_INSTR(LoadRuntimeString) - MOTH_BEGIN_INSTR(LoadRegExp) -// TRACE(value, "%s", instr.value.toString(context)->toQString().toUtf8().constData()); - VALUE(instr.result) = static_cast<CompiledData::CompilationUnit*>(engine->current->compilationUnit)->runtimeRegularExpressions[instr.regExpId]; - MOTH_END_INSTR(LoadRegExp) + MOTH_BEGIN_INSTR(MoveRegExp) + STACK_VALUE(destReg) = Runtime::RegexpLiteral::call(engine, regExpId); + MOTH_END_INSTR(MoveRegExp) MOTH_BEGIN_INSTR(LoadClosure) - STOREVALUE(instr.result, Runtime::method_closure(engine, instr.value)); + acc = Runtime::Closure::call(engine, value); MOTH_END_INSTR(LoadClosure) MOTH_BEGIN_INSTR(LoadName) - TRACE(inline, "property name = %s", runtimeStrings[instr.name]->toQString().toUtf8().constData()); - STOREVALUE(instr.result, Runtime::method_getActivationProperty(engine, instr.name)); + STORE_IP(); + acc = Runtime::LoadName::call(engine, name); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadName) - MOTH_BEGIN_INSTR(GetGlobalLookup) - QV4::Lookup *l = engine->current->lookups + instr.index; - STOREVALUE(instr.result, l->globalGetter(l, engine)); - MOTH_END_INSTR(GetGlobalLookup) + MOTH_BEGIN_INSTR(LoadGlobalLookup) + STORE_IP(); + QV4::Lookup *l = function->compilationUnit->runtimeLookups + index; + acc = l->globalGetter(l, engine); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(LoadGlobalLookup) + + MOTH_BEGIN_INSTR(StoreNameStrict) + STORE_IP(); + STORE_ACC(); + Runtime::StoreNameStrict::call(engine, name, accumulator); + CHECK_EXCEPTION; + MOTH_END_INSTR(StoreNameStrict) - MOTH_BEGIN_INSTR(StoreName) - TRACE(inline, "property name = %s", runtimeStrings[instr.name]->toQString().toUtf8().constData()); - Runtime::method_setActivationProperty(engine, instr.name, VALUE(instr.source)); + MOTH_BEGIN_INSTR(StoreNameSloppy) + STORE_IP(); + STORE_ACC(); + Runtime::StoreNameSloppy::call(engine, name, accumulator); CHECK_EXCEPTION; - MOTH_END_INSTR(StoreName) + MOTH_END_INSTR(StoreNameSloppy) MOTH_BEGIN_INSTR(LoadElement) - STOREVALUE(instr.result, Runtime::method_getElement(engine, VALUE(instr.base), VALUE(instr.index))); + STORE_IP(); + STORE_ACC(); +#if QT_CONFIG(qml_tracing) + acc = Runtime::LoadElement_Traced::call(engine, STACK_VALUE(base), accumulator, function->traceInfo(traceSlot)); + traceValue(acc, function, traceSlot); +#else + Q_UNUSED(traceSlot); + acc = Runtime::LoadElement::call(engine, STACK_VALUE(base), accumulator); +#endif + CHECK_EXCEPTION; MOTH_END_INSTR(LoadElement) - MOTH_BEGIN_INSTR(LoadElementLookup) - QV4::Lookup *l = engine->current->lookups + instr.lookup; - STOREVALUE(instr.result, l->indexedGetter(l, engine, VALUE(instr.base), VALUE(instr.index))); - MOTH_END_INSTR(LoadElementLookup) - MOTH_BEGIN_INSTR(StoreElement) - Runtime::method_setElement(engine, VALUE(instr.base), VALUE(instr.index), VALUE(instr.source)); + STORE_IP(); + STORE_ACC(); +#if QT_CONFIG(qml_tracing) + Runtime::StoreElement_traced::call(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator, function->traceInfo(traceSlot)); +#else + Q_UNUSED(traceSlot); + Runtime::StoreElement::call(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator); +#endif CHECK_EXCEPTION; MOTH_END_INSTR(StoreElement) - MOTH_BEGIN_INSTR(StoreElementLookup) - QV4::Lookup *l = engine->current->lookups + instr.lookup; - l->indexedSetter(l, engine, VALUE(instr.base), VALUE(instr.index), VALUE(instr.source)); - CHECK_EXCEPTION; - MOTH_END_INSTR(StoreElementLookup) - MOTH_BEGIN_INSTR(LoadProperty) - STOREVALUE(instr.result, Runtime::method_getProperty(engine, VALUE(instr.base), instr.name)); + STORE_IP(); + STORE_ACC(); + acc = Runtime::LoadProperty::call(engine, accumulator, name); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadProperty) MOTH_BEGIN_INSTR(GetLookup) - QV4::Lookup *l = engine->current->lookups + instr.index; - STOREVALUE(instr.result, l->getter(l, engine, VALUE(instr.base))); + STORE_IP(); + STORE_ACC(); + QV4::Lookup *l = function->compilationUnit->runtimeLookups + index; + acc = l->getter(l, engine, accumulator); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); MOTH_END_INSTR(GetLookup) MOTH_BEGIN_INSTR(StoreProperty) - Runtime::method_setProperty(engine, VALUE(instr.base), instr.name, VALUE(instr.source)); + STORE_IP(); + STORE_ACC(); + Runtime::StoreProperty::call(engine, STACK_VALUE(base), name, accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(StoreProperty) MOTH_BEGIN_INSTR(SetLookup) - QV4::Lookup *l = engine->current->lookups + instr.index; - l->setter(l, engine, VALUE(instr.base), VALUE(instr.source)); + STORE_IP(); + STORE_ACC(); + QV4::Lookup *l = function->compilationUnit->runtimeLookups + index; + if (!l->setter(l, engine, STACK_VALUE(base), accumulator) && function->isStrict()) + engine->throwTypeError(); CHECK_EXCEPTION; MOTH_END_INSTR(SetLookup) - MOTH_BEGIN_INSTR(StoreQObjectProperty) - Runtime::method_setQmlQObjectProperty(engine, VALUE(instr.base), instr.propertyIndex, VALUE(instr.source)); + MOTH_BEGIN_INSTR(LoadSuperProperty) + STORE_IP(); + STORE_ACC(); + acc = Runtime::LoadSuperProperty::call(engine, STACK_VALUE(property)); CHECK_EXCEPTION; - MOTH_END_INSTR(StoreQObjectProperty) + MOTH_END_INSTR(LoadSuperProperty) - MOTH_BEGIN_INSTR(LoadQObjectProperty) - STOREVALUE(instr.result, Runtime::method_getQmlQObjectProperty(engine, VALUE(instr.base), instr.propertyIndex, instr.captureRequired)); - MOTH_END_INSTR(LoadQObjectProperty) + MOTH_BEGIN_INSTR(StoreSuperProperty) + STORE_IP(); + STORE_ACC(); + Runtime::StoreSuperProperty::call(engine, STACK_VALUE(property), accumulator); + CHECK_EXCEPTION; + MOTH_END_INSTR(StoreSuperProperty) MOTH_BEGIN_INSTR(StoreScopeObjectProperty) - Runtime::method_setQmlScopeObjectProperty(engine, VALUE(instr.base), instr.propertyIndex, VALUE(instr.source)); + STORE_ACC(); + Runtime::StoreQmlScopeObjectProperty::call(engine, STACK_VALUE(base), propertyIndex, accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(StoreScopeObjectProperty) MOTH_BEGIN_INSTR(LoadScopeObjectProperty) - STOREVALUE(instr.result, Runtime::method_getQmlScopeObjectProperty(engine, VALUE(instr.base), instr.propertyIndex, instr.captureRequired)); + STORE_IP(); + acc = Runtime::LoadQmlScopeObjectProperty::call(engine, STACK_VALUE(base), propertyIndex, captureRequired); + CHECK_EXCEPTION; MOTH_END_INSTR(LoadScopeObjectProperty) MOTH_BEGIN_INSTR(StoreContextObjectProperty) - Runtime::method_setQmlContextObjectProperty(engine, VALUE(instr.base), instr.propertyIndex, VALUE(instr.source)); + STORE_IP(); + STORE_ACC(); + Runtime::StoreQmlContextObjectProperty::call(engine, STACK_VALUE(base), propertyIndex, accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(StoreContextObjectProperty) MOTH_BEGIN_INSTR(LoadContextObjectProperty) - STOREVALUE(instr.result, Runtime::method_getQmlContextObjectProperty(engine, VALUE(instr.base), instr.propertyIndex, instr.captureRequired)); + STORE_IP(); + acc = Runtime::LoadQmlContextObjectProperty::call(engine, STACK_VALUE(base), propertyIndex, captureRequired); + CHECK_EXCEPTION; MOTH_END_INSTR(LoadContextObjectProperty) MOTH_BEGIN_INSTR(LoadIdObject) - STOREVALUE(instr.result, Runtime::method_getQmlIdObject(engine, VALUE(instr.base), instr.index)); + STORE_IP(); + acc = Runtime::LoadQmlIdObject::call(engine, STACK_VALUE(base), index); + CHECK_EXCEPTION; MOTH_END_INSTR(LoadIdObject) - MOTH_BEGIN_INSTR(LoadAttachedQObjectProperty) - STOREVALUE(instr.result, Runtime::method_getQmlAttachedProperty(engine, instr.attachedPropertiesId, instr.propertyIndex)); - MOTH_END_INSTR(LoadAttachedQObjectProperty) - - MOTH_BEGIN_INSTR(LoadSingletonQObjectProperty) - STOREVALUE(instr.result, Runtime::method_getQmlSingletonQObjectProperty(engine, VALUE(instr.base), instr.propertyIndex, instr.captureRequired)); - MOTH_END_INSTR(LoadSingletonQObjectProperty) + MOTH_BEGIN_INSTR(Yield) + frame->yield = code; + frame->yieldIsIterator = false; + return acc; + MOTH_END_INSTR(Yield) + + MOTH_BEGIN_INSTR(YieldStar) + frame->yield = code; + frame->yieldIsIterator = true; + return acc; + MOTH_END_INSTR(YieldStar) + + MOTH_BEGIN_INSTR(Resume) + // check exception, in case the generator was called with throw() or return() + if (engine->hasException) { + // an empty value indicates that the generator was called with return() + if (engine->exceptionValue->asReturnedValue() != Value::emptyValue().asReturnedValue()) + goto handleUnwind; + engine->hasException = false; + *engine->exceptionValue = Value::undefinedValue(); + } else { + code += offset; + } + MOTH_END_INSTR(Resume) - MOTH_BEGIN_INSTR(Push) - TRACE(inline, "stack size: %u", instr.value); - stackSize = instr.value; - stack = scope.alloc(stackSize); - scopes[1].values = stack; - MOTH_END_INSTR(Push) + MOTH_BEGIN_INSTR(IteratorNextForYieldStar) + STORE_ACC(); + acc = Runtime::IteratorNextForYieldStar::call(engine, accumulator, STACK_VALUE(iterator), &STACK_VALUE(object)); + CHECK_EXCEPTION; + MOTH_END_INSTR(IteratorNextForYieldStar) MOTH_BEGIN_INSTR(CallValue) -#if 0 //def DO_TRACE_INSTR - if (Debugging::Debugger *debugger = context->engine()->debugger) { - if (QV4::FunctionObject *o = (VALUE(instr.dest)).asFunctionObject()) { - if (Debugging::FunctionDebugInfo *info = debugger->debugInfo(o)) { - QString n = debugger->name(o); - std::cerr << "*** Call to \"" << (n.isNull() ? "<no name>" : qPrintable(n)) << "\" defined @" << info->startLine << ":" << info->startColumn << std::endl; - } - } + STORE_IP(); + Value func = STACK_VALUE(name); + if (Q_UNLIKELY(!func.isFunctionObject())) { + acc = engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); + goto handleUnwind; } -#endif // DO_TRACE_INSTR - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = QV4::Primitive::undefinedValue(); - STOREVALUE(instr.result, Runtime::method_callValue(engine, VALUE(instr.dest), callData)); + Value undef = Value::undefinedValue(); + acc = static_cast<const FunctionObject &>(func).call(&undef, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallValue) + MOTH_BEGIN_INSTR(CallWithReceiver) + STORE_IP(); + Value func = STACK_VALUE(name); + if (Q_UNLIKELY(!func.isFunctionObject())) { + acc = engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); + goto handleUnwind; + } + acc = static_cast<const FunctionObject &>(func).call(stack + thisObject, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(CallWithReceiver) + MOTH_BEGIN_INSTR(CallProperty) - TRACE(property name, "%s, args=%u, argc=%u, this=%s", qPrintable(runtimeStrings[instr.name]->toQString()), instr.callData, instr.argc, (VALUE(instr.base)).toString(engine)->toQString().toUtf8().constData()); - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = VALUE(instr.base); - STOREVALUE(instr.result, Runtime::method_callProperty(engine, instr.name, callData)); + STORE_IP(); + acc = Runtime::CallProperty::call(engine, stack[base], name, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallProperty) MOTH_BEGIN_INSTR(CallPropertyLookup) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = VALUE(instr.base); - STOREVALUE(instr.result, Runtime::method_callPropertyLookup(engine, instr.lookupIndex, callData)); + STORE_IP(); + Lookup *l = function->compilationUnit->runtimeLookups + lookupIndex; + // ok to have the value on the stack here + Value f = Value::fromReturnedValue(l->getter(l, engine, stack[base])); + + if (Q_UNLIKELY(!f.isFunctionObject())) { + acc = engine->throwTypeError(); + goto handleUnwind; + } + + acc = static_cast<FunctionObject &>(f).call(stack + base, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallPropertyLookup) + MOTH_BEGIN_INSTR(CallElement) + STORE_IP(); + acc = Runtime::CallElement::call(engine, stack[base], STACK_VALUE(index), stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(CallElement) + + MOTH_BEGIN_INSTR(CallName) + STORE_IP(); + acc = Runtime::CallName::call(engine, name, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(CallName) + + MOTH_BEGIN_INSTR(CallPossiblyDirectEval) + STORE_IP(); + acc = Runtime::CallPossiblyDirectEval::call(engine, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(CallPossiblyDirectEval) + + MOTH_BEGIN_INSTR(CallGlobalLookup) + STORE_IP(); + acc = Runtime::CallGlobalLookup::call(engine, index, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(CallGlobalLookup) + MOTH_BEGIN_INSTR(CallScopeObjectProperty) - TRACE(property name, "%s, args=%u, argc=%u, this=%s", qPrintable(runtimeStrings[instr.name]->toQString()), instr.callData, instr.argc, (VALUE(instr.base)).toString(engine)->toQString().toUtf8().constData()); - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = VALUE(instr.base); - STOREVALUE(instr.result, Runtime::method_callQmlScopeObjectProperty(engine, instr.index, callData)); + STORE_IP(); + acc = Runtime::CallQmlScopeObjectProperty::call(engine, stack[base], name, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallScopeObjectProperty) MOTH_BEGIN_INSTR(CallContextObjectProperty) - TRACE(property name, "%s, args=%u, argc=%u, this=%s", qPrintable(runtimeStrings[instr.name]->toQString()), instr.callData, instr.argc, (VALUE(instr.base)).toString(engine)->toQString().toUtf8().constData()); - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = VALUE(instr.base); - STOREVALUE(instr.result, Runtime::method_callQmlContextObjectProperty(engine, instr.index, callData)); + STORE_IP(); + acc = Runtime::CallQmlContextObjectProperty::call(engine, stack[base], name, stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallContextObjectProperty) - MOTH_BEGIN_INSTR(CallElement) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = VALUE(instr.base); - STOREVALUE(instr.result, Runtime::method_callElement(engine, VALUE(instr.index), callData)); - MOTH_END_INSTR(CallElement) + MOTH_BEGIN_INSTR(CallWithSpread) + STORE_IP(); + acc = Runtime::CallWithSpread::call(engine, STACK_VALUE(func), STACK_VALUE(thisObject), stack + argv, argc); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(CallWithSpread) + + MOTH_BEGIN_INSTR(TailCall) + STORE_IP(); + *engine->jsAlloca(1) = Primitive::fromInt32(argc); + *engine->jsAlloca(1) = Primitive::fromInt32(argv); + *engine->jsAlloca(1) = STACK_VALUE(thisObject); + *engine->jsAlloca(1) = STACK_VALUE(func); + return Runtime::TailCall::call(frame, engine); + CHECK_EXCEPTION; + MOTH_END_INSTR(TailCall) - MOTH_BEGIN_INSTR(CallActivationProperty) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = QV4::Primitive::undefinedValue(); - STOREVALUE(instr.result, Runtime::method_callActivationProperty(engine, instr.name, callData)); - MOTH_END_INSTR(CallActivationProperty) + MOTH_BEGIN_INSTR(Construct) + STORE_IP(); + acc = Runtime::Construct::call(engine, STACK_VALUE(func), ACC, stack + argv, argc); + CHECK_EXCEPTION; + MOTH_END_INSTR(Construct) - MOTH_BEGIN_INSTR(CallGlobalLookup) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = QV4::Primitive::undefinedValue(); - STOREVALUE(instr.result, Runtime::method_callGlobalLookup(engine, instr.index, callData)); - MOTH_END_INSTR(CallGlobalLookup) + MOTH_BEGIN_INSTR(ConstructWithSpread) + STORE_IP(); + acc = Runtime::ConstructWithSpread::call(engine, STACK_VALUE(func), ACC, stack + argv, argc); + CHECK_EXCEPTION; + MOTH_END_INSTR(ConstructWithSpread) - MOTH_BEGIN_INSTR(SetExceptionHandler) - exceptionHandler = instr.offset ? ((const uchar *)&instr.offset) + instr.offset : 0; - MOTH_END_INSTR(SetExceptionHandler) + MOTH_BEGIN_INSTR(SetUnwindHandler) + frame->unwindHandler = offset ? code + offset : nullptr; + MOTH_END_INSTR(SetUnwindHandler) - MOTH_BEGIN_INSTR(CallBuiltinThrow) - Runtime::method_throwException(engine, VALUE(instr.arg)); + MOTH_BEGIN_INSTR(UnwindDispatch) CHECK_EXCEPTION; - MOTH_END_INSTR(CallBuiltinThrow) + if (frame->unwindLevel) { + --frame->unwindLevel; + if (frame->unwindLevel) + goto handleUnwind; + code = frame->unwindLabel; + } + MOTH_END_INSTR(UnwindDispatch) + + MOTH_BEGIN_INSTR(UnwindToLabel) + frame->unwindLevel = level; + frame->unwindLabel = code + offset; + goto handleUnwind; + MOTH_END_INSTR(UnwindToLabel) + + MOTH_BEGIN_INSTR(DeadTemporalZoneCheck) + if (ACC.isEmpty()) { + STORE_IP(); + STORE_ACC(); + Runtime::ThrowReferenceError::call(engine, name); + goto handleUnwind; + } + MOTH_END_INSTR(DeadTemporalZoneCheck) + + MOTH_BEGIN_INSTR(ThrowException) + STORE_IP(); + STORE_ACC(); + Runtime::ThrowException::call(engine, accumulator); + goto handleUnwind; + MOTH_END_INSTR(ThrowException) + + MOTH_BEGIN_INSTR(GetException) + acc = engine->hasException ? engine->exceptionValue->asReturnedValue() + : Value::emptyValue().asReturnedValue(); + engine->hasException = false; + MOTH_END_INSTR(HasException) + + MOTH_BEGIN_INSTR(SetException) + if (acc != Value::emptyValue().asReturnedValue()) { + *engine->exceptionValue = acc; + engine->hasException = true; + } + MOTH_END_INSTR(SetException) - MOTH_BEGIN_INSTR(CallBuiltinUnwindException) - STOREVALUE(instr.result, Runtime::method_unwindException(engine)); - MOTH_END_INSTR(CallBuiltinUnwindException) + MOTH_BEGIN_INSTR(PushCatchContext) + Runtime::PushCatchContext::call(engine, index, name); + MOTH_END_INSTR(PushCatchContext) - MOTH_BEGIN_INSTR(CallBuiltinPushCatchScope) - Runtime::method_pushCatchScope(static_cast<QV4::NoThrowEngine*>(engine), instr.name); - MOTH_END_INSTR(CallBuiltinPushCatchScope) - - MOTH_BEGIN_INSTR(CallBuiltinPushScope) - Runtime::method_pushWithScope(VALUE(instr.arg), static_cast<QV4::NoThrowEngine*>(engine)); - CHECK_EXCEPTION; - MOTH_END_INSTR(CallBuiltinPushScope) - - MOTH_BEGIN_INSTR(CallBuiltinPopScope) - Runtime::method_popScope(static_cast<QV4::NoThrowEngine*>(engine)); - MOTH_END_INSTR(CallBuiltinPopScope) - - MOTH_BEGIN_INSTR(CallBuiltinForeachIteratorObject) - STOREVALUE(instr.result, Runtime::method_foreachIterator(engine, VALUE(instr.arg))); - MOTH_END_INSTR(CallBuiltinForeachIteratorObject) - - MOTH_BEGIN_INSTR(CallBuiltinForeachNextPropertyName) - STOREVALUE(instr.result, Runtime::method_foreachNextPropertyName(VALUE(instr.arg))); - MOTH_END_INSTR(CallBuiltinForeachNextPropertyName) - - MOTH_BEGIN_INSTR(CallBuiltinDeleteMember) - STOREVALUE(instr.result, Runtime::method_deleteMember(engine, VALUE(instr.base), instr.member)); - MOTH_END_INSTR(CallBuiltinDeleteMember) - - MOTH_BEGIN_INSTR(CallBuiltinDeleteSubscript) - STOREVALUE(instr.result, Runtime::method_deleteElement(engine, VALUE(instr.base), VALUE(instr.index))); - MOTH_END_INSTR(CallBuiltinDeleteSubscript) - - MOTH_BEGIN_INSTR(CallBuiltinDeleteName) - STOREVALUE(instr.result, Runtime::method_deleteName(engine, instr.name)); - MOTH_END_INSTR(CallBuiltinDeleteName) - - MOTH_BEGIN_INSTR(CallBuiltinTypeofScopeObjectProperty) - STOREVALUE(instr.result, Runtime::method_typeofScopeObjectProperty(engine, VALUE(instr.base), instr.index)); - MOTH_END_INSTR(CallBuiltinTypeofMember) - - MOTH_BEGIN_INSTR(CallBuiltinTypeofContextObjectProperty) - STOREVALUE(instr.result, Runtime::method_typeofContextObjectProperty(engine, VALUE(instr.base), instr.index)); - MOTH_END_INSTR(CallBuiltinTypeofMember) - - MOTH_BEGIN_INSTR(CallBuiltinTypeofMember) - STOREVALUE(instr.result, Runtime::method_typeofMember(engine, VALUE(instr.base), instr.member)); - MOTH_END_INSTR(CallBuiltinTypeofMember) - - MOTH_BEGIN_INSTR(CallBuiltinTypeofSubscript) - STOREVALUE(instr.result, Runtime::method_typeofElement(engine, VALUE(instr.base), VALUE(instr.index))); - MOTH_END_INSTR(CallBuiltinTypeofSubscript) - - MOTH_BEGIN_INSTR(CallBuiltinTypeofName) - STOREVALUE(instr.result, Runtime::method_typeofName(engine, instr.name)); - MOTH_END_INSTR(CallBuiltinTypeofName) - - MOTH_BEGIN_INSTR(CallBuiltinTypeofValue) - STOREVALUE(instr.result, Runtime::method_typeofValue(engine, VALUE(instr.value))); - MOTH_END_INSTR(CallBuiltinTypeofValue) - - MOTH_BEGIN_INSTR(CallBuiltinDeclareVar) - Runtime::method_declareVar(engine, instr.isDeletable, instr.varName); - MOTH_END_INSTR(CallBuiltinDeclareVar) - - MOTH_BEGIN_INSTR(CallBuiltinDefineArray) - Q_ASSERT(instr.args + instr.argc <= stackSize); - QV4::Value *args = stack + instr.args; - STOREVALUE(instr.result, Runtime::method_arrayLiteral(engine, args, instr.argc)); - MOTH_END_INSTR(CallBuiltinDefineArray) - - MOTH_BEGIN_INSTR(CallBuiltinDefineObjectLiteral) - QV4::Value *args = stack + instr.args; - STOREVALUE(instr.result, Runtime::method_objectLiteral(engine, args, instr.internalClassId, instr.arrayValueCount, instr.arrayGetterSetterCountAndFlags)); - MOTH_END_INSTR(CallBuiltinDefineObjectLiteral) - - MOTH_BEGIN_INSTR(CallBuiltinSetupArgumentsObject) - STOREVALUE(instr.result, Runtime::method_setupArgumentsObject(engine)); - MOTH_END_INSTR(CallBuiltinSetupArgumentsObject) - - MOTH_BEGIN_INSTR(CallBuiltinConvertThisToObject) - Runtime::method_convertThisToObject(engine); - CHECK_EXCEPTION; - MOTH_END_INSTR(CallBuiltinConvertThisToObject) - - MOTH_BEGIN_INSTR(CreateValue) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = QV4::Primitive::undefinedValue(); - STOREVALUE(instr.result, Runtime::method_constructValue(engine, VALUE(instr.func), callData)); - MOTH_END_INSTR(CreateValue) - - MOTH_BEGIN_INSTR(CreateProperty) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = VALUE(instr.base); - STOREVALUE(instr.result, Runtime::method_constructProperty(engine, instr.name, callData)); - MOTH_END_INSTR(CreateProperty) - - MOTH_BEGIN_INSTR(ConstructPropertyLookup) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = VALUE(instr.base); - STOREVALUE(instr.result, Runtime::method_constructPropertyLookup(engine, instr.index, callData)); - MOTH_END_INSTR(ConstructPropertyLookup) - - MOTH_BEGIN_INSTR(CreateActivationProperty) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = QV4::Primitive::undefinedValue(); - STOREVALUE(instr.result, Runtime::method_constructActivationProperty(engine, instr.name, callData)); - MOTH_END_INSTR(CreateActivationProperty) - - MOTH_BEGIN_INSTR(ConstructGlobalLookup) - Q_ASSERT(instr.callData + instr.argc + offsetof(QV4::CallData, args)/sizeof(QV4::Value) <= stackSize); - QV4::CallData *callData = reinterpret_cast<QV4::CallData *>(stack + instr.callData); - callData->tag = quint32(Value::ValueTypeInternal::Integer); - callData->argc = instr.argc; - callData->thisObject = QV4::Primitive::undefinedValue(); - STOREVALUE(instr.result, Runtime::method_constructGlobalLookup(engine, instr.index, callData)); - MOTH_END_INSTR(ConstructGlobalLookup) + MOTH_BEGIN_INSTR(CreateCallContext) + Runtime::PushCallContext::call(frame); + MOTH_END_INSTR(CreateCallContext) + + MOTH_BEGIN_INSTR(PushWithContext) + STORE_IP(); + STORE_ACC(); + acc = Runtime::PushWithContext::call(engine, stack[CallData::Accumulator]); + CHECK_EXCEPTION; + MOTH_END_INSTR(PushWithContext) + + MOTH_BEGIN_INSTR(PushBlockContext) + STORE_ACC(); + Runtime::PushBlockContext::call(engine, index); + MOTH_END_INSTR(PushBlockContext) + + MOTH_BEGIN_INSTR(CloneBlockContext) + STORE_ACC(); + Runtime::CloneBlockContext::call(engine); + MOTH_END_INSTR(CloneBlockContext) + + MOTH_BEGIN_INSTR(PushScriptContext) + Runtime::PushScriptContext::call(engine, index); + MOTH_END_INSTR(PushScriptContext) + + MOTH_BEGIN_INSTR(PopScriptContext) + Runtime::PopScriptContext::call(engine); + MOTH_END_INSTR(PopScriptContext) + + MOTH_BEGIN_INSTR(PopContext) + ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context); + STACK_VALUE(CallData::Context) = c->d()->outer; + MOTH_END_INSTR(PopContext) + + MOTH_BEGIN_INSTR(GetIterator) + STORE_IP(); + STORE_ACC(); + acc = Runtime::GetIterator::call(engine, accumulator, iterator); + CHECK_EXCEPTION; + MOTH_END_INSTR(GetIterator) + + MOTH_BEGIN_INSTR(IteratorNext) + STORE_IP(); + STORE_ACC(); + acc = Runtime::IteratorNext::call(engine, accumulator, &STACK_VALUE(value)); + STACK_VALUE(done) = acc; + CHECK_EXCEPTION; + MOTH_END_INSTR(IteratorNext) + + MOTH_BEGIN_INSTR(IteratorClose) + STORE_IP(); + STORE_ACC(); + acc = Runtime::IteratorClose::call(engine, accumulator, STACK_VALUE(done)); + CHECK_EXCEPTION; + MOTH_END_INSTR(IteratorClose) + + MOTH_BEGIN_INSTR(DestructureRestElement) + STORE_IP(); + STORE_ACC(); + acc = Runtime::DestructureRestElement::call(engine, ACC); + CHECK_EXCEPTION; + MOTH_END_INSTR(DestructureRestElement) + + MOTH_BEGIN_INSTR(DeleteProperty) + acc = Runtime::DeleteProperty::call(engine, function, STACK_VALUE(base), STACK_VALUE(index)); + CHECK_EXCEPTION; + MOTH_END_INSTR(DeleteProperty) + + MOTH_BEGIN_INSTR(DeleteName) + acc = Runtime::DeleteName::call(engine, function, name); + CHECK_EXCEPTION; + MOTH_END_INSTR(DeleteName) + + MOTH_BEGIN_INSTR(TypeofName) + acc = Runtime::TypeofName::call(engine, name); + MOTH_END_INSTR(TypeofName) + + MOTH_BEGIN_INSTR(TypeofValue) + STORE_ACC(); + acc = Runtime::TypeofValue::call(engine, accumulator); + MOTH_END_INSTR(TypeofValue) + + MOTH_BEGIN_INSTR(DeclareVar) + Runtime::DeclareVar::call(engine, isDeletable, varName); + MOTH_END_INSTR(DeclareVar) + + MOTH_BEGIN_INSTR(DefineArray) + QV4::Value *arguments = stack + args; + acc = Runtime::ArrayLiteral::call(engine, arguments, argc); + MOTH_END_INSTR(DefineArray) + + MOTH_BEGIN_INSTR(DefineObjectLiteral) + QV4::Value *arguments = stack + args; + acc = Runtime::ObjectLiteral::call(engine, internalClassId, arguments, argc); + MOTH_END_INSTR(DefineObjectLiteral) + + MOTH_BEGIN_INSTR(CreateClass) + acc = Runtime::CreateClass::call(engine, classIndex, STACK_VALUE(heritage), stack + computedNames); + MOTH_END_INSTR(CreateClass) + + MOTH_BEGIN_INSTR(CreateMappedArgumentsObject) + acc = Runtime::CreateMappedArgumentsObject::call(engine); + MOTH_END_INSTR(CreateMappedArgumentsObject) + + MOTH_BEGIN_INSTR(CreateUnmappedArgumentsObject) + acc = Runtime::CreateUnmappedArgumentsObject::call(engine); + MOTH_END_INSTR(CreateUnmappedArgumentsObject) + + MOTH_BEGIN_INSTR(CreateRestParameter) + acc = Runtime::CreateRestParameter::call(engine, argIndex); + MOTH_END_INSTR(CreateRestParameter) + + MOTH_BEGIN_INSTR(ConvertThisToObject) + stack[CallData::This] = Runtime::ConvertThisToObject::call(engine, stack[CallData::This]); + CHECK_EXCEPTION; + MOTH_END_INSTR(ConvertThisToObject) + + MOTH_BEGIN_INSTR(LoadSuperConstructor) + acc = Runtime::LoadSuperConstructor::call(engine, stack[CallData::Function]); + CHECK_EXCEPTION; + MOTH_END_INSTR(LoadSuperConstructor) + + MOTH_BEGIN_INSTR(ToObject) + acc = ACC.toObject(engine)->asReturnedValue(); + CHECK_EXCEPTION; + MOTH_END_INSTR(ToObject) MOTH_BEGIN_INSTR(Jump) - code = ((const uchar *)&instr.offset) + instr.offset; + code += offset; MOTH_END_INSTR(Jump) - MOTH_BEGIN_INSTR(JumpEq) - bool cond = VALUEPTR(instr.condition)->toBoolean(); - TRACE(condition, "%s", cond ? "TRUE" : "FALSE"); - if (cond) - code = ((const uchar *)&instr.offset) + instr.offset; - MOTH_END_INSTR(JumpEq) + MOTH_BEGIN_INSTR(JumpTrue) + bool takeJump; + if (Q_LIKELY(ACC.integerCompatible())) + takeJump = ACC.int_32(); + else + takeJump = ACC.toBoolean(); + traceJumpTakesTruePath(takeJump, function, traceSlot); + if (takeJump) + code += offset; + MOTH_END_INSTR(JumpTrue) + + MOTH_BEGIN_INSTR(JumpFalse) + bool takeJump; + if (Q_LIKELY(ACC.integerCompatible())) + takeJump = !ACC.int_32(); + else + takeJump = !ACC.toBoolean(); + traceJumpTakesTruePath(!takeJump, function, traceSlot); + if (takeJump) + code += offset; + MOTH_END_INSTR(JumpFalse) + + MOTH_BEGIN_INSTR(JumpNoException) + if (!engine->hasException) + code += offset; + MOTH_END_INSTR(JumpNoException) + + MOTH_BEGIN_INSTR(JumpNotUndefined) + if (Q_LIKELY(acc != QV4::Encode::undefined())) + code += offset; + MOTH_END_INSTR(JumpNotUndefined) + + MOTH_BEGIN_INSTR(CmpEqNull) + acc = Encode(ACC.isNullOrUndefined()); + MOTH_END_INSTR(CmpEqNull) + + MOTH_BEGIN_INSTR(CmpNeNull) + acc = Encode(!ACC.isNullOrUndefined()); + MOTH_END_INSTR(CmpNeNull) + + MOTH_BEGIN_INSTR(CmpEqInt) + if (ACC.isIntOrBool()) { + acc = Encode(ACC.int_32() == lhs); + } else { + STORE_ACC(); + acc = Encode(compareEqualInt(accumulator, ACC, lhs)); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpEqInt) + + MOTH_BEGIN_INSTR(CmpNeInt) + if (ACC.isIntOrBool()) { + acc = Encode(bool(ACC.int_32() != lhs)); + } else { + STORE_ACC(); + acc = Encode(!compareEqualInt(accumulator, ACC, lhs)); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpNeInt) + + MOTH_BEGIN_INSTR(CmpEq) + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(left.asReturnedValue() == ACC.asReturnedValue())) { + acc = Encode(!ACC.isNaN()); + } else if (Q_LIKELY(left.isInteger() && ACC.isInteger())) { + acc = Encode(left.int_32() == ACC.int_32()); + } else { + STORE_ACC(); + acc = Encode(bool(Runtime::CompareEqual::call(left, accumulator))); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpEq) + + MOTH_BEGIN_INSTR(CmpNe) + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(left.isInteger() && ACC.isInteger())) { + acc = Encode(bool(left.int_32() != ACC.int_32())); + } else { + STORE_ACC(); + acc = Encode(bool(!Runtime::CompareEqual::call(left, accumulator))); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpNe) + + MOTH_BEGIN_INSTR(CmpGt) + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(left.isInteger() && ACC.isInteger())) { + acc = Encode(left.int_32() > ACC.int_32()); + } else if (left.isNumber() && ACC.isNumber()) { + acc = Encode(left.asDouble() > ACC.asDouble()); + } else { + STORE_ACC(); + acc = Encode(bool(Runtime::CompareGreaterThan::call(left, accumulator))); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpGt) + + MOTH_BEGIN_INSTR(CmpGe) + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(left.isInteger() && ACC.isInteger())) { + acc = Encode(left.int_32() >= ACC.int_32()); + } else if (left.isNumber() && ACC.isNumber()) { + acc = Encode(left.asDouble() >= ACC.asDouble()); + } else { + STORE_ACC(); + acc = Encode(bool(Runtime::CompareGreaterEqual::call(left, accumulator))); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpGe) + + MOTH_BEGIN_INSTR(CmpLt) + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(left.isInteger() && ACC.isInteger())) { + acc = Encode(left.int_32() < ACC.int_32()); + } else if (left.isNumber() && ACC.isNumber()) { + acc = Encode(left.asDouble() < ACC.asDouble()); + } else { + STORE_ACC(); + acc = Encode(bool(Runtime::CompareLessThan::call(left, accumulator))); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpLt) + + MOTH_BEGIN_INSTR(CmpLe) + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(left.isInteger() && ACC.isInteger())) { + acc = Encode(left.int_32() <= ACC.int_32()); + } else if (left.isNumber() && ACC.isNumber()) { + acc = Encode(left.asDouble() <= ACC.asDouble()); + } else { + STORE_ACC(); + acc = Encode(bool(Runtime::CompareLessEqual::call(left, accumulator))); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpLe) + + MOTH_BEGIN_INSTR(CmpStrictEqual) + if (STACK_VALUE(lhs).rawValue() == ACC.rawValue() && !ACC.isNaN()) { + acc = Encode(true); + } else { + STORE_ACC(); + acc = Runtime::StrictEqual::call(STACK_VALUE(lhs), accumulator); + CHECK_EXCEPTION; + } + MOTH_END_INSTR(CmpStrictEqual) + + MOTH_BEGIN_INSTR(CmpStrictNotEqual) + if (STACK_VALUE(lhs).rawValue() != ACC.rawValue() || ACC.isNaN()) { + STORE_ACC(); + acc = Runtime::StrictNotEqual::call(STACK_VALUE(lhs), accumulator); + CHECK_EXCEPTION; + } else { + acc = Encode(false); + } + MOTH_END_INSTR(CmpStrictNotEqual) + + MOTH_BEGIN_INSTR(CmpIn) + STORE_ACC(); + acc = Runtime::In::call(engine, STACK_VALUE(lhs), accumulator); + CHECK_EXCEPTION; + MOTH_END_INSTR(CmpIn) - MOTH_BEGIN_INSTR(JumpNe) - bool cond = VALUEPTR(instr.condition)->toBoolean(); - TRACE(condition, "%s", cond ? "TRUE" : "FALSE"); - if (!cond) - code = ((const uchar *)&instr.offset) + instr.offset; - MOTH_END_INSTR(JumpNe) + MOTH_BEGIN_INSTR(CmpInstanceOf) + STORE_ACC(); + acc = Runtime::Instanceof::call(engine, STACK_VALUE(lhs), ACC); + CHECK_EXCEPTION; + MOTH_END_INSTR(CmpInstanceOf) MOTH_BEGIN_INSTR(UNot) - STOREVALUE(instr.result, Runtime::method_uNot(VALUE(instr.source))); + if (ACC.integerCompatible()) { + acc = Encode(!static_cast<bool>(ACC.int_32())); + } else { + acc = Encode(!Value::toBooleanImpl(ACC)); + } MOTH_END_INSTR(UNot) - MOTH_BEGIN_INSTR(UNotBool) - bool b = VALUE(instr.source).booleanValue(); - VALUE(instr.result) = QV4::Encode(!b); - MOTH_END_INSTR(UNotBool) - MOTH_BEGIN_INSTR(UPlus) - STOREVALUE(instr.result, Runtime::method_uPlus(VALUE(instr.source))); + if (Q_UNLIKELY(!ACC.isNumber())) { + acc = Encode(ACC.toNumberImpl()); + CHECK_EXCEPTION; + } MOTH_END_INSTR(UPlus) MOTH_BEGIN_INSTR(UMinus) - STOREVALUE(instr.result, Runtime::method_uMinus(VALUE(instr.source))); + if (Q_LIKELY(ACC.integerCompatible())) { + int a = ACC.int_32(); + if (a == 0 || a == std::numeric_limits<int>::min()) { + acc = Encode(-static_cast<double>(a)); + traceDoubleValue(function, traceSlot); + } else { + acc = sub_int32(0, ACC.int_32(), function->traceInfo(traceSlot)); + } + } else if (ACC.isDouble()) { + acc ^= (1ull << 63); // simply flip sign bit + traceDoubleValue(function, traceSlot); + } else { + acc = Encode(-ACC.toNumberImpl()); + CHECK_EXCEPTION; + traceOtherValue(function, traceSlot); + } MOTH_END_INSTR(UMinus) MOTH_BEGIN_INSTR(UCompl) - STOREVALUE(instr.result, Runtime::method_complement(VALUE(instr.source))); + VALUE_TO_INT(a, ACC); + acc = Encode(~a); MOTH_END_INSTR(UCompl) - MOTH_BEGIN_INSTR(UComplInt) - VALUE(instr.result) = QV4::Encode((int)~VALUE(instr.source).integerValue()); - MOTH_END_INSTR(UComplInt) - MOTH_BEGIN_INSTR(Increment) - STOREVALUE(instr.result, Runtime::method_increment(VALUE(instr.source))); + if (Q_LIKELY(ACC.integerCompatible())) { + acc = add_int32(ACC.int_32(), 1, function->traceInfo(traceSlot)); + } else if (ACC.isDouble()) { + acc = QV4::Encode(ACC.doubleValue() + 1.); + traceDoubleValue(function, traceSlot); + } else { + acc = Encode(ACC.toNumberImpl() + 1.); + CHECK_EXCEPTION; + traceDoubleValue(function, traceSlot); + } MOTH_END_INSTR(Increment) MOTH_BEGIN_INSTR(Decrement) - STOREVALUE(instr.result, Runtime::method_decrement(VALUE(instr.source))); + if (Q_LIKELY(ACC.integerCompatible())) { + acc = sub_int32(ACC.int_32(), 1, function->traceInfo(traceSlot)); + } else if (ACC.isDouble()) { + acc = QV4::Encode(ACC.doubleValue() - 1.); + traceDoubleValue(function, traceSlot); + } else { + acc = Encode(ACC.toNumberImpl() - 1.); + CHECK_EXCEPTION; + traceDoubleValue(function, traceSlot); + } MOTH_END_INSTR(Decrement) - MOTH_BEGIN_INSTR(Binop) - QV4::Runtime::BinaryOperation op = *reinterpret_cast<QV4::Runtime::BinaryOperation *>(reinterpret_cast<char *>(&engine->runtime.runtimeMethods[instr.alu])); - STOREVALUE(instr.result, op(VALUE(instr.lhs), VALUE(instr.rhs))); - MOTH_END_INSTR(Binop) - MOTH_BEGIN_INSTR(Add) - STOREVALUE(instr.result, Runtime::method_add(engine, VALUE(instr.lhs), VALUE(instr.rhs))); + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(Value::integerCompatible(left, ACC))) { + acc = add_int32(left.int_32(), ACC.int_32(), function->traceInfo(traceSlot)); + } else if (left.isNumber() && ACC.isNumber()) { + acc = Encode(left.asDouble() + ACC.asDouble()); + traceDoubleValue(function, traceSlot); + } else { + STORE_ACC(); + acc = Runtime::Add::call(engine, left, accumulator); + CHECK_EXCEPTION; + traceOtherValue(function, traceSlot); + } MOTH_END_INSTR(Add) + MOTH_BEGIN_INSTR(Sub) + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(Value::integerCompatible(left, ACC))) { + acc = sub_int32(left.int_32(), ACC.int_32(), function->traceInfo(traceSlot)); + } else if (left.isNumber() && ACC.isNumber()) { + acc = Encode(left.asDouble() - ACC.asDouble()); + traceDoubleValue(function, traceSlot); + } else { + STORE_ACC(); + acc = Runtime::Sub::call(left, accumulator); + CHECK_EXCEPTION; + traceOtherValue(function, traceSlot); + } + MOTH_END_INSTR(Sub) + + MOTH_BEGIN_INSTR(Exp) + const Value left = STACK_VALUE(lhs); + double base = left.toNumber(); + double exp = ACC.toNumber(); + if (qIsInf(exp) && (base == 1 || base == -1)) + acc = Encode(qSNaN()); + else + acc = Encode(pow(base,exp)); + MOTH_END_INSTR(Exp) + + MOTH_BEGIN_INSTR(Mul) + const Value left = STACK_VALUE(lhs); + if (Q_LIKELY(Value::integerCompatible(left, ACC))) { + acc = mul_int32(left.int_32(), ACC.int_32(), function->traceInfo(traceSlot)); + } else if (left.isNumber() && ACC.isNumber()) { + acc = Encode(left.asDouble() * ACC.asDouble()); + traceDoubleValue(function, traceSlot); + } else { + STORE_ACC(); + acc = Runtime::Mul::call(left, accumulator); + CHECK_EXCEPTION; + traceOtherValue(function, traceSlot); + } + MOTH_END_INSTR(Mul) + + MOTH_BEGIN_INSTR(Div) + STORE_ACC(); + acc = Runtime::Div::call(STACK_VALUE(lhs), accumulator); + CHECK_EXCEPTION; + MOTH_END_INSTR(Div) + + MOTH_BEGIN_INSTR(Mod) + STORE_ACC(); + acc = Runtime::Mod::call(STACK_VALUE(lhs), accumulator); + CHECK_EXCEPTION; + traceValue(acc, function, traceSlot); + MOTH_END_INSTR(Mod) + MOTH_BEGIN_INSTR(BitAnd) - STOREVALUE(instr.result, Runtime::method_bitAnd(VALUE(instr.lhs), VALUE(instr.rhs))); + VALUE_TO_INT(l, STACK_VALUE(lhs)); + VALUE_TO_INT(a, ACC); + acc = Encode(l & a); MOTH_END_INSTR(BitAnd) MOTH_BEGIN_INSTR(BitOr) - STOREVALUE(instr.result, Runtime::method_bitOr(VALUE(instr.lhs), VALUE(instr.rhs))); + VALUE_TO_INT(l, STACK_VALUE(lhs)); + VALUE_TO_INT(a, ACC); + acc = Encode(l | a); MOTH_END_INSTR(BitOr) MOTH_BEGIN_INSTR(BitXor) - STOREVALUE(instr.result, Runtime::method_bitXor(VALUE(instr.lhs), VALUE(instr.rhs))); + VALUE_TO_INT(l, STACK_VALUE(lhs)); + VALUE_TO_INT(a, ACC); + acc = Encode(l ^ a); MOTH_END_INSTR(BitXor) + MOTH_BEGIN_INSTR(UShr) + VALUE_TO_INT(l, STACK_VALUE(lhs)); + VALUE_TO_INT(a, ACC); + acc = Encode(static_cast<uint>(l) >> uint(a & 0x1f)); + MOTH_END_INSTR(UShr) + MOTH_BEGIN_INSTR(Shr) - STOREVALUE(instr.result, QV4::Encode((int)(VALUEPTR(instr.lhs)->toInt32() >> (VALUEPTR(instr.rhs)->toInt32() & 0x1f)))); + VALUE_TO_INT(l, STACK_VALUE(lhs)); + VALUE_TO_INT(a, ACC); + acc = Encode(l >> (a & 0x1f)); MOTH_END_INSTR(Shr) MOTH_BEGIN_INSTR(Shl) - STOREVALUE(instr.result, QV4::Encode((int)(VALUEPTR(instr.lhs)->toInt32() << (VALUEPTR(instr.rhs)->toInt32() & 0x1f)))); + VALUE_TO_INT(l, STACK_VALUE(lhs)); + VALUE_TO_INT(a, ACC); + acc = Encode(l << (a & 0x1f)); MOTH_END_INSTR(Shl) MOTH_BEGIN_INSTR(BitAndConst) - int lhs = VALUEPTR(instr.lhs)->toInt32(); - STOREVALUE(instr.result, QV4::Encode((int)(lhs & instr.rhs))); - MOTH_END_INSTR(BitAnd) + VALUE_TO_INT(a, ACC); + acc = Encode(a & rhs); + CHECK_EXCEPTION; + MOTH_END_INSTR(BitAndConst) MOTH_BEGIN_INSTR(BitOrConst) - int lhs = VALUEPTR(instr.lhs)->toInt32(); - STOREVALUE(instr.result, QV4::Encode((int)(lhs | instr.rhs))); - MOTH_END_INSTR(BitOr) + VALUE_TO_INT(a, ACC); + acc = Encode(a | rhs); + MOTH_END_INSTR(BitOrConst) MOTH_BEGIN_INSTR(BitXorConst) - int lhs = VALUEPTR(instr.lhs)->toInt32(); - STOREVALUE(instr.result, QV4::Encode((int)(lhs ^ instr.rhs))); - MOTH_END_INSTR(BitXor) + VALUE_TO_INT(a, ACC); + acc = Encode(a ^ rhs); + MOTH_END_INSTR(BitXorConst) + + MOTH_BEGIN_INSTR(UShrConst) + acc = Encode(ACC.toUInt32() >> uint(rhs)); + MOTH_END_INSTR(UShrConst) MOTH_BEGIN_INSTR(ShrConst) - STOREVALUE(instr.result, QV4::Encode((int)(VALUEPTR(instr.lhs)->toInt32() >> instr.rhs))); + VALUE_TO_INT(a, ACC); + acc = Encode(a >> rhs); MOTH_END_INSTR(ShrConst) MOTH_BEGIN_INSTR(ShlConst) - STOREVALUE(instr.result, QV4::Encode((int)(VALUEPTR(instr.lhs)->toInt32() << instr.rhs))); + VALUE_TO_INT(a, ACC); + acc = Encode(a << rhs); MOTH_END_INSTR(ShlConst) - MOTH_BEGIN_INSTR(Mul) - STOREVALUE(instr.result, Runtime::method_mul(VALUE(instr.lhs), VALUE(instr.rhs))); - MOTH_END_INSTR(Mul) + MOTH_BEGIN_INSTR(Ret) + return acc; + MOTH_END_INSTR(Ret) - MOTH_BEGIN_INSTR(Sub) - STOREVALUE(instr.result, Runtime::method_sub(VALUE(instr.lhs), VALUE(instr.rhs))); - MOTH_END_INSTR(Sub) + MOTH_BEGIN_INSTR(InitializeBlockDeadTemporalZone) + acc = Encode(Value::emptyValue()); + for (int i = firstReg, end = firstReg + count; i < end; ++i) + STACK_VALUE(i) = acc; + MOTH_END_INSTR(InitializeBlockDeadTemporalZone) - MOTH_BEGIN_INSTR(BinopContext) - QV4::Runtime::BinaryOperationContext op = *reinterpret_cast<QV4::Runtime::BinaryOperationContext *>(reinterpret_cast<char *>(&engine->runtime.runtimeMethods[instr.alu])); - STOREVALUE(instr.result, op(engine, VALUE(instr.lhs), VALUE(instr.rhs))); - MOTH_END_INSTR(BinopContext) + MOTH_BEGIN_INSTR(ThrowOnNullOrUndefined) + if (Value::fromReturnedValue(acc).isNullOrUndefined()) { + engine->throwTypeError(); + goto handleUnwind; + } + MOTH_END_INSTR(ThrowOnNullOrUndefined) - MOTH_BEGIN_INSTR(Ret) -// TRACE(Ret, "returning value %s", result.toString(context)->toQString().toUtf8().constData()); - return VALUE(instr.result).asReturnedValue(); - MOTH_END_INSTR(Ret) + MOTH_BEGIN_INSTR(GetTemplateObject) + acc = Runtime::GetTemplateObject::call(function, index); + MOTH_END_INSTR(GetTemplateObject) -#ifndef QT_NO_QML_DEBUGGER MOTH_BEGIN_INSTR(Debug) - engine->current->lineNumber = instr.lineNumber; - QV4::Debugging::Debugger *debugger = engine->debugger(); - if (debugger && debugger->pauseAtNextOpportunity()) - debugger->maybeBreakAtInstruction(); - if (qt_v4IsDebugging) - qt_v4CheckForBreak(engine->currentContext); +#if QT_CONFIG(qml_debug) + STORE_IP(); + debug_slowPath(engine); +#endif // QT_CONFIG(qml_debug) MOTH_END_INSTR(Debug) - MOTH_BEGIN_INSTR(Line) - engine->current->lineNumber = instr.lineNumber; - if (qt_v4IsDebugging) - qt_v4CheckForBreak(engine->currentContext); - MOTH_END_INSTR(Line) -#endif // QT_NO_QML_DEBUGGER - - MOTH_BEGIN_INSTR(LoadThis) - VALUE(instr.result) = engine->currentContext->thisObject(); - MOTH_END_INSTR(LoadThis) - MOTH_BEGIN_INSTR(LoadQmlContext) - VALUE(instr.result) = Runtime::method_getQmlContext(static_cast<QV4::NoThrowEngine*>(engine)); + STACK_VALUE(result) = Runtime::LoadQmlContext::call(engine); MOTH_END_INSTR(LoadQmlContext) MOTH_BEGIN_INSTR(LoadQmlImportedScripts) - VALUE(instr.result) = Runtime::method_getQmlImportedScripts(static_cast<QV4::NoThrowEngine*>(engine)); + STACK_VALUE(result) = Runtime::LoadQmlImportedScripts::call(engine); MOTH_END_INSTR(LoadQmlImportedScripts) - MOTH_BEGIN_INSTR(LoadQmlSingleton) - VALUE(instr.result) = Runtime::method_getQmlSingleton(static_cast<QV4::NoThrowEngine*>(engine), instr.name); - MOTH_END_INSTR(LoadQmlSingleton) - -#ifdef MOTH_THREADED_INTERPRETER - // nothing to do -#else - default: - qFatal("QQmlJS::Moth::VME: Internal error - unknown instruction %d", genericInstr->common.instructionType); - break; + handleUnwind: + Q_ASSERT(engine->hasException || frame->unwindLevel); + if (!frame->unwindHandler) { + acc = Encode::undefined(); + return acc; } -#endif - - Q_ASSERT(false); - catchException: - Q_ASSERT(engine->hasException); - if (!exceptionHandler) - return QV4::Encode::undefined(); - code = exceptionHandler; + code = frame->unwindHandler; } } - -QV4::ReturnedValue VME::exec(ExecutionEngine *engine, const uchar *code) -{ - VME vme; - QV4::Debugging::Debugger *debugger = engine->debugger(); - if (debugger) - debugger->enteringFunction(); - QV4::ReturnedValue retVal = vme.run(engine, code); - if (debugger) - debugger->leavingFunction(retVal); - return retVal; -} diff --git a/src/qml/jsruntime/qv4vme_moth_p.h b/src/qml/jsruntime/qv4vme_moth_p.h index 8d46207f2b..4ac7120d36 100644 --- a/src/qml/jsruntime/qv4vme_moth_p.h +++ b/src/qml/jsruntime/qv4vme_moth_p.h @@ -52,23 +52,23 @@ // #include <private/qv4global_p.h> -#include <private/qv4runtime_p.h> -#include <private/qv4instr_moth_p.h> - -QT_REQUIRE_CONFIG(qml_interpreter); QT_BEGIN_NAMESPACE namespace QV4 { namespace Moth { +void runTracingJit(QV4::Function *function); + class VME { public: - static QV4::ReturnedValue exec(QV4::ExecutionEngine *, const uchar *); - -private: - QV4::ReturnedValue run(QV4::ExecutionEngine *, const uchar *code); + struct ExecData { + QV4::Function *function; + const QV4::ExecutionContext *scope; + }; + static QV4::ReturnedValue exec(CppStackFrame *frame, ExecutionEngine *engine); + static QV4::ReturnedValue interpret(CppStackFrame *frame, ExecutionEngine *engine, const char *codeEntry); }; } // namespace Moth diff --git a/src/qml/jsruntime/qv4vtable_p.h b/src/qml/jsruntime/qv4vtable_p.h new file mode 100644 index 0000000000..00dcb962d3 --- /dev/null +++ b/src/qml/jsruntime/qv4vtable_p.h @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QV4VTABLE_P_H +#define QV4VTABLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4global_p.h" + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +struct OwnPropertyKeyIterator { + virtual ~OwnPropertyKeyIterator() = 0; + virtual PropertyKey next(const Object *o, Property *p = nullptr, PropertyAttributes *attrs = nullptr) = 0; +}; + +struct VTable +{ + typedef void (*Destroy)(Heap::Base *); + typedef void (*MarkObjects)(Heap::Base *, MarkStack *markStack); + typedef bool (*IsEqualTo)(Managed *m, Managed *other); + + typedef ReturnedValue (*Get)(const Managed *, PropertyKey id, const Value *receiver, bool *hasProperty); + typedef bool (*Put)(Managed *, PropertyKey id, const Value &value, Value *receiver); + typedef bool (*DeleteProperty)(Managed *m, PropertyKey id); + typedef bool (*HasProperty)(const Managed *m, PropertyKey id); + typedef PropertyAttributes (*GetOwnProperty)(const Managed *m, PropertyKey id, Property *p); + typedef bool (*DefineOwnProperty)(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs); + typedef bool (*IsExtensible)(const Managed *); + typedef bool (*PreventExtensions)(Managed *); + typedef Heap::Object *(*GetPrototypeOf)(const Managed *); + typedef bool (*SetPrototypeOf)(Managed *, const Object *); + typedef qint64 (*GetLength)(const Managed *m); + typedef OwnPropertyKeyIterator *(*OwnPropertyKeys)(const Object *m, Value *target); + typedef ReturnedValue (*InstanceOf)(const Object *typeObject, const Value &var); + + typedef ReturnedValue (*Call)(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); + typedef ReturnedValue (*CallAsConstructor)(const FunctionObject *, const Value *argv, int argc, const Value *newTarget); + + const VTable * const parent; + quint16 inlinePropertyOffset; + quint16 nInlineProperties; + quint8 isExecutionContext; + quint8 isString; + quint8 isObject; + quint8 isFunctionObject; + quint8 isErrorObject; + quint8 isArrayData; + quint8 isStringOrSymbol; + quint8 type; + quint8 unused[4]; + const char *className; + + Destroy destroy; + MarkObjects markObjects; + IsEqualTo isEqualTo; + + Get get; + Put put; + DeleteProperty deleteProperty; + HasProperty hasProperty; + GetOwnProperty getOwnProperty; + DefineOwnProperty defineOwnProperty; + IsExtensible isExtensible; + PreventExtensions preventExtensions; + GetPrototypeOf getPrototypeOf; + SetPrototypeOf setPrototypeOf; + GetLength getLength; + OwnPropertyKeys ownPropertyKeys; + InstanceOf instanceOf; + + Call call; + CallAsConstructor callAsConstructor; +}; + + +struct VTableBase { +protected: + static constexpr VTable::Destroy virtualDestroy = nullptr; + static constexpr VTable::IsEqualTo virtualIsEqualTo = nullptr; + + static constexpr VTable::Get virtualGet = nullptr; + static constexpr VTable::Put virtualPut = nullptr; + static constexpr VTable::DeleteProperty virtualDeleteProperty = nullptr; + static constexpr VTable::HasProperty virtualHasProperty = nullptr; + static constexpr VTable::GetOwnProperty virtualGetOwnProperty = nullptr; + static constexpr VTable::DefineOwnProperty virtualDefineOwnProperty = nullptr; + static constexpr VTable::IsExtensible virtualIsExtensible = nullptr; + static constexpr VTable::PreventExtensions virtualPreventExtensions = nullptr; + static constexpr VTable::GetPrototypeOf virtualGetPrototypeOf = nullptr; + static constexpr VTable::SetPrototypeOf virtualSetPrototypeOf = nullptr; + static constexpr VTable::GetLength virtualGetLength = nullptr; + static constexpr VTable::OwnPropertyKeys virtualOwnPropertyKeys = nullptr; + static constexpr VTable::InstanceOf virtualInstanceOf = nullptr; + + static constexpr VTable::Call virtualCall = nullptr; + static constexpr VTable::CallAsConstructor virtualCallAsConstructor = nullptr; +}; + +#define DEFINE_MANAGED_VTABLE_INT(classname, parentVTable) \ +{ \ + parentVTable, \ + (sizeof(classname::Data) + sizeof(QV4::Value) - 1)/sizeof(QV4::Value), \ + (sizeof(classname::Data) + (classname::NInlineProperties*sizeof(QV4::Value)) + QV4::Chunk::SlotSize - 1)/QV4::Chunk::SlotSize*QV4::Chunk::SlotSize/sizeof(QV4::Value) \ + - (sizeof(classname::Data) + sizeof(QV4::Value) - 1)/sizeof(QV4::Value), \ + classname::IsExecutionContext, \ + classname::IsString, \ + classname::IsObject, \ + classname::IsFunctionObject, \ + classname::IsErrorObject, \ + classname::IsArrayData, \ + classname::IsStringOrSymbol, \ + classname::MyType, \ + { 0, 0, 0, 0 }, \ + #classname, \ + \ + classname::virtualDestroy, \ + classname::Data::markObjects, \ + classname::virtualIsEqualTo, \ + \ + classname::virtualGet, \ + classname::virtualPut, \ + classname::virtualDeleteProperty, \ + classname::virtualHasProperty, \ + classname::virtualGetOwnProperty, \ + classname::virtualDefineOwnProperty, \ + classname::virtualIsExtensible, \ + classname::virtualPreventExtensions, \ + classname::virtualGetPrototypeOf, \ + classname::virtualSetPrototypeOf, \ + classname::virtualGetLength, \ + classname::virtualOwnPropertyKeys, \ + classname::virtualInstanceOf, \ + \ + classname::virtualCall, \ + classname::virtualCallAsConstructor, \ +} + +#define DEFINE_MANAGED_VTABLE(classname) \ +const QV4::VTable classname::static_vtbl = DEFINE_MANAGED_VTABLE_INT(classname, 0) + +#define V4_OBJECT2(DataClass, superClass) \ + private: \ + DataClass() Q_DECL_EQ_DELETE; \ + Q_DISABLE_COPY(DataClass) \ + public: \ + Q_MANAGED_CHECK \ + typedef QV4::Heap::DataClass Data; \ + typedef superClass SuperClass; \ + static const QV4::VTable static_vtbl; \ + static inline const QV4::VTable *staticVTable() { return &static_vtbl; } \ + V4_MANAGED_SIZE_TEST \ + QV4::Heap::DataClass *d_unchecked() const { return static_cast<QV4::Heap::DataClass *>(m()); } \ + QV4::Heap::DataClass *d() const { \ + QV4::Heap::DataClass *dptr = d_unchecked(); \ + dptr->_checkIsInitialized(); \ + return dptr; \ + } \ + Q_STATIC_ASSERT(std::is_trivial< QV4::Heap::DataClass >::value); + +#define V4_PROTOTYPE(p) \ + static QV4::Object *defaultPrototype(QV4::ExecutionEngine *e) \ + { return e->p(); } + + +#define DEFINE_OBJECT_VTABLE_BASE(classname) \ + const QV4::VTable classname::static_vtbl = DEFINE_MANAGED_VTABLE_INT(classname, (std::is_same<classname::SuperClass, Object>::value) ? nullptr : &classname::SuperClass::static_vtbl) + +#define DEFINE_OBJECT_VTABLE(classname) \ +DEFINE_OBJECT_VTABLE_BASE(classname) + +#define DEFINE_OBJECT_TEMPLATE_VTABLE(classname) \ +template<> DEFINE_OBJECT_VTABLE_BASE(classname) + +} + +QT_END_NAMESPACE + +#endif |