diff options
Diffstat (limited to 'src/qml/jsruntime/qv4typedarray.cpp')
-rw-r--r-- | src/qml/jsruntime/qv4typedarray.cpp | 1582 |
1 files changed, 1341 insertions, 241 deletions
diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index fe27d7c2d2..d83f021450 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -36,169 +36,241 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ + #include "qv4typedarray_p.h" +#include "qv4arrayiterator_p.h" #include "qv4arraybuffer_p.h" #include "qv4string_p.h" +#include "qv4jscall_p.h" +#include "qv4symbol_p.h" +#include "qv4runtime_p.h" +#include <QtCore/qatomic.h> #include <cmath> using namespace QV4; +DEFINE_OBJECT_VTABLE(IntrinsicTypedArrayCtor); +DEFINE_OBJECT_VTABLE(IntrinsicTypedArrayPrototype); DEFINE_OBJECT_VTABLE(TypedArrayCtor); DEFINE_OBJECT_VTABLE(TypedArrayPrototype); DEFINE_OBJECT_VTABLE(TypedArray); -Q_STATIC_ASSERT((int)ExecutionEngine::NTypedArrayTypes == (int)Heap::TypedArray::NTypes); +Q_STATIC_ASSERT((int)ExecutionEngine::NTypedArrayTypes == (int)NTypedArrayTypes); -ReturnedValue Int8ArrayRead(const char *data, int index) +static inline int toInt32(Value v) { - return Encode((int)(signed char)data[index]); + Q_ASSERT(v.isNumber()); + if (v.isInteger()) + return v.integerValue(); + return Double::toInt32(v.doubleValue()); } -void Int8ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +static inline double toDouble(Value v) { - signed char v = (signed char)value.toUInt32(); - if (e->hasException) - return; - data[index] = v; + Q_ASSERT(v.isNumber()); + if (v.isInteger()) + return v.integerValue(); + return v.doubleValue(); } -ReturnedValue UInt8ArrayRead(const char *data, int index) -{ - return Encode((int)(unsigned char)data[index]); +struct ClampedUInt8 { + quint8 c; +}; + +template <typename T> +ReturnedValue typeToValue(T t) { + return Encode(t); +} + +template <> +ReturnedValue typeToValue(ClampedUInt8 t) { + return Encode(t.c); } -void UInt8ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +T valueToType(Value value) { - unsigned char v = (unsigned char)value.toUInt32(); - if (e->hasException) - return; - data[index] = v; + Q_ASSERT(value.isNumber()); + int n = toInt32(value); + return static_cast<T>(n); } -void UInt8ClampedArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <> +ClampedUInt8 valueToType(Value value) { - if (value.isInteger()) { - data[index] = (char)(unsigned char)qBound(0, value.integerValue(), 255); - return; - } - double d = value.toNumber(); - if (e->hasException) - return; + Q_ASSERT(value.isNumber()); + if (value.isInteger()) + return { static_cast<quint8>(qBound(0, value.integerValue(), 255)) }; + Q_ASSERT(value.isDouble()); + double d = value.doubleValue(); // ### is there a way to optimise this? - if (d <= 0 || std::isnan(d)) { - data[index] = 0; - return; - } - if (d >= 255) { - data[index] = (char)(255); - return; - } + if (d <= 0 || std::isnan(d)) + return { 0 }; + if (d >= 255) + return { 255 }; double f = std::floor(d); - if (f + 0.5 < d) { - data[index] = (unsigned char)(f + 1); - return; - } - if (d < f + 0.5) { - data[index] = (unsigned char)(f); - return; - } - if (int(f) % 2) { + if (f + 0.5 < d) + return { (quint8)(f + 1) }; + if (d < f + 0.5) + return { (quint8)(f) }; + if (int(f) % 2) // odd number - data[index] = (unsigned char)(f + 1); - return; - } - data[index] = (unsigned char)(f); + return { (quint8)(f + 1) }; + return { (quint8)(f) }; +} + +template <> +float valueToType(Value value) +{ + Q_ASSERT(value.isNumber()); + double d = toDouble(value); + return static_cast<float>(d); +} + +template <> +double valueToType(Value value) +{ + Q_ASSERT(value.isNumber()); + return toDouble(value); } -ReturnedValue Int16ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue read(const char *data) { + return typeToValue(*reinterpret_cast<const T *>(data)); +} +template <typename T> +void write(char *data, Value value) { - return Encode((int)*(const short *)(data + index)); + *reinterpret_cast<T *>(data) = valueToType<T>(value); } -void Int16ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicAdd(char *data, Value v) { - short v = (short)value.toInt32(); - if (e->hasException) - return; - *(short *)(data + index) = v; + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndAddOrdered(*mem, value); + return typeToValue(value); } -ReturnedValue UInt16ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue atomicAnd(char *data, Value v) { - return Encode((int)*(const unsigned short *)(data + index)); + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndAndOrdered(*mem, value); + return typeToValue(value); } -void UInt16ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicExchange(char *data, Value v) { - unsigned short v = (unsigned short)value.toInt32(); - if (e->hasException) - return; - *(unsigned short *)(data + index) = v; + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndStoreOrdered(*mem, value); + return typeToValue(value); } -ReturnedValue Int32ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue atomicOr(char *data, Value v) { - return Encode(*(const int *)(data + index)); + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndOrOrdered(*mem, value); + return typeToValue(value); } -void Int32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicSub(char *data, Value v) { - int v = (int)value.toInt32(); - if (e->hasException) - return; - *(int *)(data + index) = v; + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndSubOrdered(*mem, value); + return typeToValue(value); } -ReturnedValue UInt32ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue atomicXor(char *data, Value v) { - return Encode(*(const unsigned int *)(data + index)); + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + value = QAtomicOps<T>::fetchAndXorOrdered(*mem, value); + return typeToValue(value); } -void UInt32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicCompareExchange(char *data, Value expected, Value v) { - unsigned int v = (unsigned int)value.toUInt32(); - if (e->hasException) - return; - *(unsigned int *)(data + index) = v; + T value = valueToType<T>(v); + T exp = valueToType<T>(expected); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + T old; + QAtomicOps<T>::testAndSetOrdered(*mem, exp, value, &old); + return typeToValue(old); } -ReturnedValue Float32ArrayRead(const char *data, int index) +template <typename T> +ReturnedValue atomicLoad(char *data) { - return Encode(*(const float *)(data + index)); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + T val = QAtomicOps<T>::load(*mem); + return typeToValue(val); } -void Float32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template <typename T> +ReturnedValue atomicStore(char *data, Value v) { - float v = value.toNumber(); - if (e->hasException) - return; - *(float *)(data + index) = v; + T value = valueToType<T>(v); + typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); + QAtomicOps<T>::store(*mem, value); + return typeToValue(value); } -ReturnedValue Float64ArrayRead(const char *data, int index) + +template<typename T> +constexpr TypedArrayOperations TypedArrayOperations::create(const char *name) { - return Encode(*(const double *)(data + index)); + return { sizeof(T), + name, + ::read<T>, + ::write<T>, + { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }, + nullptr, + nullptr, + nullptr + }; } -void Float64ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) +template<typename T> +constexpr TypedArrayOperations TypedArrayOperations::createWithAtomics(const char *name) { - double v = value.toNumber(); - if (e->hasException) - return; - *(double *)(data + index) = v; + return { sizeof(T), + name, + ::read<T>, + ::write<T>, + { ::atomicAdd<T>, ::atomicAnd<T>, ::atomicExchange<T>, ::atomicOr<T>, ::atomicSub<T>, ::atomicXor<T> }, + ::atomicCompareExchange<T>, + ::atomicLoad<T>, + ::atomicStore<T> + }; } -const TypedArrayOperations operations[Heap::TypedArray::NTypes] = { - { 1, "Int8Array", Int8ArrayRead, Int8ArrayWrite }, - { 1, "Uint8Array", UInt8ArrayRead, UInt8ArrayWrite }, - { 1, "Uint8ClampedArray", UInt8ArrayRead, UInt8ClampedArrayWrite }, - { 2, "Int16Array", Int16ArrayRead, Int16ArrayWrite }, - { 2, "Uint16Array", UInt16ArrayRead, UInt16ArrayWrite }, - { 4, "Int32Array", Int32ArrayRead, Int32ArrayWrite }, - { 4, "Uint32Array", UInt32ArrayRead, UInt32ArrayWrite }, - { 4, "Float32Array", Float32ArrayRead, Float32ArrayWrite }, - { 8, "Float64Array", Float64ArrayRead, Float64ArrayWrite }, +const TypedArrayOperations operations[NTypedArrayTypes] = { +#ifdef Q_ATOMIC_INT8_IS_SUPPORTED + TypedArrayOperations::createWithAtomics<qint8>("Int8Array"), + TypedArrayOperations::createWithAtomics<quint8>("Uint8Array"), +#else + TypedArrayOperations::create<qint8>("Int8Array"), + TypedArrayOperations::create<quint8>("Uint8Array"), +#endif + TypedArrayOperations::createWithAtomics<qint16>("Int16Array"), + TypedArrayOperations::createWithAtomics<quint16>("Uint16Array"), + TypedArrayOperations::createWithAtomics<qint32>("Int32Array"), + TypedArrayOperations::createWithAtomics<quint32>("Uint32Array"), + TypedArrayOperations::create<ClampedUInt8>("Uint8ClampedArray"), + TypedArrayOperations::create<float>("Float32Array"), + TypedArrayOperations::create<double>("Float64Array") }; @@ -208,49 +280,58 @@ void Heap::TypedArrayCtor::init(QV4::ExecutionContext *scope, TypedArray::Type t type = t; } -void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callData) +ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { - Scoped<TypedArrayCtor> that(scope, static_cast<const TypedArrayCtor *>(m)); + Scope scope(f->engine()); + const TypedArrayCtor *that = static_cast<const TypedArrayCtor *>(f); + + auto updateProto = [=](Scope &scope, Scoped<TypedArray> &a) { + if (newTarget->heapObject() != f->heapObject() && newTarget->isFunctionObject()) { + const FunctionObject *nt = static_cast<const FunctionObject *>(newTarget); + ScopedObject o(scope, nt->protoProperty()); + if (o) + a->setPrototypeOf(o); + } + }; - if (!callData->argc || !callData->args[0].isObject()) { + if (!argc || !argv[0].isObject()) { // ECMA 6 22.2.1.1 - double l = callData->argc ? callData->args[0].toNumber() : 0; - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + qint64 l = argc ? argv[0].toIndex() : 0; + if (scope.engine->hasException) + return Encode::undefined(); + // ### lift UINT_MAX restriction + if (l < 0 || l > UINT_MAX) + return scope.engine->throwRangeError(QLatin1String("Index out of range.")); uint len = (uint)l; if (l != len) scope.engine->throwRangeError(QStringLiteral("Non integer length for typed array.")); uint byteLength = len * operations[that->d()->type].bytesPerElement; Scoped<ArrayBuffer> buffer(scope, scope.engine->newArrayBuffer(byteLength)); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + if (scope.engine->hasException) + return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, buffer->d()); array->d()->byteLength = byteLength; array->d()->byteOffset = 0; - scope.result = array.asReturnedValue(); - return; + updateProto(scope, array); + return array.asReturnedValue(); } - Scoped<TypedArray> typedArray(scope, callData->argument(0)); + Scoped<TypedArray> typedArray(scope, argc ? argv[0] : Value::undefinedValue()); if (!!typedArray) { // ECMA 6 22.2.1.2 Scoped<ArrayBuffer> buffer(scope, typedArray->d()->buffer); + if (!buffer || buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); uint srcElementSize = typedArray->d()->type->bytesPerElement; uint destElementSize = operations[that->d()->type].bytesPerElement; uint byteLength = typedArray->d()->byteLength; uint destByteLength = byteLength*destElementSize/srcElementSize; Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(destByteLength)); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + if (scope.engine->hasException) + return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, newBuffer->d()); @@ -266,48 +347,46 @@ void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callDat } else { // not same size, we need to loop uint l = typedArray->length(); - TypedArrayRead read = typedArray->d()->type->read; - TypedArrayWrite write =array->d()->type->write; + TypedArrayOperations::Read read = typedArray->d()->type->read; + TypedArrayOperations::Write write =array->d()->type->write; for (uint i = 0; i < l; ++i) { - Primitive val; - val.setRawValue(read(src, i*srcElementSize)); - write(scope.engine, dest, i*destElementSize, val); + Value val; + val.setRawValue(read(src + i*srcElementSize)); + write(dest + i*destElementSize, val); } } - scope.result = array.asReturnedValue(); - return; + updateProto(scope, array); + return array.asReturnedValue(); } - Scoped<ArrayBuffer> buffer(scope, callData->argument(0)); + Scoped<ArrayBuffer> buffer(scope, argc ? argv[0] : Value::undefinedValue()); if (!!buffer) { // ECMA 6 22.2.1.4 - double dbyteOffset = callData->argc > 1 ? callData->args[1].toInteger() : 0; + double dbyteOffset = argc > 1 ? argv[1].toInteger() : 0; + + if (buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + uint byteOffset = (uint)dbyteOffset; uint elementSize = operations[that->d()->type].bytesPerElement; - if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->byteLength()) { - scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset")); - return; - } + if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->byteLength()) + return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset")); uint byteLength; - if (callData->argc < 3 || callData->args[2].isUndefined()) { + if (argc < 3 || argv[2].isUndefined()) { byteLength = buffer->byteLength() - byteOffset; - if (buffer->byteLength() < byteOffset || byteLength % elementSize) { - scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); - return; - } + if (buffer->byteLength() < byteOffset || byteLength % elementSize) + return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); } else { - double l = qBound(0., callData->args[2].toInteger(), (double)UINT_MAX); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + double l = qBound(0., argv[2].toInteger(), (double)UINT_MAX); + if (scope.engine->hasException) + return Encode::undefined(); + if (buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); l *= elementSize; - if (buffer->byteLength() - byteOffset < l) { - scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); - return; - } + if (buffer->byteLength() - byteOffset < l) + return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); byteLength = (uint)l; } @@ -315,25 +394,25 @@ void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callDat array->d()->buffer.set(scope.engine, buffer->d()); array->d()->byteLength = byteLength; array->d()->byteOffset = byteOffset; - scope.result = array.asReturnedValue(); - return; + + updateProto(scope, array); + return array.asReturnedValue(); } // ECMA 6 22.2.1.3 - ScopedObject o(scope, callData->argument(0)); + ScopedObject o(scope, argc ? argv[0] : Value::undefinedValue()); uint l = (uint) qBound(0., ScopedValue(scope, o->get(scope.engine->id_length()))->toInteger(), (double)UINT_MAX); - if (scope.engine->hasException) { - scope.result = scope.engine->throwTypeError(); - return; - } + if (scope.engine->hasException) + return scope.engine->throwTypeError(); uint elementSize = operations[that->d()->type].bytesPerElement; - Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(l * elementSize)); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + size_t bufferSize; + if (mul_overflow(size_t(l), size_t(elementSize), &bufferSize)) + return scope.engine->throwRangeError(QLatin1String("new TypedArray: invalid length")); + Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(bufferSize)); + if (scope.engine->hasException) + return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer.set(scope.engine, newBuffer->d()); @@ -344,172 +423,1017 @@ void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callDat char *b = newBuffer->d()->data->data(); ScopedValue val(scope); while (idx < l) { - val = o->getIndexed(idx); - array->d()->type->write(scope.engine, b, 0, val); - if (scope.engine->hasException) { - scope.result = Encode::undefined(); - return; - } + val = o->get(idx); + val = val->convertedToNumber(); + if (scope.engine->hasException) + return Encode::undefined(); + array->d()->type->write(b, val); + if (scope.engine->hasException) + return Encode::undefined(); ++idx; b += elementSize; } - - scope.result = array.asReturnedValue(); + updateProto(scope, array); + return array.asReturnedValue(); } -void TypedArrayCtor::call(const Managed *that, Scope &scope, CallData *callData) +ReturnedValue TypedArrayCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) { - construct(that, scope, callData); + return f->engine()->throwTypeError(QStringLiteral("calling a TypedArray constructor without new is invalid")); } void Heap::TypedArray::init(Type t) { Object::init(); - type = operations + t; - arrayType = t; + type = operations + static_cast<int>(t); + arrayType = static_cast<int>(t); } Heap::TypedArray *TypedArray::create(ExecutionEngine *e, Heap::TypedArray::Type t) { - QV4::InternalClass *ic = e->internalClasses[EngineBase::Class_Empty]->changeVTable(staticVTable()); - ic = ic->changePrototype(e->typedArrayPrototype[t].d()); - return e->memoryManager->allocObject<TypedArray>(ic, e->typedArrayPrototype + t, t); + Scope scope(e); + Scoped<InternalClass> ic(scope, e->newInternalClass(staticVTable(), e->typedArrayPrototype + static_cast<int>(t))); + return e->memoryManager->allocObject<TypedArray>(ic->d(), t); } -ReturnedValue TypedArray::getIndexed(const Managed *m, uint index, bool *hasProperty) +ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualGet(m, id, receiver, hasProperty); + // fall through, with index == UINT_MAX it'll do the right thing. + Scope scope(static_cast<const Object *>(m)->engine()); Scoped<TypedArray> a(scope, static_cast<const TypedArray *>(m)); + if (a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); - uint bytesPerElement = a->d()->type->bytesPerElement; - uint byteOffset = a->d()->byteOffset + index * bytesPerElement; - if (byteOffset + bytesPerElement > (uint)a->d()->buffer->byteLength()) { + if (index >= a->length()) { if (hasProperty) *hasProperty = false; return Encode::undefined(); } + + uint bytesPerElement = a->d()->type->bytesPerElement; + uint byteOffset = a->d()->byteOffset + index * bytesPerElement; + Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); + if (hasProperty) *hasProperty = true; - return a->d()->type->read(a->d()->buffer->data->data(), byteOffset); + return a->d()->type->read(a->d()->buffer->data->data() + byteOffset); +} + +bool TypedArray::virtualHasProperty(const Managed *m, PropertyKey id) +{ + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualHasProperty(m, id); + // fall through, with index == UINT_MAX it'll do the right thing. + + const TypedArray *a = static_cast<const TypedArray *>(m); + if (a->d()->buffer->isDetachedBuffer()) { + a->engine()->throwTypeError(); + return false; + } + if (index >= a->length()) + return false; + return true; } -bool TypedArray::putIndexed(Managed *m, uint index, const Value &value) +PropertyAttributes TypedArray::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualGetOwnProperty(m, id, p); + // fall through, with index == UINT_MAX it'll do the right thing. + + bool hasProperty = false; + ReturnedValue v = virtualGet(m, id, m, &hasProperty); + if (p) + p->value = v; + return hasProperty ? Attr_NotConfigurable : PropertyAttributes(); +} + +bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) +{ + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualPut(m, id, value, receiver); + // fall through, with index == UINT_MAX it'll do the right thing. + ExecutionEngine *v4 = static_cast<Object *>(m)->engine(); if (v4->hasException) return false; Scope scope(v4); Scoped<TypedArray> a(scope, static_cast<TypedArray *>(m)); + if (a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + if (index >= a->length()) + return false; uint bytesPerElement = a->d()->type->bytesPerElement; uint byteOffset = a->d()->byteOffset + index * bytesPerElement; - if (byteOffset + bytesPerElement > (uint)a->d()->buffer->byteLength()) - goto reject; + Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); - a->d()->type->write(scope.engine, a->d()->buffer->data->data(), byteOffset, value); + Value v = Value::fromReturnedValue(value.convertedToNumber()); + if (scope.hasException() || a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + a->d()->type->write(a->d()->buffer->data->data() + byteOffset, v); return true; +} -reject: - if (scope.engine->current->strictMode) - scope.engine->throwTypeError(); - return false; +bool TypedArray::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) +{ + uint index = id.asArrayIndex(); + if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + return Object::virtualDefineOwnProperty(m, id, p, attrs); + // fall through, with index == UINT_MAX it'll do the right thing. + + TypedArray *a = static_cast<TypedArray *>(m); + if (index >= a->length() || attrs.isAccessor()) + return false; + + if (attrs.hasConfigurable() && attrs.isConfigurable()) + return false; + if (attrs.hasEnumerable() && !attrs.isEnumerable()) + return false; + if (attrs.hasWritable() && !attrs.isWritable()) + return false; + if (!p->value.isEmpty()) { + ExecutionEngine *engine = a->engine(); + + Value v = Value::fromReturnedValue(p->value.convertedToNumber()); + if (engine->hasException || a->d()->buffer->isDetachedBuffer()) + return engine->throwTypeError(); + uint bytesPerElement = a->d()->type->bytesPerElement; + uint byteOffset = a->d()->byteOffset + index * bytesPerElement; + Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); + a->d()->type->write(a->d()->buffer->data->data() + byteOffset, v); + } + return true; +} + +struct TypedArrayOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator +{ + ~TypedArrayOwnPropertyKeyIterator() override = default; + PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; + +}; + +PropertyKey TypedArrayOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) +{ + const TypedArray *a = static_cast<const TypedArray *>(o); + if (arrayIndex < a->length()) { + if (attrs) + *attrs = Attr_NotConfigurable; + PropertyKey id = PropertyKey::fromArrayIndex(arrayIndex); + if (pd) { + bool hasProperty = false; + pd->value = TypedArray::virtualGet(a, id, a, &hasProperty); + } + ++arrayIndex; + return id; + } + + arrayIndex = UINT_MAX; + return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); +} + +OwnPropertyKeyIterator *TypedArray::virtualOwnPropertyKeys(const Object *m, Value *target) +{ + *target = *m; + return new TypedArrayOwnPropertyKeyIterator(); } void TypedArrayPrototype::init(ExecutionEngine *engine, TypedArrayCtor *ctor) { Scope scope(engine); ScopedObject o(scope); - ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(3)); - ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); - ctor->defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Primitive::fromInt32(operations[ctor->d()->type].bytesPerElement)); + + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(3)); + ctor->defineReadonlyProperty(engine->id_prototype(), *this); + ctor->defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast<int>(ctor->d()->type)].bytesPerElement)); + ctor->setPrototypeOf(engine->intrinsicTypedArrayCtor()); + + setPrototypeOf(engine->intrinsicTypedArrayPrototype()); defineDefaultProperty(engine->id_constructor(), (o = ctor)); - defineAccessorProperty(QStringLiteral("buffer"), method_get_buffer, 0); - defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, 0); - defineAccessorProperty(QStringLiteral("byteOffset"), method_get_byteOffset, 0); - defineAccessorProperty(QStringLiteral("length"), method_get_length, 0); - defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Primitive::fromInt32(operations[ctor->d()->type].bytesPerElement)); + defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast<int>(ctor->d()->type)].bytesPerElement)); +} - defineDefaultProperty(QStringLiteral("set"), method_set, 1); - defineDefaultProperty(QStringLiteral("subarray"), method_subarray, 0); +ReturnedValue IntrinsicTypedArrayPrototype::method_get_buffer(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + ExecutionEngine *v4 = b->engine(); + const TypedArray *v = thisObject->as<TypedArray>(); + if (!v) + return v4->throwTypeError(); + + return v->d()->buffer->asReturnedValue(); } -void TypedArrayPrototype::method_get_buffer(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteLength(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<TypedArray> v(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + const TypedArray *v = thisObject->as<TypedArray>(); if (!v) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); + + if (v->d()->buffer->isDetachedBuffer()) + return Encode(0); - scope.result = v->d()->buffer; + return Encode(v->d()->byteLength); } -void TypedArrayPrototype::method_get_byteLength(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteOffset(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<TypedArray> v(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + const TypedArray *v = thisObject->as<TypedArray>(); if (!v) - THROW_TYPE_ERROR(); + return v4->throwTypeError(); - scope.result = Encode(v->d()->byteLength); + if (v->d()->buffer->isDetachedBuffer()) + return Encode(0); + + return Encode(v->d()->byteOffset); } -void TypedArrayPrototype::method_get_byteOffset(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_get_length(const FunctionObject *b, const Value *thisObject, const Value *, int) { - Scoped<TypedArray> v(scope, callData->thisObject); + ExecutionEngine *v4 = b->engine(); + const TypedArray *v = thisObject->as<TypedArray>(); if (!v) + return v4->throwTypeError(); + + if (v->d()->buffer->isDetachedBuffer()) + return Encode(0); + + return Encode(v->d()->byteLength/v->d()->type->bytesPerElement); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_copyWithin(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + Scoped<TypedArray> O(scope, thisObject); + if (!O || O->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + if (!argc) + return O->asReturnedValue(); + + qint64 len = static_cast<uint>(O->length()); + + qint64 to = static_cast<qint64>(argv[0].toInteger()); + if (to < 0) + to = qMax(len + to, 0ll); + else + to = qMin(to, len); + + qint64 from = (argc > 1) ? static_cast<qint64>(argv[1].toInteger()) : 0ll; + if (from < 0) + from = qMax(len + from, 0ll); + else + from = qMin(from, len); + + double fend = argv[2].toInteger(); + if (fend > len) + fend = len; + qint64 end = (argc > 2 && !argv[2].isUndefined()) ? static_cast<qint64>(fend) : len; + if (end < 0) + end = qMax(len + end, 0ll); + else + end = qMin(end, len); + + qint64 count = qMin(end - from, len - to); + + if (count <= 0) + return O->asReturnedValue(); + + if (O->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + if (from != to) { + int elementSize = O->d()->type->bytesPerElement; + char *data = O->d()->buffer->data->data() + O->d()->byteOffset; + memmove(data + to*elementSize, data + from*elementSize, count*elementSize); + } + + return O->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); + ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; + return ao->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_every(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + ScopedValue r(scope); + Value *arguments = scope.alloc(3); - scope.result = Encode(v->d()->byteOffset); + const char *data = v->d()->buffer->data->data(); + uint bytesPerElement = v->d()->type->bytesPerElement; + uint byteOffset = v->d()->byteOffset; + + bool ok = true; + for (uint k = 0; ok && k < len; ++k) { + if (v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + arguments[0] = v->d()->type->read(data + byteOffset + k * bytesPerElement); + + arguments[1] = Value::fromDouble(k); + arguments[2] = v; + r = callback->call(that, arguments, 3); + ok = r->toBoolean(); + } + return Encode(ok); } -void TypedArrayPrototype::method_get_length(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<TypedArray> v(scope, callData->thisObject); - if (!v) + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + double dlen = len; + double relativeStart = argc > 1 ? argv[1].toInteger() : 0.; + double relativeEnd = len; + if (argc > 2 && !argv[2].isUndefined()) + relativeEnd = argv[2].toInteger(); + + uint k = 0; + uint fin = 0; + + if (relativeStart < 0) { + k = static_cast<uint>(std::max(len+relativeStart, 0.)); + } else { + k = static_cast<uint>(std::min(relativeStart, dlen)); + } + + if (relativeEnd < 0) { + fin = static_cast<uint>(std::max(len + relativeEnd, 0.)); + } else { + fin = static_cast<uint>(std::min(relativeEnd, dlen)); + } + + double val = argc ? argv[0].toNumber() : std::numeric_limits<double>::quiet_NaN(); + Value value = Value::fromDouble(val); + if (scope.hasException() || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + char *data = v->d()->buffer->data->data(); + uint bytesPerElement = v->d()->type->bytesPerElement; + uint byteOffset = v->d()->byteOffset; + + while (k < fin) { + v->d()->type->write(data + byteOffset + k * bytesPerElement, value); + k++; + } + + return v.asReturnedValue(); +} + +static TypedArray *typedArraySpeciesCreate(Scope &scope, const TypedArray *instance, uint len) +{ + const FunctionObject *constructor = instance->speciesConstructor(scope, scope.engine->typedArrayCtors + instance->d()->arrayType); + if (!constructor) { + scope.engine->throwTypeError(); + return nullptr; + } + + Value *arguments = scope.alloc(1); + arguments[0] = Encode(len); + Scoped<TypedArray> a(scope, constructor->callAsConstructor(arguments, 1)); + if (!a || a->d()->buffer->isDetachedBuffer() || a->length() < len) { + scope.engine->throwTypeError(); + return nullptr; + } + return a; +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_filter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue selected(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); + Value *list = arguments; + + uint to = 0; + for (uint k = 0; k < len; ++k) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool exists; + arguments[0] = instance->get(k, &exists); + if (!exists) + continue; + + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + selected = callback->call(that, arguments, 3); + if (selected->toBoolean()) { + ++arguments; + scope.alloc(1); + ++to; + } + } + + TypedArray *a = typedArraySpeciesCreate(scope, instance, to); + if (!a) + return Encode::undefined(); - scope.result = Encode(v->d()->byteLength/v->d()->type->bytesPerElement); + for (uint i = 0; i < to; ++i) + a->put(i, list[i]); + + return a->asReturnedValue(); } -void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_find(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<TypedArray> a(scope, callData->thisObject); + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + if (!argc || !argv[0].isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue result(scope); + Value *arguments = scope.alloc(3); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + + for (uint k = 0; k < len; ++k) { + if (v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + arguments[0] = v->get(k); + CHECK_EXCEPTION(); + + arguments[1] = Value::fromDouble(k); + arguments[2] = v; + result = callback->call(that, arguments, 3); + + CHECK_EXCEPTION(); + if (result->toBoolean()) + return arguments[0].asReturnedValue(); + } + + RETURN_UNDEFINED(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_findIndex(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + if (!argc || !argv[0].isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue result(scope); + Value *arguments = scope.alloc(3); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + + for (uint k = 0; k < len; ++k) { + if (v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + arguments[0] = v->get(k); + CHECK_EXCEPTION(); + + arguments[1] = Value::fromDouble(k); + arguments[2] = v; + result = callback->call(that, arguments, 3); + + CHECK_EXCEPTION(); + if (result->toBoolean()) + return Encode(k); + } + + return Encode(-1); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + if (!argc || !argv->isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); + + for (uint k = 0; k < len; ++k) { + if (v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool exists; + arguments[0] = v->get(k, &exists); + if (!exists) + continue; + + arguments[1] = Value::fromDouble(k); + arguments[2] = v; + callback->call(that, arguments, 3); + } + RETURN_UNDEFINED(); +} + + +ReturnedValue IntrinsicTypedArrayPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + if (len == 0) { + return Encode(false); + } + + double n = 0; + if (argc > 1 && !argv[1].isUndefined()) { + n = argv[1].toInteger(); + } + + double k = 0; + if (n >= 0) { + k = n; + } else { + k = len + n; + if (k < 0) { + k = 0; + } + } + + while (k < len) { + ScopedValue val(scope, v->get(k)); + if (val->sameValueZero(argv[0])) { + return Encode(true); + } + k++; + } + + return Encode(false); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + if (!len) + return Encode(-1); + + ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue()); + uint fromIndex = 0; + + if (argc >= 2) { + double f = argv[1].toInteger(); + CHECK_EXCEPTION(); + if (f >= len) + return Encode(-1); + if (f < 0) + f = qMax(len + f, 0.); + fromIndex = (uint) f; + } + + if (v->isStringObject()) { + ScopedValue value(scope); + for (uint k = fromIndex; k < len; ++k) { + bool exists; + value = v->get(k, &exists); + if (exists && RuntimeHelpers::strictEqual(value, searchValue)) + return Encode(k); + } + return Encode(-1); + } + + ScopedValue value(scope); + + for (uint i = fromIndex; i < len; ++i) { + bool exists; + value = v->get(i, &exists); + CHECK_EXCEPTION(); + if (exists && RuntimeHelpers::strictEqual(value, searchValue)) + return Encode(i); + } + return Encode(-1); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_join(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = v->length(); + + ScopedValue arg(scope, argc ? argv[0] : Value::undefinedValue()); + + QString r4; + if (arg->isUndefined()) + r4 = QStringLiteral(","); + else + r4 = arg->toQString(); + + const quint32 r2 = len; + + if (!r2) + return Encode(scope.engine->newString()); + + QString R; + + // + // crazy! + // + ScopedString name(scope, scope.engine->newString(QStringLiteral("0"))); + ScopedValue r6(scope, v->get(name)); + if (!r6->isNullOrUndefined()) + R = r6->toQString(); + + ScopedValue r12(scope); + for (quint32 k = 1; k < r2; ++k) { + R += r4; + + name = Value::fromDouble(k).toString(scope.engine); + r12 = v->get(name); + CHECK_EXCEPTION(); + + if (!r12->isNullOrUndefined()) + R += r12->toQString(); + } + + return Encode(scope.engine->newString(R)); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_keys(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); + ao->d()->iterationKind = IteratorKind::KeyIteratorKind; + return ao->asReturnedValue(); +} + + +ReturnedValue IntrinsicTypedArrayPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + if (!len) + return Encode(-1); + + ScopedValue searchValue(scope); + uint fromIndex = len; + + if (argc >= 1) + searchValue = argv[0]; + else + searchValue = Value::undefinedValue(); + + if (argc >= 2) { + double f = argv[1].toInteger(); + CHECK_EXCEPTION(); + if (f > 0) + f = qMin(f, (double)(len - 1)); + else if (f < 0) { + f = len + f; + if (f < 0) + return Encode(-1); + } + fromIndex = (uint) f + 1; + } + + ScopedValue value(scope); + for (uint k = fromIndex; k > 0;) { + --k; + bool exists; + value = instance->get(k, &exists); + if (exists && RuntimeHelpers::strictEqual(value, searchValue)) + return Encode(k); + } + return Encode(-1); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_map(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + TypedArray *a = typedArraySpeciesCreate(scope, instance, len); if (!a) + return Encode::undefined(); + + ScopedValue v(scope); + ScopedValue mapped(scope); + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + Value *arguments = scope.alloc(3); + + for (uint k = 0; k < len; ++k) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + arguments[0] = instance->get(k); + + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + mapped = callback->call(that, arguments, 3); + a->put(k, mapped); + } + return a->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_reduce(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + uint k = 0; + ScopedValue acc(scope); + ScopedValue v(scope); + + if (argc > 1) { + acc = argv[1]; + } else { + bool kPresent = false; + while (k < len && !kPresent) { + v = instance->get(k, &kPresent); + if (kPresent) + acc = v; + ++k; + } + if (!kPresent) + THROW_TYPE_ERROR(); + } + + Value *arguments = scope.alloc(4); + + while (k < len) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool kPresent; + v = instance->get(k, &kPresent); + if (kPresent) { + arguments[0] = acc; + arguments[1] = v; + arguments[2] = Value::fromDouble(k); + arguments[3] = instance; + acc = callback->call(nullptr, arguments, 4); + } + ++k; + } + return acc->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + if (len == 0) { + if (argc == 1) + THROW_TYPE_ERROR(); + return argv[1].asReturnedValue(); + } + + uint k = len; + ScopedValue acc(scope); + ScopedValue v(scope); + if (argc > 1) { + acc = argv[1]; + } else { + bool kPresent = false; + while (k > 0 && !kPresent) { + v = instance->get(k - 1, &kPresent); + if (kPresent) + acc = v; + --k; + } + if (!kPresent) + THROW_TYPE_ERROR(); + } + + Value *arguments = scope.alloc(4); + + while (k > 0) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool kPresent; + v = instance->get(k - 1, &kPresent); + if (kPresent) { + arguments[0] = acc; + arguments[1] = v; + arguments[2] = Value::fromDouble(k - 1); + arguments[3] = instance; + acc = callback->call(nullptr, arguments, 4); + } + --k; + } + return acc->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_reverse(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint length = instance->length(); + + int lo = 0, hi = length - 1; + + ScopedValue lval(scope); + ScopedValue hval(scope); + for (; lo < hi; ++lo, --hi) { + bool loExists, hiExists; + lval = instance->get(lo, &loExists); + hval = instance->get(hi, &hiExists); + Q_ASSERT(hiExists && loExists); + bool ok; + ok = instance->put(lo, hval); + Q_ASSERT(ok); + ok = instance->put(hi, lval); + Q_ASSERT(ok); + } + return instance->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_some(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + if (!argc || !argv->isFunctionObject()) + THROW_TYPE_ERROR(); + const FunctionObject *callback = static_cast<const FunctionObject *>(argv); + + ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); + ScopedValue result(scope); + Value *arguments = scope.alloc(3); + + for (uint k = 0; k < len; ++k) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + bool exists; + arguments[0] = instance->get(k, &exists); + if (!exists) + continue; + + arguments[1] = Value::fromDouble(k); + arguments[2] = instance; + result = callback->call(that, arguments, 3); + if (result->toBoolean()) + return Encode(true); + } + return Encode(false); +} + + +ReturnedValue IntrinsicTypedArrayPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> v(scope, thisObject); + if (!v || v->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); + ao->d()->iterationKind = IteratorKind::ValueIteratorKind; + return ao->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(b); + Scoped<TypedArray> a(scope, *thisObject); + if (!a) + return scope.engine->throwTypeError(); Scoped<ArrayBuffer> buffer(scope, a->d()->buffer); - if (!buffer) - scope.engine->throwTypeError(); - double doffset = callData->argc >= 2 ? callData->args[1].toInteger() : 0; + double doffset = argc >= 2 ? argv[1].toInteger() : 0; if (scope.engine->hasException) RETURN_UNDEFINED(); + if (!buffer || buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); if (doffset < 0 || doffset >= UINT_MAX) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); uint offset = (uint)doffset; uint elementSize = a->d()->type->bytesPerElement; - Scoped<TypedArray> srcTypedArray(scope, callData->args[0]); + Scoped<TypedArray> srcTypedArray(scope, argv[0]); if (!srcTypedArray) { // src is a regular object - ScopedObject o(scope, callData->args[0].toObject(scope.engine)); + ScopedObject o(scope, argv[0].toObject(scope.engine)); if (scope.engine->hasException || !o) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); double len = ScopedValue(scope, o->get(scope.engine->id_length()))->toNumber(); uint l = (uint)len; if (scope.engine->hasException || l != len) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); if (offset + l > a->length()) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); uint idx = 0; + if (buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); char *b = buffer->d()->data->data() + a->d()->byteOffset + offset*elementSize; ScopedValue val(scope); while (idx < l) { - val = o->getIndexed(idx); - a->d()->type->write(scope.engine, b, 0, val); + val = o->get(idx); + if (scope.hasException()) + return Encode::undefined(); + val = val->convertedToNumber(); + if (scope.hasException() || buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + a->d()->type->write(b, val); if (scope.engine->hasException) RETURN_UNDEFINED(); ++idx; @@ -520,8 +1444,8 @@ void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, Call // src is a typed array Scoped<ArrayBuffer> srcBuffer(scope, srcTypedArray->d()->buffer); - if (!srcBuffer) - THROW_TYPE_ERROR(); + if (!srcBuffer || srcBuffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); uint l = srcTypedArray->length(); if (offset + l > a->length()) @@ -535,7 +1459,7 @@ void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, Call RETURN_UNDEFINED(); } - char *srcCopy = 0; + char *srcCopy = nullptr; if (buffer->d() == srcBuffer->d()) { // same buffer, need to take a temporary copy, to not run into problems srcCopy = new char[srcTypedArray->d()->byteLength]; @@ -545,12 +1469,12 @@ void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, Call // typed arrays of different kind, need to manually loop uint srcElementSize = srcTypedArray->d()->type->bytesPerElement; - TypedArrayRead read = srcTypedArray->d()->type->read; - TypedArrayWrite write = a->d()->type->write; + TypedArrayOperations::Read read = srcTypedArray->d()->type->read; + TypedArrayOperations::Write write = a->d()->type->write; for (uint i = 0; i < l; ++i) { - Primitive val; - val.setRawValue(read(src, i*srcElementSize)); - write(scope.engine, dest, i*elementSize, val); + Value val; + val.setRawValue(read(src + i*srcElementSize)); + write(dest + i*elementSize, val); } if (srcCopy) @@ -559,24 +1483,71 @@ void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, Call RETURN_UNDEFINED(); } -void TypedArrayPrototype::method_subarray(const BuiltinFunction *, Scope &scope, CallData *callData) +ReturnedValue IntrinsicTypedArrayPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { - Scoped<TypedArray> a(scope, callData->thisObject); + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + + double s = (argc ? argv[0] : Value::undefinedValue()).toInteger(); + uint start; + if (s < 0) + start = (uint)qMax(len + s, 0.); + else if (s > len) + start = len; + else + start = (uint) s; + uint end = len; + if (argc > 1 && !argv[1].isUndefined()) { + double e = argv[1].toInteger(); + if (e < 0) + end = (uint)qMax(len + e, 0.); + else if (e > len) + end = len; + else + end = (uint) e; + } + uint count = start > end ? 0 : end - start; + TypedArray *a = typedArraySpeciesCreate(scope, instance, count); if (!a) - THROW_TYPE_ERROR(); + return Encode::undefined(); + + ScopedValue v(scope); + uint n = 0; + for (uint i = start; i < end; ++i) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + v = instance->get(i); + if (a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + a->put(n, v); + ++n; + } + return a->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_subarray(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(builtin); + Scoped<TypedArray> a(scope, *thisObject); + + if (!a) + return scope.engine->throwTypeError(); Scoped<ArrayBuffer> buffer(scope, a->d()->buffer); - if (!buffer) - THROW_TYPE_ERROR(); + Q_ASSERT(buffer); int len = a->length(); - double b = callData->argc > 0 ? callData->args[0].toInteger() : 0; + double b = argc > 0 ? argv[0].toInteger() : 0; if (b < 0) b = len + b; uint begin = (uint)qBound(0., b, (double)len); - double e = callData->argc < 2 || callData->args[1].isUndefined() ? len : callData->args[1].toInteger(); + double e = argc < 2 || argv[1].isUndefined() ? len : argv[1].toInteger(); if (e < 0) e = len + e; uint end = (uint)qBound(0., e, (double)len); @@ -588,13 +1559,142 @@ void TypedArrayPrototype::method_subarray(const BuiltinFunction *, Scope &scope, int newLen = end - begin; - ScopedFunctionObject constructor(scope, a->get(scope.engine->id_constructor())); + ScopedFunctionObject constructor(scope, a->speciesConstructor(scope, scope.engine->typedArrayCtors + a->d()->arrayType)); if (!constructor) - THROW_TYPE_ERROR(); + return scope.engine->throwTypeError(); + + Value *arguments = scope.alloc(3); + arguments[0] = buffer; + arguments[1] = Encode(a->d()->byteOffset + begin*a->d()->type->bytesPerElement); + arguments[2] = Encode(newLen); + a = constructor->callAsConstructor(arguments, 3); + if (!a || a->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + return a->asReturnedValue(); +} - ScopedCallData cData(scope, 3); - cData->args[0] = buffer; - cData->args[1] = Encode(a->d()->byteOffset + begin*a->d()->type->bytesPerElement); - cData->args[2] = Encode(newLen); - constructor->construct(scope, cData); +ReturnedValue IntrinsicTypedArrayPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int) +{ + Scope scope(b); + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + + uint len = instance->length(); + const QString separator = QStringLiteral(","); + + QString R; + + ScopedValue v(scope); + ScopedString s(scope); + + for (uint k = 0; k < len; ++k) { + if (instance->d()->buffer->isDetachedBuffer()) + return scope.engine->throwTypeError(); + if (k) + R += separator; + + v = instance->get(k); + v = Runtime::CallElement::call(scope.engine, v, *scope.engine->id_toLocaleString(), nullptr, 0); + s = v->toString(scope.engine); + if (scope.hasException()) + return Encode::undefined(); + + R += s->toQString(); + } + return scope.engine->newString(R)->asReturnedValue(); +} + +ReturnedValue IntrinsicTypedArrayPrototype::method_get_toStringTag(const FunctionObject *, const Value *thisObject, const Value *, int) +{ + const TypedArray *a = thisObject->as<TypedArray>(); + if (!a) + return Encode::undefined(); + + return a->engine()->newString(QString::fromLatin1(a->d()->type->name))->asReturnedValue(); +} + +static bool validateTypedArray(const Object *o) +{ + const TypedArray *a = o->as<TypedArray>(); + if (!a) + return false; + if (a->d()->buffer->isDetachedBuffer()) + return false; + return true; +} + +ReturnedValue IntrinsicTypedArrayCtor::method_of(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) +{ + Scope scope(f); + int len = argc; + const Value *items = argv; + const FunctionObject *C = thisObject->as<FunctionObject>(); + if (!C || !C->isConstructor()) + return scope.engine->throwTypeError(); + + Value lenValue = Value::fromInt32(len); + ScopedObject newObj(scope, C->callAsConstructor(&lenValue, 1)); + if (scope.hasException()) + return Encode::undefined(); + if (!::validateTypedArray(newObj)) + return scope.engine->throwTypeError(); + TypedArray *a = newObj->as<TypedArray>(); + Q_ASSERT(a); + if (a->length() < static_cast<uint>(len)) + return scope.engine->throwTypeError(); + + for (int k = 0; k < len; ++k) { + newObj->put(PropertyKey::fromArrayIndex(k), items[k]); + } + return newObj->asReturnedValue(); +} + +void IntrinsicTypedArrayPrototype::init(ExecutionEngine *engine, IntrinsicTypedArrayCtor *ctor) +{ + Scope scope(engine); + ctor->defineReadonlyProperty(engine->id_prototype(), *this); + ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0)); + ScopedString s(scope, engine->newString(QStringLiteral("TypedArray"))); + ctor->defineReadonlyConfigurableProperty(engine->id_name(), s); + s = scope.engine->newString(QStringLiteral("of")); + ctor->defineDefaultProperty(s, IntrinsicTypedArrayCtor::method_of); + ctor->addSymbolSpecies(); + + defineAccessorProperty(QStringLiteral("buffer"), method_get_buffer, nullptr); + defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, nullptr); + defineAccessorProperty(QStringLiteral("byteOffset"), method_get_byteOffset, nullptr); + defineAccessorProperty(QStringLiteral("length"), method_get_length, nullptr); + + defineDefaultProperty(QStringLiteral("copyWithin"), method_copyWithin, 2); + defineDefaultProperty(QStringLiteral("entries"), method_entries, 0); + defineDefaultProperty(QStringLiteral("every"), method_every, 1); + defineDefaultProperty(QStringLiteral("fill"), method_fill, 1); + defineDefaultProperty(QStringLiteral("filter"), method_filter, 1); + defineDefaultProperty(QStringLiteral("find"), method_find, 1); + defineDefaultProperty(QStringLiteral("findIndex"), method_findIndex, 1); + defineDefaultProperty(QStringLiteral("forEach"), method_forEach, 1); + defineDefaultProperty(QStringLiteral("includes"), method_includes, 1); + defineDefaultProperty(QStringLiteral("indexOf"), method_indexOf, 1); + defineDefaultProperty(QStringLiteral("join"), method_join, 1); + defineDefaultProperty(QStringLiteral("keys"), method_keys, 0); + defineDefaultProperty(QStringLiteral("lastIndexOf"), method_lastIndexOf, 1); + defineDefaultProperty(QStringLiteral("map"), method_map, 1); + defineDefaultProperty(QStringLiteral("reduce"), method_reduce, 1); + defineDefaultProperty(QStringLiteral("reduceRight"), method_reduceRight, 1); + defineDefaultProperty(QStringLiteral("reverse"), method_reverse, 0); + defineDefaultProperty(QStringLiteral("some"), method_some, 1); + defineDefaultProperty(QStringLiteral("set"), method_set, 1); + defineDefaultProperty(QStringLiteral("slice"), method_slice, 2); + defineDefaultProperty(QStringLiteral("subarray"), method_subarray, 2); + defineDefaultProperty(engine->id_toLocaleString(), method_toLocaleString, 0); + ScopedObject f(scope, engine->arrayPrototype()->get(engine->id_toString())); + defineDefaultProperty(engine->id_toString(), f); + + ScopedString valuesString(scope, engine->newIdentifier(QStringLiteral("values"))); + ScopedObject values(scope, FunctionObject::createBuiltinFunction(engine, valuesString, method_values, 0)); + defineDefaultProperty(QStringLiteral("values"), values); + defineDefaultProperty(engine->symbol_iterator(), values); + + defineAccessorProperty(engine->symbol_toStringTag(), method_get_toStringTag, nullptr); } |