// 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 #include "qv4typedarray_p.h" #include "qv4arrayiterator_p.h" #include "qv4arraybuffer_p.h" #include "qv4symbol_p.h" #include "qv4runtime_p.h" #include #include 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)NTypedArrayTypes); static inline int toInt32(Value v) { Q_ASSERT(v.isNumber()); if (v.isInteger()) return v.integerValue(); return QJSNumberCoercion::toInteger(v.doubleValue()); } static inline double toDouble(Value v) { Q_ASSERT(v.isNumber()); if (v.isInteger()) return v.integerValue(); return v.doubleValue(); } struct ClampedUInt8 { quint8 c; }; template ReturnedValue typeToValue(T t) { return Encode(t); } template <> ReturnedValue typeToValue(ClampedUInt8 t) { return Encode(t.c); } template T valueToType(Value value) { Q_ASSERT(value.isNumber()); int n = toInt32(value); return static_cast(n); } template <> ClampedUInt8 valueToType(Value value) { Q_ASSERT(value.isNumber()); if (value.isInteger()) return { static_cast(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)) return { 0 }; if (d >= 255) return { 255 }; double f = std::floor(d); if (f + 0.5 < d) return { (quint8)(f + 1) }; if (d < f + 0.5) return { (quint8)(f) }; if (int(f) % 2) // odd number return { (quint8)(f + 1) }; return { (quint8)(f) }; } template <> float valueToType(Value value) { Q_ASSERT(value.isNumber()); double d = toDouble(value); return static_cast(d); } template <> double valueToType(Value value) { Q_ASSERT(value.isNumber()); return toDouble(value); } template ReturnedValue read(const char *data) { return typeToValue(*reinterpret_cast(data)); } template void write(char *data, Value value) { *reinterpret_cast(data) = valueToType(value); } template ReturnedValue atomicAdd(char *data, Value v) { T value = valueToType(v); typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); value = QAtomicOps::fetchAndAddOrdered(*mem, value); return typeToValue(value); } template ReturnedValue atomicAnd(char *data, Value v) { T value = valueToType(v); typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); value = QAtomicOps::fetchAndAndOrdered(*mem, value); return typeToValue(value); } template ReturnedValue atomicExchange(char *data, Value v) { T value = valueToType(v); typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); value = QAtomicOps::fetchAndStoreOrdered(*mem, value); return typeToValue(value); } template ReturnedValue atomicOr(char *data, Value v) { T value = valueToType(v); typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); value = QAtomicOps::fetchAndOrOrdered(*mem, value); return typeToValue(value); } template ReturnedValue atomicSub(char *data, Value v) { T value = valueToType(v); typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); value = QAtomicOps::fetchAndSubOrdered(*mem, value); return typeToValue(value); } template ReturnedValue atomicXor(char *data, Value v) { T value = valueToType(v); typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); value = QAtomicOps::fetchAndXorOrdered(*mem, value); return typeToValue(value); } template ReturnedValue atomicCompareExchange(char *data, Value expected, Value v) { T value = valueToType(v); T exp = valueToType(expected); typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); T old; QAtomicOps::testAndSetOrdered(*mem, exp, value, &old); return typeToValue(old); } template ReturnedValue atomicLoad(char *data) { typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); T val = QAtomicOps::loadRelaxed(*mem); return typeToValue(val); } template ReturnedValue atomicStore(char *data, Value v) { T value = valueToType(v); typename QAtomicOps::Type *mem = reinterpret_cast::Type *>(data); QAtomicOps::storeRelaxed(*mem, value); return typeToValue(value); } template constexpr TypedArrayOperations TypedArrayOperations::create(const char *name) { return { sizeof(T), name, ::read, ::write, { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }, nullptr, nullptr, nullptr }; } template constexpr TypedArrayOperations TypedArrayOperations::createWithAtomics(const char *name) { return { sizeof(T), name, ::read, ::write, { ::atomicAdd, ::atomicAnd, ::atomicExchange, ::atomicOr, ::atomicSub, ::atomicXor }, ::atomicCompareExchange, ::atomicLoad, ::atomicStore }; } const TypedArrayOperations operations[NTypedArrayTypes] = { #ifdef Q_ATOMIC_INT8_IS_SUPPORTED TypedArrayOperations::createWithAtomics("Int8Array"), TypedArrayOperations::createWithAtomics("Uint8Array"), #else TypedArrayOperations::create("Int8Array"), TypedArrayOperations::create("Uint8Array"), #endif TypedArrayOperations::createWithAtomics("Int16Array"), TypedArrayOperations::createWithAtomics("Uint16Array"), TypedArrayOperations::createWithAtomics("Int32Array"), TypedArrayOperations::createWithAtomics("Uint32Array"), TypedArrayOperations::create("Uint8ClampedArray"), TypedArrayOperations::create("Float32Array"), TypedArrayOperations::create("Float64Array") }; void Heap::TypedArrayCtor::init(QV4::ExecutionContext *scope, TypedArray::Type t) { Heap::FunctionObject::init(scope, QLatin1String(operations[t].name)); type = t; } ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { Scope scope(f->engine()); const TypedArrayCtor *that = static_cast(f); auto updateProto = [=](Scope &scope, Scoped &a) { if (newTarget->heapObject() != f->heapObject() && newTarget->isFunctionObject()) { const FunctionObject *nt = static_cast(newTarget); ScopedObject o(scope, nt->protoProperty()); if (o) a->setPrototypeOf(o); } }; if (!argc || !argv[0].isObject()) { // ECMA 6 22.2.1.1 const double l = argc ? argv[0].toInteger() : 0; if (scope.hasException()) return Encode::undefined(); if (l < 0 || l > std::numeric_limits::max()) return scope.engine->throwRangeError(QLatin1String("Index out of range.")); const double byteLength = l * operations[that->d()->type].bytesPerElement; // TODO: This is an artificial restriction due to the fact that we store the byteLength in // uint below. We should allow up to INT_MAX elements of any size. if (byteLength > std::numeric_limits::max()) return scope.engine->throwRangeError(QLatin1String("Index out of range.")); Scoped buffer(scope, scope.engine->newArrayBuffer(size_t(byteLength))); if (scope.hasException()) return Encode::undefined(); Scoped 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; updateProto(scope, array); return array.asReturnedValue(); } Scoped typedArray(scope, argc ? argv[0] : Value::undefinedValue()); if (!!typedArray) { // ECMA 6 22.2.1.2 Scoped buffer(scope, typedArray->d()->buffer); if (!buffer || buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint srcElementSize = typedArray->bytesPerElement(); uint destElementSize = operations[that->d()->type].bytesPerElement; uint byteLength = typedArray->byteLength(); uint destByteLength = byteLength*destElementSize/srcElementSize; Scoped newBuffer(scope, scope.engine->newArrayBuffer(destByteLength)); if (scope.hasException()) return Encode::undefined(); Scoped array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, newBuffer->d()); array->d()->byteLength = destByteLength; array->d()->byteOffset = 0; const char *src = buffer->constArrayData() + typedArray->byteOffset(); char *dest = newBuffer->arrayData(); // check if src and new type have the same size. In that case we can simply memcpy the data if (srcElementSize == destElementSize) { memcpy(dest, src, byteLength); } else { // not same size, we need to loop uint l = typedArray->length(); TypedArrayOperations::Read read = typedArray->d()->type->read; TypedArrayOperations::Write write =array->d()->type->write; for (uint i = 0; i < l; ++i) { Value val; val.setRawValue(read(src + i*srcElementSize)); write(dest + i*destElementSize, val); } } updateProto(scope, array); return array.asReturnedValue(); } Scoped buffer(scope, argc ? argv[0] : Value::undefinedValue()); if (!!buffer) { // ECMA 6 22.2.1.4 double dbyteOffset = argc > 1 ? argv[1].toInteger() : 0; if (buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint byteOffset = (uint)dbyteOffset; uint elementSize = operations[that->d()->type].bytesPerElement; if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->arrayDataLength()) return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset")); uint byteLength; if (argc < 3 || argv[2].isUndefined()) { byteLength = buffer->arrayDataLength() - byteOffset; if (buffer->arrayDataLength() < byteOffset || byteLength % elementSize) return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); } else { double l = qBound(0., argv[2].toInteger(), (double)UINT_MAX); if (scope.hasException()) return Encode::undefined(); if (buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); l *= elementSize; if (buffer->arrayDataLength() - byteOffset < l) return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); byteLength = (uint)l; } Scoped array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, buffer->d()); array->d()->byteLength = byteLength; array->d()->byteOffset = byteOffset; updateProto(scope, array); return array.asReturnedValue(); } // ECMA 6 22.2.1.3 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.hasException()) return scope.engine->throwTypeError(); uint elementSize = operations[that->d()->type].bytesPerElement; size_t bufferSize; if (qMulOverflow(size_t(l), size_t(elementSize), &bufferSize)) return scope.engine->throwRangeError(QLatin1String("new TypedArray: invalid length")); Scoped newBuffer(scope, scope.engine->newArrayBuffer(bufferSize)); if (scope.hasException()) return Encode::undefined(); Scoped array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, newBuffer->d()); array->d()->byteLength = l * elementSize; array->d()->byteOffset = 0; uint idx = 0; char *b = newBuffer->arrayData(); ScopedValue val(scope); while (idx < l) { val = o->get(idx); val = val->convertedToNumber(); if (scope.hasException()) return Encode::undefined(); array->d()->type->write(b, val); if (scope.hasException()) return Encode::undefined(); ++idx; b += elementSize; } updateProto(scope, array); return array.asReturnedValue(); } ReturnedValue TypedArrayCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) { return f->engine()->throwTypeError(QStringLiteral("calling a TypedArray constructor without new is invalid")); } void Heap::TypedArray::init(Type t) { Object::init(); type = operations + static_cast(t); arrayType = static_cast(t); } Heap::TypedArray *TypedArray::create(ExecutionEngine *e, Heap::TypedArray::Type t) { Scope scope(e); Scoped ic(scope, e->newInternalClass(staticVTable(), e->typedArrayPrototype + static_cast(t))); return e->memoryManager->allocObject(ic->d(), t); } ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { const bool isArrayIndex = id.isArrayIndex(); if (!isArrayIndex && !id.isCanonicalNumericIndexString()) return Object::virtualGet(m, id, receiver, hasProperty); Scope scope(static_cast(m)->engine()); Scoped a(scope, static_cast(m)); if (a->hasDetachedArrayData()) return scope.engine->throwTypeError(); if (!isArrayIndex || id.asArrayIndex() >= a->length()) { if (hasProperty) *hasProperty = false; return Encode::undefined(); } uint bytesPerElement = a->bytesPerElement(); uint byteOffset = a->byteOffset() + id.asArrayIndex() * bytesPerElement; Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength()); if (hasProperty) *hasProperty = true; return a->d()->type->read(a->constArrayData() + byteOffset); } bool TypedArray::virtualHasProperty(const Managed *m, PropertyKey id) { const bool isArrayIndex = id.isArrayIndex(); if (!isArrayIndex && !id.isCanonicalNumericIndexString()) return Object::virtualHasProperty(m, id); const TypedArray *a = static_cast(m); if (a->hasDetachedArrayData()) { a->engine()->throwTypeError(); return false; } return isArrayIndex && id.asArrayIndex() < a->length(); } PropertyAttributes TypedArray::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { if (!id.isArrayIndex() && !id.isCanonicalNumericIndexString()) return Object::virtualGetOwnProperty(m, id, p); 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) { const bool isArrayIndex = id.isArrayIndex(); if (!isArrayIndex && !id.isCanonicalNumericIndexString()) return Object::virtualPut(m, id, value, receiver); ExecutionEngine *v4 = static_cast(m)->engine(); if (v4->hasException) return false; Scope scope(v4); Scoped a(scope, static_cast(m)); if (a->hasDetachedArrayData()) return scope.engine->throwTypeError(); if (!isArrayIndex) return false; const uint index = id.asArrayIndex(); if (index >= a->length()) return false; uint bytesPerElement = a->bytesPerElement(); uint byteOffset = a->byteOffset() + index * bytesPerElement; Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength()); Value v = Value::fromReturnedValue(value.convertedToNumber()); if (scope.hasException() || a->hasDetachedArrayData()) return scope.engine->throwTypeError(); a->d()->type->write(a->arrayData() + byteOffset, v); return true; } bool TypedArray::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) { if (!id.isArrayIndex()) { return !id.isCanonicalNumericIndexString() && Object::virtualDefineOwnProperty(m, id, p, attrs); } const uint index = id.asArrayIndex(); TypedArray *a = static_cast(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->hasDetachedArrayData()) return engine->throwTypeError(); uint bytesPerElement = a->bytesPerElement(); uint byteOffset = a->byteOffset() + index * bytesPerElement; Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength()); a->d()->type->write(a->arrayData() + 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(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->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(3)); ctor->defineReadonlyProperty(engine->id_prototype(), *this); ctor->defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast(ctor->d()->type)].bytesPerElement)); ctor->setPrototypeOf(engine->intrinsicTypedArrayCtor()); setPrototypeOf(engine->intrinsicTypedArrayPrototype()); defineDefaultProperty(engine->id_constructor(), (o = ctor)); defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast(ctor->d()->type)].bytesPerElement)); } ReturnedValue IntrinsicTypedArrayPrototype::method_get_buffer(const FunctionObject *b, const Value *thisObject, const Value *, int) { ExecutionEngine *v4 = b->engine(); const TypedArray *v = thisObject->as(); if (!v) return v4->throwTypeError(); return v->d()->buffer->asReturnedValue(); } ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteLength(const FunctionObject *b, const Value *thisObject, const Value *, int) { ExecutionEngine *v4 = b->engine(); const TypedArray *v = thisObject->as(); if (!v) return v4->throwTypeError(); if (v->hasDetachedArrayData()) return Encode(0); return Encode(v->byteLength()); } ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteOffset(const FunctionObject *b, const Value *thisObject, const Value *, int) { ExecutionEngine *v4 = b->engine(); const TypedArray *v = thisObject->as(); if (!v) return v4->throwTypeError(); if (v->hasDetachedArrayData()) return Encode(0); return Encode(v->byteOffset()); } ReturnedValue IntrinsicTypedArrayPrototype::method_get_length(const FunctionObject *b, const Value *thisObject, const Value *, int) { ExecutionEngine *v4 = b->engine(); const TypedArray *v = thisObject->as(); if (!v) return v4->throwTypeError(); if (v->hasDetachedArrayData()) return Encode(0); return Encode(v->length()); } ReturnedValue IntrinsicTypedArrayPrototype::method_copyWithin(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { Scope scope(f); Scoped instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); if (!argc) return instance->asReturnedValue(); const double len = instance->length(); Q_ASSERT(std::isfinite(len)); const double target = argv[0].toInteger(); const double start = (argc > 1) ? argv[1].toInteger() : 0; const double end = (argc > 2 && !argv[2].isUndefined()) ? argv[2].toInteger() : len; const double fin = end < 0 ? std::max(len + end, 0.0) : std::min(end, len); const qsizetype from = start < 0 ? std::max(len + start, 0.0) : std::min(start, len); const qsizetype to = target < 0 ? std::max(len + target, 0.0) : std::min(target, len); const qsizetype count = std::min(fin - from, len - to); if (count <= 0) return instance->asReturnedValue(); if (from != to) { int elementSize = instance->bytesPerElement(); char *data = instance->arrayData() + instance->byteOffset(); memmove(data + to * elementSize, data + from * elementSize, count * elementSize); } return instance->asReturnedValue(); } ReturnedValue IntrinsicTypedArrayPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) { Scope scope(b); Scoped v(scope, thisObject); if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); Scoped 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 v(scope, thisObject); if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(argv); ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); ScopedValue r(scope); Value *arguments = scope.alloc(3); const char *data = v->constArrayData(); uint bytesPerElement = v->bytesPerElement(); uint byteOffset = v->byteOffset(); bool ok = true; for (uint k = 0; ok && k < len; ++k) { if (v->hasDetachedArrayData()) 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); CHECK_EXCEPTION(); ok = r->toBoolean(); } return Encode(ok); } ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); Scoped v(scope, thisObject); if (!v || v->hasDetachedArrayData()) 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(std::max(len+relativeStart, 0.)); } else { k = static_cast(std::min(relativeStart, dlen)); } if (relativeEnd < 0) { fin = static_cast(std::max(len + relativeEnd, 0.)); } else { fin = static_cast(std::min(relativeEnd, dlen)); } double val = argc ? argv[0].toNumber() : std::numeric_limits::quiet_NaN(); Value value = Value::fromDouble(val); if (scope.hasException() || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); char *data = v->arrayData(); uint bytesPerElement = v->bytesPerElement(); uint byteOffset = v->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 a(scope, constructor->callAsConstructor(arguments, 1)); if (!a || a->hasDetachedArrayData() || 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 instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(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->hasDetachedArrayData()) 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); CHECK_EXCEPTION(); if (selected->toBoolean()) { ++arguments; scope.alloc(1); ++to; } } TypedArray *a = typedArraySpeciesCreate(scope, instance, to); if (!a) return Encode::undefined(); for (uint i = 0; i < to; ++i) a->put(i, list[i]); return a->asReturnedValue(); } ReturnedValue IntrinsicTypedArrayPrototype::method_find(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); Scoped v(scope, thisObject); if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); if (!argc || !argv[0].isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(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->hasDetachedArrayData()) 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 v(scope, thisObject); if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); if (!argc || !argv[0].isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(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->hasDetachedArrayData()) 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 v(scope, thisObject); if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(argv); ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); Value *arguments = scope.alloc(3); for (uint k = 0; k < len; ++k) { if (v->hasDetachedArrayData()) 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 v(scope, thisObject); if (!v || v->hasDetachedArrayData()) 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 v(scope, thisObject); if (!v || v->hasDetachedArrayData()) 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 *functionObject, const Value *thisObject, const Value *argv, int argc) { Scope scope(functionObject); Scoped typedArray(scope, thisObject); if (!typedArray || typedArray->hasDetachedArrayData()) return scope.engine->throwTypeError(); // We cannot optimize the resolution of the argument away if length is 0. // It may have side effects. ScopedValue argument(scope, argc ? argv[0] : Value::undefinedValue()); const QString separator = argument->isUndefined() ? QStringLiteral(",") : argument->toQString(); const quint32 length = typedArray->length(); if (!length) return Encode(scope.engine->newString()); QString result; ScopedString name(scope, scope.engine->newString(QStringLiteral("0"))); ScopedValue value(scope, typedArray->get(name)); if (!value->isNullOrUndefined()) result = value->toQString(); for (quint32 i = 1; i < length; ++i) { result += separator; name = Value::fromDouble(i).toString(scope.engine); value = typedArray->get(name); CHECK_EXCEPTION(); if (!value->isNullOrUndefined()) result += value->toQString(); } return Encode(scope.engine->newString(result)); } ReturnedValue IntrinsicTypedArrayPrototype::method_keys(const FunctionObject *b, const Value *thisObject, const Value *, int) { Scope scope(b); Scoped v(scope, thisObject); if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); Scoped 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 instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) 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 instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(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->hasDetachedArrayData()) return scope.engine->throwTypeError(); arguments[0] = instance->get(k); arguments[1] = Value::fromDouble(k); arguments[2] = instance; mapped = callback->call(that, arguments, 3); CHECK_EXCEPTION(); 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 instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(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->hasDetachedArrayData()) 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); CHECK_EXCEPTION(); } ++k; } return acc->asReturnedValue(); } ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); Scoped instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(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->hasDetachedArrayData()) 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); CHECK_EXCEPTION(); } --k; } return acc->asReturnedValue(); } ReturnedValue IntrinsicTypedArrayPrototype::method_reverse(const FunctionObject *b, const Value *thisObject, const Value *, int) { Scope scope(b); Scoped instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) 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 instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); const FunctionObject *callback = static_cast(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->hasDetachedArrayData()) 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); CHECK_EXCEPTION(); 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 v(scope, thisObject); if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); Scoped 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 a(scope, *thisObject); if (!a) return scope.engine->throwTypeError(); Scoped buffer(scope, a->d()->buffer); double doffset = argc >= 2 ? argv[1].toInteger() : 0; if (scope.hasException()) RETURN_UNDEFINED(); if (!buffer || buffer->hasDetachedArrayData()) 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->bytesPerElement(); Scoped srcTypedArray(scope, argv[0]); if (!srcTypedArray) { // src is a regular object ScopedObject o(scope, argv[0].toObject(scope.engine)); if (scope.hasException() || !o) return scope.engine->throwTypeError(); double len = ScopedValue(scope, o->get(scope.engine->id_length()))->toNumber(); uint l = (uint)len; if (scope.hasException() || l != len) return scope.engine->throwTypeError(); const uint aLength = a->length(); if (offset > aLength || l > aLength - offset) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); uint idx = 0; if (buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); char *b = buffer->arrayData() + a->byteOffset() + offset*elementSize; ScopedValue val(scope); while (idx < l) { val = o->get(idx); if (scope.hasException()) return Encode::undefined(); val = val->convertedToNumber(); if (scope.hasException() || buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); a->d()->type->write(b, val); if (scope.hasException()) RETURN_UNDEFINED(); ++idx; b += elementSize; } RETURN_UNDEFINED(); } // src is a typed array Scoped srcBuffer(scope, srcTypedArray->d()->buffer); if (!srcBuffer || srcBuffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint l = srcTypedArray->length(); const uint aLength = a->length(); if (offset > aLength || l > aLength - offset) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); char *dest = buffer->arrayData() + a->byteOffset() + offset*elementSize; const char *src = srcBuffer->d()->constArrayData() + srcTypedArray->byteOffset(); if (srcTypedArray->d()->type == a->d()->type) { // same type of typed arrays, use memmove (as srcbuffer and buffer could be the same) memmove(dest, src, srcTypedArray->byteLength()); RETURN_UNDEFINED(); } 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->byteLength()]; memcpy(srcCopy, src, srcTypedArray->byteLength()); src = srcCopy; } // typed arrays of different kind, need to manually loop uint srcElementSize = srcTypedArray->bytesPerElement(); TypedArrayOperations::Read read = srcTypedArray->d()->type->read; TypedArrayOperations::Write write = a->d()->type->write; for (uint i = 0; i < l; ++i) { Value val; val.setRawValue(read(src + i*srcElementSize)); write(dest + i*elementSize, val); } if (srcCopy) delete [] srcCopy; RETURN_UNDEFINED(); } ReturnedValue IntrinsicTypedArrayPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { Scope scope(b); Scoped instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) 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) return Encode::undefined(); ScopedValue v(scope); uint n = 0; for (uint i = start; i < end; ++i) { if (instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); v = instance->get(i); if (a->hasDetachedArrayData()) 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 a(scope, *thisObject); if (!a) return scope.engine->throwTypeError(); Scoped buffer(scope, a->d()->buffer); Q_ASSERT(buffer); int len = a->length(); double b = argc > 0 ? argv[0].toInteger() : 0; if (b < 0) b = len + b; uint begin = (uint)qBound(0., b, (double)len); 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); if (end < begin) end = begin; if (scope.hasException()) RETURN_UNDEFINED(); int newLen = end - begin; ScopedFunctionObject constructor(scope, a->speciesConstructor(scope, scope.engine->typedArrayCtors + a->d()->arrayType)); if (!constructor) return scope.engine->throwTypeError(); Value *arguments = scope.alloc(3); arguments[0] = buffer; arguments[1] = Encode(a->byteOffset() + begin * a->bytesPerElement()); arguments[2] = Encode(newLen); a = constructor->callAsConstructor(arguments, 3); if (!a || a->hasDetachedArrayData()) return scope.engine->throwTypeError(); return a->asReturnedValue(); } ReturnedValue IntrinsicTypedArrayPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int) { Scope scope(b); Scoped instance(scope, thisObject); if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); 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 (uint k = 0; k < len; ++k) { if (instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); if (k) R += separator; v = instance->get(k); Q_ASSERT(!v->isNullOrUndefined()); // typed array cannot hold null or undefined ScopedObject valueAsObject(scope, v->toObject(scope.engine)); Q_ASSERT(valueAsObject); // only null or undefined cannot be converted to object 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 IntrinsicTypedArrayPrototype::method_get_toStringTag(const FunctionObject *, const Value *thisObject, const Value *, int) { const TypedArray *a = thisObject->as(); 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(); if (!a) return false; if (a->hasDetachedArrayData()) 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(); 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(); Q_ASSERT(a); if (a->length() < static_cast(len)) return scope.engine->throwTypeError(); for (int k = 0; k < len; ++k) { newObj->put(PropertyKey::fromArrayIndex(k), items[k]); } return newObj->asReturnedValue(); } ReturnedValue IntrinsicTypedArrayCtor::method_from(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { Scope scope(f); ScopedObject itemsObject(scope, argv[0]); bool usingIterator = false; 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); } // Iterator validity check goes after map function validity has been checked. if (itemsObject) { // If the object claims to support iterators, then let's try use them. ScopedValue it(scope, itemsObject->get(scope.engine->symbol_iterator())); CHECK_EXCEPTION(); if (!it->isNullOrUndefined()) { ScopedFunctionObject itfunc(scope, it); if (!itfunc) return scope.engine->throwTypeError(); usingIterator = true; } } ScopedValue thisArg(scope); if (argc > 2) thisArg = argv[2]; const FunctionObject *C = thisObject->as(); if (usingIterator) { // Item iteration supported, so let's go ahead and try use that. CHECK_EXCEPTION(); qint64 iterableLength = 0; Value *nextValue = scope.alloc(1); ScopedValue done(scope); ScopedObject lengthIterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true)); CHECK_EXCEPTION(); // symbol_iterator threw; whoops. if (!lengthIterator) { return scope.engine->throwTypeError(); // symbol_iterator wasn't an object. } forever { // Here we calculate the length of the iterable range. if (iterableLength > (static_cast(1) << 53) - 1) { ScopedValue error(scope, scope.engine->throwTypeError()); return Runtime::IteratorClose::call(scope.engine, lengthIterator); } // Retrieve the next value. If the iteration ends, we're done here. done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, lengthIterator, nextValue)); if (scope.hasException()) return Runtime::IteratorClose::call(scope.engine, lengthIterator); if (done->toBoolean()) { break; } iterableLength++; } // Constructor validity check goes after we have calculated the length, because that calculation can throw // errors that are not type errors and at least the tests expect those rather than type errors. if (!C || !C->isConstructor()) return scope.engine->throwTypeError(); ScopedObject iterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true)); CHECK_EXCEPTION(); // symbol_iterator can throw. if (!iterator) { return scope.engine->throwTypeError(); // symbol_iterator wasn't an object. } ScopedObject a(scope, Value::undefinedValue()); ScopedValue ctorArgument(scope, Value::fromReturnedValue(QV4::Encode(int(iterableLength)))); a = C->callAsConstructor(ctorArgument, 1); CHECK_EXCEPTION(); // We check exceptions above, and only after doing so, check the array's validity after construction. if (!::validateTypedArray(a) || (a->getLength() < iterableLength)) return scope.engine->throwTypeError(); // The loop below traverses the iterator, and puts elements into the created array. ScopedValue mappedValue(scope, Value::undefinedValue()); for (qint64 k = 0; k < iterableLength; ++k) { done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, iterator, nextValue)); if (scope.hasException()) return Runtime::IteratorClose::call(scope.engine, iterator); if (mapfn) { 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; } a->put(k, mappedValue); if (scope.hasException()) return Runtime::IteratorClose::call(scope.engine, iterator); } return a.asReturnedValue(); } else { // Array-like fallback. We request elements by index, and put them into the created array. 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())); int len = arrayLike->getLength(); CHECK_EXCEPTION(); // Getting the length may throw, and must do so before we check the constructor validity. if (!C || !C->isConstructor()) return scope.engine->throwTypeError(); ScopedObject a(scope, Value::undefinedValue()); ScopedValue ctorArgument(scope, Value::fromReturnedValue(QV4::Encode(len))); a = C->callAsConstructor(ctorArgument, 1); CHECK_EXCEPTION(); // We check exceptions above, and only after doing so, check the array's validity after construction. if (!::validateTypedArray(a) || (a->getLength() < len)) return scope.engine->throwTypeError(); ScopedValue mappedValue(scope, Value::undefinedValue()); ScopedValue kValue(scope); for (int k = 0; k < len; ++k) { 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; } a->put(k, mappedValue); CHECK_EXCEPTION(); } return a.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); s = scope.engine->newString(QStringLiteral("from")); ctor->defineDefaultProperty(s, IntrinsicTypedArrayCtor::method_from, 1); 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); }