blob: df9f117d04bb7b22cb74d6dab75691b3ebbddcba [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 Crimson AS <info@crimson.no>
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qv4arrayobject_p.h"
#include "qv4objectiterator_p.h"
#include "qv4arrayiterator_p.h"
#include "qv4sparsearray_p.h"
#include "qv4objectproto_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;
DEFINE_OBJECT_VTABLE(ArrayCtor);
void Heap::ArrayCtor::init(QV4::ExecutionContext *scope)
{
Heap::FunctionObject::init(scope, 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.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();
}
}
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();
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();
}
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 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->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();
uint 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.alloc(3);
ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
for (uint 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();
uint 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.alloc(3);
ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
for (uint 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(k);
}
return Encode(-1);
}
ReturnedValue ArrayPrototype::method_join(const FunctionObject *functionObject,
const Value *thisObject, const Value *argv, int argc)
{
Scope scope(functionObject);
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();
uint 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());
uint 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 (uint 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());
uint len = o->getLength();
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;
}
ScopedValue v(scope);
uint n = 0;
for (uint 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);
ScopedObject instance(scope, thisObject->toObject(scope.engine));
if (!instance)
RETURN_UNDEFINED();
uint len = instance->getLength();
ScopedValue comparefn(scope, argc ? argv[0] : Value::undefinedValue());
ArrayData::sort(scope.engine, instance, comparefn, len);
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());
uint 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 (uint 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();
}
}
uint 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(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();
uint len = instance->getLength();
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 (instance->isStringObject()) {
ScopedValue v(scope);
for (uint k = fromIndex; k < len; ++k) {
bool exists;
v = instance->get(k, &exists);
if (exists && RuntimeHelpers::strictEqual(v, searchValue))
return Encode(k);
}
return Encode(-1);
}
ScopedValue value(scope);
if (ArgumentsObject::isNonStrictArgumentsObject(instance) ||
(instance->arrayType() >= Heap::ArrayData::Sparse) || instance->protoHasArray()) {
// lets be safe and slow
for (uint i = fromIndex; i < len; ++i) {
bool exists;
value = instance->get(i, &exists);
CHECK_EXCEPTION();
if (exists && RuntimeHelpers::strictEqual(value, searchValue))
return Encode(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();
uint len = instance->getLength();
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 v(scope);
for (uint k = fromIndex; k > 0;) {
--k;
bool exists;
v = instance->get(k, &exists);
CHECK_EXCEPTION();
if (exists && RuntimeHelpers::strictEqual(v, searchValue))
return Encode(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();
uint 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.alloc(3);
bool ok = true;
for (uint 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();
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);
}
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);
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;
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();
uint 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.alloc(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;
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.alloc(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();
uint 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.alloc(3);
uint to = 0;
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;
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();
uint len = instance->getLength();
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) {
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();
uint 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();
}
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) {
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();
}