Revision control

++ // Copyright (C) 2018 Crimson AS <info@crimson.no>
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qv4arrayobject_p.h"
++ #include "qv4arrayiterator_p.h"
++ #include "qv4objectproto_p.h"
++ #include "qv4argumentsobject_p.h"
++ #include "qv4runtime_p.h"
++ #include "qv4symbol_p.h"
++ #include <QtCore/qscopedvaluerollback.h>
++
++ using namespace QV4;
++
++ DEFINE_OBJECT_VTABLE(ArrayCtor);
++
++ void Heap::ArrayCtor::init(QV4::ExecutionEngine *engine)
++ {
++ Heap::FunctionObject::init(engine, QStringLiteral("Array"));
++ }
++
++ ReturnedValue ArrayCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget)
++ {
++ ExecutionEngine *v4 = static_cast<const ArrayCtor *>(f)->engine();
++ Scope scope(v4);
++ ScopedArrayObject a(scope, v4->newArrayObject());
++ if (newTarget)
++ a->setProtoFromNewTarget(newTarget);
++ uint len;
++ if (argc == 1 && argv[0].isNumber()) {
++ bool ok;
++ len = argv[0].asArrayLength(&ok);
++
++ if (!ok)
++ return v4->throwRangeError(argv[0]);
++
++ if (len < 0x1000)
++ a->arrayReserve(len);
++ } else {
++ len = argc;
++ a->arrayReserve(len);
++ a->arrayPut(0, argv, len);
++ }
++ a->setArrayLengthUnchecked(len);
++
++ return a.asReturnedValue();
++ }
++
++ ReturnedValue ArrayCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc)
++ {
++ return virtualCallAsConstructor(f, argv, argc, f);
++ }
++
++ void ArrayPrototype::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("isArray"), method_isArray, 1);
++ ctor->defineDefaultProperty(QStringLiteral("of"), method_of, 0);
++ ctor->defineDefaultProperty(QStringLiteral("from"), method_from, 1);
++ ctor->addSymbolSpecies();
++
++ Scoped<InternalClass> ic(scope, engine->classes[EngineBase::Class_Empty]
++ ->changeVTable(QV4::Object::staticVTable()));
++ ScopedObject unscopables(scope, engine->newObject(ic->d()));
++ ScopedString name(scope);
++ defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
++ defineDefaultProperty(engine->id_toString(), method_toString, 0);
++ defineDefaultProperty(engine->id_toLocaleString(), method_toLocaleString, 0);
++ defineDefaultProperty(QStringLiteral("concat"), method_concat, 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);
++ defineDefaultProperty(QStringLiteral("shift"), method_shift, 0);
++ defineDefaultProperty(QStringLiteral("slice"), method_slice, 2);
++ defineDefaultProperty(QStringLiteral("sort"), method_sort, 1);
++ defineDefaultProperty(QStringLiteral("splice"), method_splice, 2);
++ defineDefaultProperty(QStringLiteral("unshift"), method_unshift, 1);
++ defineDefaultProperty(QStringLiteral("indexOf"), method_indexOf, 1);
++ defineDefaultProperty(QStringLiteral("lastIndexOf"), method_lastIndexOf, 1);
++ defineDefaultProperty(QStringLiteral("every"), method_every, 1);
++ defineDefaultProperty(QStringLiteral("some"), method_some, 1);
++ defineDefaultProperty(QStringLiteral("forEach"), method_forEach, 1);
++ defineDefaultProperty(QStringLiteral("map"), method_map, 1);
++ 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 ? Value::fromReturnedValue(QV4::Encode(len))
++ : Value::undefinedValue());
++ a = ctor->callAsConstructor(argument, useLen ? 1 : 0);
++ } else {
++ a = scope.engine->newArrayObject(len);
++ }
++
++ return a;
++ }
++
++ ReturnedValue ArrayPrototype::method_from(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
++ {
++ 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.constructUndefined(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.constructUndefined(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 error(scope, scope.engine->throwTypeError());
++ return Runtime::IteratorClose::call(scope.engine, iterator);
++ }
++
++ // 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) {
++ Q_ASSERT(mapArguments); // if mapfn is set, we always setup mapArguments with scope.alloc
++ mapArguments[0] = *nextValue;
++ mapArguments[1] = Value::fromDouble(k);
++ mappedValue = mapfn->call(thisArg, mapArguments, 2);
++ if (scope.hasException())
++ return Runtime::IteratorClose::call(scope.engine, iterator);
++ } 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.hasException())
++ return Runtime::IteratorClose::call(scope.engine, iterator);
++
++ 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) {
++ Q_ASSERT(mapArguments); // if mapfn is set, we always setup mapArguments with scope.alloc
++ 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();
++ }
++
++ }
++
++ ReturnedValue ArrayPrototype::method_of(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(builtin);
++ ScopedFunctionObject that(scope, thisObject);
++ ScopedObject a(createObjectFromCtorOrArray(scope, that, true, argc));
++ CHECK_EXCEPTION();
++
++ 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++;
++ }
++
++ // 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();
++ }
++
++ ReturnedValue ArrayPrototype::method_toString(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
++ {
++ 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 checkedResult(scope.engine, 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();
++
++ qint64 len = instance->getLength();
++ const QString separator = QStringLiteral(",");
++
++ QString R;
++
++ ScopedValue v(scope);
++ ScopedString s(scope);
++
++ ScopedPropertyKey tolocaleString(scope, scope.engine->id_toLocaleString()->toPropertyKey());
++ Q_ASSERT(!scope.engine->hasException);
++
++ for (qint64 k = 0; k < len; ++k) {
++ if (k)
++ R += separator;
++
++ v = instance->get(k);
++ if (v->isNullOrUndefined())
++ continue;
++
++ ScopedObject valueAsObject(scope, v->toObject(scope.engine));
++ Q_ASSERT(valueAsObject); // null and undefined handled above
++
++ ScopedFunctionObject function(scope, valueAsObject->get(tolocaleString));
++ if (!function)
++ return scope.engine->throwTypeError();
++
++ v = function->call(valueAsObject, nullptr, 0);
++ if (scope.hasException())
++ return Encode::undefined();
++
++ s = v->toString(scope.engine);
++ if (scope.hasException())
++ return Encode::undefined();
++
++ R += s->toQString();
++ }
++ return scope.engine->newString(R)->asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_concat(const FunctionObject *b, const Value *that, const Value *argv, int argc)
++ {
++ Scope scope(b);
++ ScopedObject thisObject(scope, that->toObject(scope.engine));
++ if (!thisObject)
++ RETURN_UNDEFINED();
++
++ ScopedArrayObject result(scope, scope.engine->newArrayObject());
++
++ ScopedArrayObject elt(scope);
++ ScopedObject eltAsObj(scope);
++ ScopedValue entry(scope);
++ 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 qint64 len = eltAsObj->getLength();
++ if (scope.hasException())
++ return Encode::undefined();
++
++ for (qint64 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->isArrayLike()) {
++ const uint startIndex = result->getLength();
++ for (qint64 i = 0, len = eltAsObj->getLength(); i < len; ++i) {
++ entry = eltAsObj->get(i);
++ // spec says not to throw if this fails
++ result->put(startIndex + i, entry);
++ }
++ } else {
++ result->arraySet(result->getLength(), *v);
++ }
++ }
++
++ 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();
++ }
++
++ ReturnedValue ArrayPrototype::method_find(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();
++
++ qint64 len = instance->getLength();
++
++ if (!argc || !argv[0].isFunctionObject())
++ THROW_TYPE_ERROR();
++ const FunctionObject *callback = static_cast<const FunctionObject *>(argv);
++
++ ScopedValue result(scope);
++ Value *arguments = scope.constructUndefined(3);
++
++ ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
++
++ for (qint64 k = 0; k < len; ++k) {
++ arguments[0] = instance->get(k);
++ CHECK_EXCEPTION();
++
++ arguments[1] = Value::fromDouble(k);
++ arguments[2] = instance;
++ result = callback->call(that, arguments, 3);
++
++ CHECK_EXCEPTION();
++ if (result->toBoolean())
++ return arguments[0].asReturnedValue();
++ }
++
++ RETURN_UNDEFINED();
++ }
++
++ ReturnedValue ArrayPrototype::method_findIndex(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();
++
++ qint64 len = instance->getLength();
++
++ if (!argc || !argv[0].isFunctionObject())
++ THROW_TYPE_ERROR();
++ const FunctionObject *callback = static_cast<const FunctionObject *>(argv);
++
++ ScopedValue result(scope);
++ Value *arguments = scope.constructUndefined(3);
++
++ ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
++
++ for (qint64 k = 0; k < len; ++k) {
++ arguments[0] = instance->get(k);
++ CHECK_EXCEPTION();
++
++ arguments[1] = Value::fromDouble(k);
++ arguments[2] = instance;
++ result = callback->call(that, arguments, 3);
++
++ CHECK_EXCEPTION();
++ if (result->toBoolean())
++ return Encode(double(k));
++ }
++
++ return Encode(-1);
++ }
++
++ ReturnedValue ArrayPrototype::method_join(const FunctionObject *functionObject,
++ const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(functionObject);
++ CHECK_STACK_LIMITS(scope.engine)
++ ScopedObject instance(scope, thisObject->toObject(scope.engine));
++
++ if (!instance)
++ return Encode(scope.engine->newString());
++
++ // We cannot optimize the resolution of the argument away in case of length == 0
++ // It may have side effects.
++ ScopedValue argument(scope, argc ? argv[0] : Value::undefinedValue());
++ const QString separator = argument->isUndefined()
++ ? QStringLiteral(",")
++ : argument->toQString();
++
++ ScopedValue scopedLength(scope, instance->get(scope.engine->id_length()));
++ const quint32 genericLength = scopedLength->isUndefined() ? 0 : scopedLength->toUInt32();
++ if (!genericLength)
++ return Encode(scope.engine->newString());
++
++ QString result;
++ if (auto *arrayObject = instance->as<ArrayObject>()) {
++ ScopedValue entry(scope);
++ const qint64 arrayLength = arrayObject->getLength();
++ Q_ASSERT(arrayLength >= 0);
++ Q_ASSERT(arrayLength <= std::numeric_limits<quint32>::max());
++ for (quint32 i = 0; i < quint32(arrayLength); ++i) {
++ if (i)
++ result += separator;
++
++ entry = arrayObject->get(i);
++ CHECK_EXCEPTION();
++ if (!entry->isNullOrUndefined())
++ result += entry->toQString();
++ }
++ } else {
++ ScopedString name(scope, scope.engine->newString(QStringLiteral("0")));
++ ScopedValue value(scope, instance->get(name));
++ CHECK_EXCEPTION();
++
++ if (!value->isNullOrUndefined())
++ result = value->toQString();
++
++ for (quint32 i = 1; i < genericLength; ++i) {
++ result += separator;
++
++ name = Value::fromDouble(i).toString(scope.engine);
++ value = instance->get(name);
++ CHECK_EXCEPTION();
++
++ if (!value->isNullOrUndefined())
++ result += value->toQString();
++ }
++ }
++
++ return Encode(scope.engine->newString(result));
++ }
++
++ ReturnedValue ArrayPrototype::method_pop(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(b);
++ ScopedObject instance(scope, thisObject->toObject(scope.engine));
++ if (!instance)
++ RETURN_UNDEFINED();
++
++ qint64 len = instance->getLength();
++
++ if (!len) {
++ if (!instance->isArrayObject())
++ instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromInt32(0)));
++ RETURN_UNDEFINED();
++ }
++
++ ScopedValue result(scope, instance->get(len - 1));
++ CHECK_EXCEPTION();
++
++ if (!instance->deleteProperty(PropertyKey::fromArrayIndex(len - 1)))
++ return scope.engine->throwTypeError();
++
++ if (instance->isArrayObject())
++ instance->setArrayLength(len - 1);
++ else {
++ if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(len - 1))))
++ return scope.engine->throwTypeError();
++ }
++ return result->asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_push(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();
++
++ instance->arrayCreate();
++ Q_ASSERT(instance->arrayData());
++
++ qint64 len = instance->getLength();
++
++ if (len + quint64(argc) >= UINT_MAX) {
++ // ughh... this goes beyond UINT_MAX
++ double l = len;
++ ScopedString s(scope);
++ 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 + 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")));
++ return scope.engine->throwRangeError(str);
++ }
++ return Encode(newLen);
++ }
++
++ if (!argc)
++ ;
++ else if (!instance->protoHasArray() && instance->arrayData()->length() <= len && instance->arrayData()->type == Heap::ArrayData::Simple) {
++ instance->arrayData()->vtable()->putArray(instance, len, argv, argc);
++ len = instance->arrayData()->length();
++ } else {
++ 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 {
++ if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(len))))
++ return scope.engine->throwTypeError();
++ }
++
++ return Encode(uint(len));
++ }
++
++ ReturnedValue ArrayPrototype::method_reverse(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(b);
++ ScopedObject instance(scope, thisObject->toObject(scope.engine));
++ if (!instance)
++ RETURN_UNDEFINED();
++
++ 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;
++
++ ScopedValue lval(scope);
++ ScopedValue hval(scope);
++ for (; lo < hi; ++lo, --hi) {
++ bool loExists, hiExists;
++ lval = instance->get(lo, &loExists);
++ hval = instance->get(hi, &hiExists);
++ CHECK_EXCEPTION();
++ bool ok;
++ if (hiExists)
++ ok = instance->put(lo, hval);
++ else
++ 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();
++ }
++ return instance->asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_shift(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(b);
++ ScopedObject instance(scope, thisObject->toObject(scope.engine));
++ if (!instance)
++ RETURN_UNDEFINED();
++
++ instance->arrayCreate();
++ Q_ASSERT(instance->arrayData());
++
++ qint64 len = instance->getLength();
++
++ if (!len) {
++ if (!instance->isArrayObject())
++ 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) {
++ result = instance->arrayData()->vtable()->pop_front(instance);
++ } else {
++ result = instance->get(uint(0));
++ CHECK_EXCEPTION();
++ ScopedValue v(scope);
++ // do it the slow way
++ for (qint64 k = 1; k < len; ++k) {
++ bool exists;
++ v = instance->get(k, &exists);
++ CHECK_EXCEPTION();
++ bool ok;
++ if (exists)
++ ok = instance->put(k - 1, v);
++ else
++ ok = instance->deleteProperty(PropertyKey::fromArrayIndex(k - 1));
++ if (!ok)
++ return scope.engine->throwTypeError();
++ }
++ bool ok = instance->deleteProperty(PropertyKey::fromArrayIndex(len - 1));
++ if (!ok)
++ return scope.engine->throwTypeError();
++ }
++
++ if (instance->isArrayObject())
++ instance->setArrayLengthUnchecked(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();
++ }
++
++ ReturnedValue ArrayPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(b);
++ ScopedObject o(scope, thisObject->toObject(scope.engine));
++ if (!o)
++ RETURN_UNDEFINED();
++
++ ScopedArrayObject result(scope, scope.engine->newArrayObject());
++ qint64 len = o->getLength();
++ double s = (argc ? argv[0] : Value::undefinedValue()).toInteger();
++ qint64 start;
++ if (s < 0)
++ start = static_cast<qint64>(qMax(double(len) + s, 0.));
++ else if (s > len)
++ start = len;
++ else
++ start = qint64(s);
++ qint64 end = len;
++ if (argc > 1 && !argv[1].isUndefined()) {
++ double e = argv[1].toInteger();
++ if (e < 0)
++ end = static_cast<qint64>(qMax(double(len) + e, 0.));
++ else if (e > len)
++ end = len;
++ else
++ end = qint64(e);
++ }
++
++ if (end > start && qint64(std::numeric_limits<quint32>::max()) < end - start)
++ return scope.engine->throwRangeError(QString::fromLatin1("Array length out of range."));
++
++ ScopedValue v(scope);
++ qint64 n = 0;
++ for (qint64 i = start; i < end; ++i) {
++ bool exists;
++ v = o->get(i, &exists);
++ CHECK_EXCEPTION();
++ if (exists)
++ result->arraySet(n, v);
++ ++n;
++ }
++ return result->asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_sort(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++
++ Scope scope(b);
++
++ ScopedValue comparefn(scope, argc ? argv[0] : Value::undefinedValue());
++
++ // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
++ if (!comparefn->isUndefined() && !comparefn->isFunctionObject())
++ return scope.engine->throwTypeError(QStringLiteral("The provided comparison function is not callable."));
++
++ // 2. Let obj be ? ToObject(this value).
++ ScopedObject instance(scope, thisObject->toObject(scope.engine));
++ if (!instance)
++ RETURN_UNDEFINED();
++
++ // 3. Let len be ? LengthOfArrayLike(obj).
++ qint64 len = instance->getLength();
++
++ if (instance->arrayData() && instance->arrayData()->length()) {
++ ArrayData::sort(scope.engine, instance, comparefn, len);
++ } else {
++ // Generic implementation that does not require a populated
++ // ArrayData, this is used, for example, by `Sequences` which
++ // store their data in a different way.
++
++ // 5. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, skip-holes)
++ Value* sorted = scope.constructUndefined(scope.engine->safeForAllocLength(len));
++ CHECK_EXCEPTION();
++
++ uint written = 0;
++ for (uint index = 0; index < len; ++index) {
++ bool hasProperty = false;
++ auto element = instance->get(index, &hasProperty);
++
++ if (hasProperty) {
++ sorted[written] = element;
++ ++written;
++ }
++ }
++
++ std::stable_sort(sorted, sorted + written, ArrayElementLessThan(scope.engine, comparefn));
++
++ // [...]
++ // 8. Repeat, while j < itemCount,
++ // a. Perform ? Set(obj, ! ToString(𝔽(j)), sortedList[j], true).
++ // [...]
++ for (uint index = 0; index < written; ++index) {
++ instance->setIndexed(index, sorted[index], QV4::Object::DoThrowOnRejection);
++ CHECK_EXCEPTION();
++ }
++
++ // [...]
++ // 10. Repeat, while j < len,
++ // a. Perform ? DeletePropertyOrThrow(obj, ! ToString(𝔽(j))).
++ // [...]
++ while (written < len) {
++ if (!instance->deleteProperty(PropertyKey::fromArrayIndex(written)))
++ return scope.engine->throwTypeError();
++ ++written;
++ }
++ }
++
++ // 11. Return obj
++ return thisObject->asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_splice(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();
++
++ qint64 len = instance->getLength();
++
++ double rs = (argc ? argv[0] : Value::undefinedValue()).toInteger();
++ qint64 start;
++ if (rs < 0)
++ start = static_cast<qint64>(qMax(0., len + rs));
++ else
++ 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)));
++ }
++
++ 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->get(start + i, &exists);
++ CHECK_EXCEPTION();
++ if (exists)
++ newArray->arrayPut(i, v);
++ }
++ newArray->setArrayLengthUnchecked(deleteCount);
++
++
++ if (itemCount < deleteCount) {
++ for (uint k = start; k < len - deleteCount; ++k) {
++ bool exists;
++ v = instance->get(k + deleteCount, &exists);
++ CHECK_EXCEPTION();
++ bool ok;
++ if (exists)
++ ok = instance->put(k + itemCount, v);
++ else
++ ok = instance->deleteProperty(PropertyKey::fromArrayIndex(k + itemCount));
++ if (!ok)
++ return scope.engine->throwTypeError();
++ }
++ for (uint k = len; k > len - deleteCount + itemCount; --k) {
++ 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->get(k + deleteCount - 1, &exists);
++ CHECK_EXCEPTION();
++ bool ok;
++ if (exists)
++ ok = instance->put(k + itemCount - 1, v);
++ else
++ ok = instance->deleteProperty(PropertyKey::fromArrayIndex(k + itemCount - 1));
++ if (!ok)
++ return scope.engine->throwTypeError();
++ --k;
++ }
++ }
++
++ for (uint i = 0; i < itemCount; ++i)
++ instance->put(start + i, argv[i + 2]);
++
++ if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(len - deleteCount + itemCount))))
++ return scope.engine->throwTypeError();
++
++ return newArray->asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_unshift(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();
++
++ instance->arrayCreate();
++ Q_ASSERT(instance->arrayData());
++
++ qint64 len = instance->getLength();
++
++ if (!instance->protoHasArray() && !instance->arrayData()->attrs && instance->arrayData()->length() <= len &&
++ instance->arrayData()->type != Heap::ArrayData::Custom) {
++ instance->arrayData()->vtable()->push_front(instance, argv, argc);
++ } else {
++ ScopedValue v(scope);
++ for (qint64 k = len; k > 0; --k) {
++ bool exists;
++ v = instance->get(k - 1, &exists);
++ bool ok;
++ if (exists)
++ ok = instance->put(k + argc - 1, v);
++ else
++ 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();
++ }
++ }
++
++ qint64 newLen = len + argc;
++ if (instance->isArrayObject())
++ instance->setArrayLengthUnchecked(newLen);
++ else {
++ if (!instance->put(scope.engine->id_length(), ScopedValue(scope, Value::fromDouble(newLen))))
++ return scope.engine->throwTypeError();
++ }
++
++ return Encode(double(newLen));
++ }
++
++ ReturnedValue ArrayPrototype::method_includes(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();
++
++ 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 val(scope);
++ while (k < len) {
++ val = 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();
++
++ qint64 len = instance->getLength();
++ if (!len)
++ return Encode(-1);
++
++ ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue());
++ qint64 fromIndex = 0;
++
++ if (argc >= 2) {
++ double f = argv[1].toInteger();
++ CHECK_EXCEPTION();
++ if (f >= len)
++ return Encode(-1);
++ if (f < 0)
++ f = qMax(double(len) + f, 0.);
++ fromIndex = qint64(f);
++ }
++
++ if (instance->isStringObject()) {
++ ScopedValue v(scope);
++ for (qint64 k = fromIndex; k < len; ++k) {
++ bool exists;
++ v = instance->get(k, &exists);
++ if (exists && RuntimeHelpers::strictEqual(v, searchValue))
++ return Encode(double(k));
++ }
++ return Encode(-1);
++ }
++
++ ScopedValue value(scope);
++
++ if (ArgumentsObject::isNonStrictArgumentsObject(instance) ||
++ (instance->arrayType() >= Heap::ArrayData::Sparse) || instance->protoHasArray()) {
++ // lets be safe and slow
++ for (qint64 i = fromIndex; i < len; ++i) {
++ bool exists;
++ value = instance->get(i, &exists);
++ CHECK_EXCEPTION();
++ if (exists && RuntimeHelpers::strictEqual(value, searchValue))
++ return Encode(double(i));
++ }
++ } else if (!instance->arrayData()) {
++ return Encode(-1);
++ } else {
++ 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;
++ uint idx = fromIndex;
++ while (idx < len) {
++ value = sa->data(idx);
++ CHECK_EXCEPTION();
++ if (RuntimeHelpers::strictEqual(value, searchValue))
++ return Encode(idx);
++ ++idx;
++ }
++ }
++ 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();
++ }
++
++ ReturnedValue ArrayPrototype::method_lastIndexOf(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();
++
++ qint64 len = instance->getLength();
++ if (!len)
++ return Encode(-1);
++
++ ScopedValue searchValue(scope);
++ qint64 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 = double(len) + f;
++ if (f < 0)
++ return Encode(-1);
++ }
++ fromIndex = qint64(f) + 1;
++ }
++
++ ScopedValue v(scope);
++ for (qint64 k = fromIndex; k > 0;) {
++ --k;
++ bool exists;
++ v = instance->get(k, &exists);
++ CHECK_EXCEPTION();
++ if (exists && RuntimeHelpers::strictEqual(v, searchValue))
++ return Encode(double(k));
++ }
++ return Encode(-1);
++ }
++
++ ReturnedValue ArrayPrototype::method_every(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();
++
++ qint64 len = instance->getLength();
++
++ 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.constructUndefined(3);
++
++ bool ok = true;
++ for (qint64 k = 0; ok && k < len; ++k) {
++ bool exists;
++ arguments[0] = instance->get(k, &exists);
++ if (!exists)
++ continue;
++
++ arguments[1] = Value::fromDouble(k);
++ arguments[2] = instance;
++ r = callback->call(that, arguments, 3);
++ CHECK_EXCEPTION();
++ ok = r->toBoolean();
++ }
++ return Encode(ok);
++ }
++
++ ReturnedValue ArrayPrototype::method_fill(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();
++
++ const qsizetype len = instance->getLength();
++ Q_ASSERT(len >= 0);
++
++ const qsizetype relativeStart = argc > 1 ? argv[1].toInteger() : 0;
++ qsizetype relativeEnd = len;
++ if (argc > 2 && !argv[2].isUndefined())
++ relativeEnd = argv[2].toInteger();
++
++ qsizetype k = 0;
++ qsizetype fin = 0;
++
++ if (relativeStart < 0) {
++ if (relativeStart > -len)
++ k = std::max(len + relativeStart, qsizetype(0));
++ } else {
++ k = std::min(relativeStart, len);
++ }
++ Q_ASSERT(k >= 0);
++
++ if (relativeEnd < 0) {
++ if (relativeEnd > -len)
++ fin = std::max(len + relativeEnd, qsizetype(0));
++ } else {
++ fin = std::min(relativeEnd, len);
++ }
++ Q_ASSERT(fin >= 0);
++
++ if (sizeof(qsizetype) > sizeof(uint) && fin > qsizetype(std::numeric_limits<uint>::max()))
++ return scope.engine->throwRangeError(QString::fromLatin1("Array length out of range."));
++
++ for (; k < fin; ++k)
++ instance->setIndexed(uint(k), argv[0], QV4::Object::DoThrowOnRejection);
++
++ 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();
++
++ qint64 len = instance->getLength();
++
++ 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.constructUndefined(3);
++
++ for (qint64 k = 0; k < len; ++k) {
++ 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);
++ CHECK_EXCEPTION();
++ if (result->toBoolean())
++ return Encode(true);
++ }
++ return Encode(false);
++ }
++
++ ReturnedValue ArrayPrototype::method_forEach(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();
++
++ qint64 len = instance->getLength();
++
++ 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.constructUndefined(3);
++
++ for (qint64 k = 0; k < len; ++k) {
++ bool exists;
++ arguments[0] = instance->get(k, &exists);
++ if (!exists)
++ continue;
++
++ arguments[1] = Value::fromDouble(k);
++ arguments[2] = instance;
++ callback->call(that, arguments, 3);
++ }
++ RETURN_UNDEFINED();
++ }
++
++ ReturnedValue ArrayPrototype::method_map(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();
++
++ qint64 len = instance->getLength();
++
++ 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);
++
++ ScopedValue v(scope);
++ ScopedValue mapped(scope);
++ ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
++ Value *arguments = scope.constructUndefined(3);
++
++ for (uint k = 0; k < len; ++k) {
++ bool exists;
++ arguments[0] = instance->get(k, &exists);
++ if (!exists)
++ continue;
++
++ arguments[1] = Value::fromDouble(k);
++ arguments[2] = instance;
++ mapped = callback->call(that, arguments, 3);
++ CHECK_EXCEPTION();
++ a->arraySet(k, mapped);
++ }
++ return a.asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_filter(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();
++
++ qint64 len = instance->getLength();
++
++ if (!argc || !argv->isFunctionObject())
++ THROW_TYPE_ERROR();
++ const FunctionObject *callback = static_cast<const FunctionObject *>(argv);
++
++ ScopedArrayObject a(scope, scope.engine->newArrayObject());
++ a->arrayReserve(len);
++
++ ScopedValue selected(scope);
++ ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
++ Value *arguments = scope.constructUndefined(3);
++
++ uint to = 0;
++ for (qint64 k = 0; k < len; ++k) {
++ 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);
++ CHECK_EXCEPTION();
++ if (selected->toBoolean()) {
++ a->arraySet(to, arguments[0]);
++ ++to;
++ }
++ }
++ return a.asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_reduce(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();
++
++ qint64 len = instance->getLength();
++
++ if (!argc || !argv->isFunctionObject())
++ THROW_TYPE_ERROR();
++ const FunctionObject *callback = static_cast<const FunctionObject *>(argv);
++
++ qint64 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.constructUndefined(4);
++
++ while (k < len) {
++ 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);
++ CHECK_EXCEPTION();
++ }
++ ++k;
++ }
++ return acc->asReturnedValue();
++ }
++
++ ReturnedValue ArrayPrototype::method_reduceRight(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();
++
++ qint64 len = instance->getLength();
++
++ 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();
++ }
++
++ qint64 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.constructUndefined(4);
++
++ while (k > 0) {
++ 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);
++ CHECK_EXCEPTION();
++ }
++ --k;
++ }
++ 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();
++ }