blob: d03b67aa27909ce3aa3dd0835a70544c3abb8a63 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "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)NTypedArrayTypes);
static inline int toInt32(Value v)
{
Q_ASSERT(v.isNumber());
if (v.isInteger())
return v.integerValue();
return Double::toInt32(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 <typename T>
ReturnedValue typeToValue(T t) {
return Encode(t);
}
template <>
ReturnedValue typeToValue(ClampedUInt8 t) {
return Encode(t.c);
}
template <typename T>
T valueToType(Value value)
{
Q_ASSERT(value.isNumber());
int n = toInt32(value);
return static_cast<T>(n);
}
template <>
ClampedUInt8 valueToType(Value value)
{
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))
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<float>(d);
}
template <>
double valueToType(Value value)
{
Q_ASSERT(value.isNumber());
return toDouble(value);
}
template <typename T>
ReturnedValue read(const char *data) {
return typeToValue(*reinterpret_cast<const T *>(data));
}
template <typename T>
void write(char *data, Value value)
{
*reinterpret_cast<T *>(data) = valueToType<T>(value);
}
template <typename T>
ReturnedValue atomicAdd(char *data, Value 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);
}
template <typename T>
ReturnedValue atomicAnd(char *data, Value v)
{
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);
}
template <typename T>
ReturnedValue atomicExchange(char *data, Value 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);
}
template <typename T>
ReturnedValue atomicOr(char *data, Value v)
{
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);
}
template <typename T>
ReturnedValue atomicSub(char *data, Value 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);
}
template <typename T>
ReturnedValue atomicXor(char *data, Value v)
{
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);
}
template <typename T>
ReturnedValue atomicCompareExchange(char *data, Value expected, Value 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);
}
template <typename T>
ReturnedValue atomicLoad(char *data)
{
typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
T val = QAtomicOps<T>::loadRelaxed(*mem);
return typeToValue(val);
}
template <typename T>
ReturnedValue atomicStore(char *data, Value v)
{
T value = valueToType<T>(v);
typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
QAtomicOps<T>::storeRelaxed(*mem, value);
return typeToValue(value);
}
template<typename T>
constexpr TypedArrayOperations TypedArrayOperations::create(const char *name)
{
return { sizeof(T),
name,
::read<T>,
::write<T>,
{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr },
nullptr,
nullptr,
nullptr
};
}
template<typename T>
constexpr TypedArrayOperations TypedArrayOperations::createWithAtomics(const char *name)
{
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[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")
};
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<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 (!argc || !argv[0].isObject()) {
// ECMA 6 22.2.1.1
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)
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;
updateProto(scope, array);
return array.asReturnedValue();
}
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)
return Encode::undefined();
Scoped<TypedArray> 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->d()->data->data() + typedArray->d()->byteOffset;
char *dest = newBuffer->d()->data->data();
// 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<ArrayBuffer> 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->isDetachedBuffer())
return scope.engine->throwTypeError();
uint byteOffset = (uint)dbyteOffset;
uint elementSize = operations[that->d()->type].bytesPerElement;
if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->byteLength())
return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset"));
uint byteLength;
if (argc < 3 || argv[2].isUndefined()) {
byteLength = buffer->byteLength() - byteOffset;
if (buffer->byteLength() < 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.engine->hasException)
return Encode::undefined();
if (buffer->isDetachedBuffer())
return scope.engine->throwTypeError();
l *= elementSize;
if (buffer->byteLength() - byteOffset < l)
return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length"));
byteLength = (uint)l;
}
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 = 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.engine->hasException)
return scope.engine->throwTypeError();
uint elementSize = operations[that->d()->type].bytesPerElement;
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());
array->d()->byteLength = l * elementSize;
array->d()->byteOffset = 0;
uint idx = 0;
char *b = newBuffer->d()->data->data();
ScopedValue val(scope);
while (idx < l) {
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;
}
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<int>(t);
arrayType = static_cast<int>(t);
}
Heap::TypedArray *TypedArray::create(ExecutionEngine *e, Heap::TypedArray::Type 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::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<const Object *>(m)->engine());
Scoped<TypedArray> a(scope, static_cast<const TypedArray *>(m));
if (a->d()->buffer->isDetachedBuffer())
return scope.engine->throwTypeError();
if (!isArrayIndex || id.asArrayIndex() >= a->length()) {
if (hasProperty)
*hasProperty = false;
return Encode::undefined();
}
uint bytesPerElement = a->d()->type->bytesPerElement;
uint byteOffset = a->d()->byteOffset + id.asArrayIndex() * 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);
}
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<const TypedArray *>(m);
if (a->d()->buffer->isDetachedBuffer()) {
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<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 (!isArrayIndex)
return false;
const uint index = id.asArrayIndex();
if (index >= a->length())
return false;
uint bytesPerElement = a->d()->type->bytesPerElement;
uint byteOffset = a->d()->byteOffset + index * bytesPerElement;
Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength());
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;
}
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<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->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));
defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast<int>(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<TypedArray>();
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<TypedArray>();
if (!v)
return v4->throwTypeError();
if (v->d()->buffer->isDetachedBuffer())
return Encode(0);
return Encode(v->d()->byteLength);
}
ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteOffset(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
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()->byteOffset);
}
ReturnedValue IntrinsicTypedArrayPrototype::method_get_length(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
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);
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);
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<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);
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<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 *functionObject, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(functionObject);
Scoped<TypedArray> typedArray(scope, thisObject);
if (!typedArray || typedArray->d()->buffer->isDetachedBuffer())
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<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);
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<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);
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<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);
CHECK_EXCEPTION();
}
--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);
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<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);
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, argv[0]);
if (!srcTypedArray) {
// src is a regular object
ScopedObject o(scope, argv[0].toObject(scope.engine));
if (scope.engine->hasException || !o)
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)
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->isDetachedBuffer())
return scope.engine->throwTypeError();
char *b = buffer->d()->data->data() + a->d()->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->isDetachedBuffer())
return scope.engine->throwTypeError();
a->d()->type->write(b, val);
if (scope.engine->hasException)
RETURN_UNDEFINED();
++idx;
b += elementSize;
}
RETURN_UNDEFINED();
}
// src is a typed array
Scoped<ArrayBuffer> srcBuffer(scope, srcTypedArray->d()->buffer);
if (!srcBuffer || srcBuffer->isDetachedBuffer())
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->d()->data->data() + a->d()->byteOffset + offset*elementSize;
const char *src = srcBuffer->d()->data->data() + srcTypedArray->d()->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->d()->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->d()->byteLength];
memcpy(srcCopy, src, srcTypedArray->d()->byteLength);
src = srcCopy;
}
// typed arrays of different kind, need to manually loop
uint srcElementSize = srcTypedArray->d()->type->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<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)
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);
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.engine->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->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();
}
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();
}
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<FunctionObject>();
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<qint64>(1) << 53) - 1) {
ScopedValue falsey(scope, Encode(false));
ScopedValue error(scope, scope.engine->throwTypeError());
return Runtime::IteratorClose::call(scope.engine, lengthIterator, falsey);
}
// Retrieve the next value. If the iteration ends, we're done here.
done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, lengthIterator, nextValue));
if (scope.engine->hasException)
return Runtime::IteratorClose::call(scope.engine, lengthIterator, Value::fromBoolean(false));
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.engine->hasException)
return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(false));
if (mapfn) {
mapArguments[0] = *nextValue;
mapArguments[1] = Value::fromDouble(k);
mappedValue = mapfn->call(thisArg, mapArguments, 2);
if (scope.engine->hasException)
return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(false));
} else {
mappedValue = *nextValue;
}
a->put(k, mappedValue);
if (scope.engine->hasException)
return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(false));
}
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);
}