diff options
Diffstat (limited to 'src/qml/jsruntime/qv4typedarray.cpp')
-rw-r--r-- | src/qml/jsruntime/qv4typedarray.cpp | 584 |
1 files changed, 362 insertions, 222 deletions
diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index 7d33167762..9c752f43bb 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -1,47 +1,9 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qv4typedarray_p.h" #include "qv4arrayiterator_p.h" #include "qv4arraybuffer_p.h" -#include "qv4string_p.h" -#include "qv4jscall_p.h" #include "qv4symbol_p.h" #include "qv4runtime_p.h" #include <QtCore/qatomic.h> @@ -63,7 +25,7 @@ static inline int toInt32(Value v) Q_ASSERT(v.isNumber()); if (v.isInteger()) return v.integerValue(); - return Double::toInt32(v.doubleValue()); + return QJSNumberCoercion::toInteger(v.doubleValue()); } static inline double toDouble(Value v) @@ -274,9 +236,9 @@ const TypedArrayOperations operations[NTypedArrayTypes] = { }; -void Heap::TypedArrayCtor::init(QV4::ExecutionContext *scope, TypedArray::Type t) +void Heap::TypedArrayCtor::init(QV4::ExecutionEngine *engine, TypedArray::Type t) { - Heap::FunctionObject::init(scope, QLatin1String(operations[t].name)); + Heap::FunctionObject::init(engine, QLatin1String(operations[t].name)); type = t; } @@ -296,18 +258,21 @@ ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, if (!argc || !argv[0].isObject()) { // ECMA 6 22.2.1.1 - qint64 l = argc ? argv[0].toIndex() : 0; - if (scope.engine->hasException) + const double l = argc ? argv[0].toInteger() : 0; + if (scope.hasException()) return Encode::undefined(); - // ### lift UINT_MAX restriction - if (l < 0 || l > UINT_MAX) + if (l < 0 || l > std::numeric_limits<int>::max()) + return scope.engine->throwRangeError(QLatin1String("Index out of range.")); + + const double byteLength = l * operations[that->d()->type].bytesPerElement; + + // TODO: This is an artificial restriction due to the fact that we store the byteLength in + // uint below. We should allow up to INT_MAX elements of any size. + if (byteLength > std::numeric_limits<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) + + Scoped<ArrayBuffer> buffer(scope, scope.engine->newArrayBuffer(size_t(byteLength))); + if (scope.hasException()) return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); @@ -322,15 +287,15 @@ ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, if (!!typedArray) { // ECMA 6 22.2.1.2 Scoped<ArrayBuffer> buffer(scope, typedArray->d()->buffer); - if (!buffer || buffer->isDetachedBuffer()) + if (!buffer || buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); - uint srcElementSize = typedArray->d()->type->bytesPerElement; + uint srcElementSize = typedArray->bytesPerElement(); uint destElementSize = operations[that->d()->type].bytesPerElement; - uint byteLength = typedArray->d()->byteLength; + uint byteLength = typedArray->byteLength(); uint destByteLength = byteLength*destElementSize/srcElementSize; Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(destByteLength)); - if (scope.engine->hasException) + if (scope.hasException()) return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); @@ -338,8 +303,8 @@ ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, array->d()->byteLength = destByteLength; array->d()->byteOffset = 0; - const char *src = buffer->d()->data->data() + typedArray->d()->byteOffset; - char *dest = newBuffer->d()->data->data(); + const char *src = buffer->constArrayData() + typedArray->byteOffset(); + char *dest = newBuffer->arrayData(); // check if src and new type have the same size. In that case we can simply memcpy the data if (srcElementSize == destElementSize) { @@ -365,27 +330,27 @@ ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, double dbyteOffset = argc > 1 ? argv[1].toInteger() : 0; - if (buffer->isDetachedBuffer()) + if (buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint byteOffset = (uint)dbyteOffset; uint elementSize = operations[that->d()->type].bytesPerElement; - if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->byteLength()) + if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->arrayDataLength()) return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset")); uint byteLength; if (argc < 3 || argv[2].isUndefined()) { - byteLength = buffer->byteLength() - byteOffset; - if (buffer->byteLength() < byteOffset || byteLength % elementSize) + byteLength = buffer->arrayDataLength() - byteOffset; + if (buffer->arrayDataLength() < byteOffset || byteLength % elementSize) return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); } else { double l = qBound(0., argv[2].toInteger(), (double)UINT_MAX); - if (scope.engine->hasException) + if (scope.hasException()) return Encode::undefined(); - if (buffer->isDetachedBuffer()) + if (buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); l *= elementSize; - if (buffer->byteLength() - byteOffset < l) + if (buffer->arrayDataLength() - byteOffset < l) return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); byteLength = (uint)l; } @@ -403,15 +368,15 @@ ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, 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) + if (scope.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)) + if (qMulOverflow(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) + if (scope.hasException()) return Encode::undefined(); Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type)); @@ -420,15 +385,15 @@ ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, array->d()->byteOffset = 0; uint idx = 0; - char *b = newBuffer->d()->data->data(); + char *b = newBuffer->arrayData(); ScopedValue val(scope); while (idx < l) { val = o->get(idx); val = val->convertedToNumber(); - if (scope.engine->hasException) + if (scope.hasException()) return Encode::undefined(); array->d()->type->write(b, val); - if (scope.engine->hasException) + if (scope.hasException()) return Encode::undefined(); ++idx; b += elementSize; @@ -465,7 +430,7 @@ ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Val Scope scope(static_cast<const Object *>(m)->engine()); Scoped<TypedArray> a(scope, static_cast<const TypedArray *>(m)); - if (a->d()->buffer->isDetachedBuffer()) + if (a->hasDetachedArrayData()) return scope.engine->throwTypeError(); if (!isArrayIndex || id.asArrayIndex() >= a->length()) { @@ -474,13 +439,13 @@ ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Val 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()); + uint bytesPerElement = a->bytesPerElement(); + uint byteOffset = a->byteOffset() + id.asArrayIndex() * bytesPerElement; + Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength()); if (hasProperty) *hasProperty = true; - return a->d()->type->read(a->d()->buffer->data->data() + byteOffset); + return a->d()->type->read(a->constArrayData() + byteOffset); } bool TypedArray::virtualHasProperty(const Managed *m, PropertyKey id) @@ -490,7 +455,7 @@ bool TypedArray::virtualHasProperty(const Managed *m, PropertyKey id) return Object::virtualHasProperty(m, id); const TypedArray *a = static_cast<const TypedArray *>(m); - if (a->d()->buffer->isDetachedBuffer()) { + if (a->hasDetachedArrayData()) { a->engine()->throwTypeError(); return false; } @@ -521,7 +486,7 @@ bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Valu Scope scope(v4); Scoped<TypedArray> a(scope, static_cast<TypedArray *>(m)); - if (a->d()->buffer->isDetachedBuffer()) + if (a->hasDetachedArrayData()) return scope.engine->throwTypeError(); if (!isArrayIndex) @@ -531,14 +496,14 @@ bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Valu 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()); + uint bytesPerElement = a->bytesPerElement(); + uint byteOffset = a->byteOffset() + index * bytesPerElement; + Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength()); Value v = Value::fromReturnedValue(value.convertedToNumber()); - if (scope.hasException() || a->d()->buffer->isDetachedBuffer()) + if (scope.hasException() || a->hasDetachedArrayData()) return scope.engine->throwTypeError(); - a->d()->type->write(a->d()->buffer->data->data() + byteOffset, v); + a->d()->type->write(a->arrayData() + byteOffset, v); return true; } @@ -564,12 +529,12 @@ bool TypedArray::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Prop ExecutionEngine *engine = a->engine(); Value v = Value::fromReturnedValue(p->value.convertedToNumber()); - if (engine->hasException || a->d()->buffer->isDetachedBuffer()) + if (engine->hasException || a->hasDetachedArrayData()) 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); + uint bytesPerElement = a->bytesPerElement(); + uint byteOffset = a->byteOffset() + index * bytesPerElement; + Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength()); + a->d()->type->write(a->arrayData() + byteOffset, v); } return true; } @@ -638,10 +603,10 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteLength(const Function if (!v) return v4->throwTypeError(); - if (v->d()->buffer->isDetachedBuffer()) + if (v->hasDetachedArrayData()) return Encode(0); - return Encode(v->d()->byteLength); + return Encode(v->byteLength()); } ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteOffset(const FunctionObject *b, const Value *thisObject, const Value *, int) @@ -651,10 +616,10 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteOffset(const Function if (!v) return v4->throwTypeError(); - if (v->d()->buffer->isDetachedBuffer()) + if (v->hasDetachedArrayData()) return Encode(0); - return Encode(v->d()->byteOffset); + return Encode(v->byteOffset()); } ReturnedValue IntrinsicTypedArrayPrototype::method_get_length(const FunctionObject *b, const Value *thisObject, const Value *, int) @@ -664,67 +629,66 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_get_length(const FunctionObje if (!v) return v4->throwTypeError(); - if (v->d()->buffer->isDetachedBuffer()) + if (v->hasDetachedArrayData()) return Encode(0); - return Encode(v->d()->byteLength/v->d()->type->bytesPerElement); + return Encode(v->length()); } 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()) + Scoped<TypedArray> instance(scope, thisObject); + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); if (!argc) - return O->asReturnedValue(); + return instance->asReturnedValue(); - qint64 len = static_cast<uint>(O->length()); + const double len = instance->length(); + Q_ASSERT(std::isfinite(len)); - qint64 to = static_cast<qint64>(argv[0].toInteger()); - if (to < 0) - to = qMax(len + to, 0ll); - else - to = qMin(to, len); + const double target = argv[0].toInteger(); - 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); + const double start = (argc > 1) + ? argv[1].toInteger() + : 0; - qint64 count = qMin(end - from, len - to); + const double end = (argc > 2 && !argv[2].isUndefined()) + ? argv[2].toInteger() + : len; - if (count <= 0) - return O->asReturnedValue(); + const double fin = end < 0 + ? std::max(len + end, 0.0) + : std::min(end, len); - if (O->d()->buffer->isDetachedBuffer()) - return scope.engine->throwTypeError(); + const qsizetype from = start < 0 + ? std::max(len + start, 0.0) + : std::min(start, len); + + const qsizetype to = target < 0 + ? std::max(len + target, 0.0) + : std::min(target, len); + + const qsizetype count = std::min(fin - from, len - to); + + if (count <= 0) + return instance->asReturnedValue(); if (from != to) { - int elementSize = O->d()->type->bytesPerElement; - char *data = O->d()->buffer->data->data() + O->d()->byteOffset; - memmove(data + to*elementSize, data + from*elementSize, count*elementSize); + int elementSize = instance->bytesPerElement(); + char *data = instance->arrayData() + instance->byteOffset(); + memmove(data + to * elementSize, data + from * elementSize, count * elementSize); } - return O->asReturnedValue(); + return instance->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()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); @@ -736,7 +700,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_every(const FunctionObject *b { Scope scope(b); Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); @@ -749,13 +713,13 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_every(const FunctionObject *b 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; + const char *data = v->constArrayData(); + uint bytesPerElement = v->bytesPerElement(); + uint byteOffset = v->byteOffset(); bool ok = true; for (uint k = 0; ok && k < len; ++k) { - if (v->d()->buffer->isDetachedBuffer()) + if (v->hasDetachedArrayData()) return scope.engine->throwTypeError(); arguments[0] = v->d()->type->read(data + byteOffset + k * bytesPerElement); @@ -763,6 +727,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_every(const FunctionObject *b arguments[1] = Value::fromDouble(k); arguments[2] = v; r = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); ok = r->toBoolean(); } return Encode(ok); @@ -772,7 +737,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, { Scope scope(b); Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); @@ -797,14 +762,20 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, 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()) + if (scope.hasException() || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); - char *data = v->d()->buffer->data->data(); - uint bytesPerElement = v->d()->type->bytesPerElement; - uint byteOffset = v->d()->byteOffset; + char *data = v->arrayData(); + uint bytesPerElement = v->bytesPerElement(); + uint byteOffset = v->byteOffset(); + + Value value; + if (!argc) + value.setDouble(std::numeric_limits<double>::quiet_NaN()); + else if (argv[0].isNumber()) + value = argv[0]; + else + value.setDouble(argv[0].toNumber()); while (k < fin) { v->d()->type->write(data + byteOffset + k * bytesPerElement, value); @@ -825,7 +796,7 @@ static TypedArray *typedArraySpeciesCreate(Scope &scope, const TypedArray *insta 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) { + if (!a || a->hasDetachedArrayData() || a->length() < len) { scope.engine->throwTypeError(); return nullptr; } @@ -836,7 +807,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_filter(const FunctionObject * { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); @@ -852,7 +823,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_filter(const FunctionObject * uint to = 0; for (uint k = 0; k < len; ++k) { - if (instance->d()->buffer->isDetachedBuffer()) + if (instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); bool exists; arguments[0] = instance->get(k, &exists); @@ -862,6 +833,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_filter(const FunctionObject * arguments[1] = Value::fromDouble(k); arguments[2] = instance; selected = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); if (selected->toBoolean()) { ++arguments; scope.alloc(1); @@ -883,7 +855,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_find(const FunctionObject *b, { Scope scope(b); Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); @@ -898,7 +870,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_find(const FunctionObject *b, ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); for (uint k = 0; k < len; ++k) { - if (v->d()->buffer->isDetachedBuffer()) + if (v->hasDetachedArrayData()) return scope.engine->throwTypeError(); arguments[0] = v->get(k); CHECK_EXCEPTION(); @@ -919,7 +891,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_findIndex(const FunctionObjec { Scope scope(b); Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); @@ -934,7 +906,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_findIndex(const FunctionObjec ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); for (uint k = 0; k < len; ++k) { - if (v->d()->buffer->isDetachedBuffer()) + if (v->hasDetachedArrayData()) return scope.engine->throwTypeError(); arguments[0] = v->get(k); CHECK_EXCEPTION(); @@ -955,7 +927,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_forEach(const FunctionObject { Scope scope(b); Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); @@ -968,7 +940,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_forEach(const FunctionObject Value *arguments = scope.alloc(3); for (uint k = 0; k < len; ++k) { - if (v->d()->buffer->isDetachedBuffer()) + if (v->hasDetachedArrayData()) return scope.engine->throwTypeError(); bool exists; arguments[0] = v->get(k, &exists); @@ -987,7 +959,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_includes(const FunctionObject { Scope scope(b); Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); @@ -1025,7 +997,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_indexOf(const FunctionObject { Scope scope(b); Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = v->length(); @@ -1068,58 +1040,51 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_indexOf(const FunctionObject return Encode(-1); } -ReturnedValue IntrinsicTypedArrayPrototype::method_join(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) +ReturnedValue IntrinsicTypedArrayPrototype::method_join( + const FunctionObject *functionObject, const Value *thisObject, const Value *argv, int argc) { - Scope scope(b); - Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + Scope scope(functionObject); + Scoped<TypedArray> typedArray(scope, thisObject); + if (!typedArray || typedArray->hasDetachedArrayData()) return scope.engine->throwTypeError(); - uint len = v->length(); - - ScopedValue arg(scope, argc ? argv[0] : Value::undefinedValue()); + // 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(); - QString r4; - if (arg->isUndefined()) - r4 = QStringLiteral(","); - else - r4 = arg->toQString(); - - const quint32 r2 = len; - - if (!r2) + const quint32 length = typedArray->length(); + if (!length) return Encode(scope.engine->newString()); - QString R; + QString result; - // - // crazy! - // ScopedString name(scope, scope.engine->newString(QStringLiteral("0"))); - ScopedValue r6(scope, v->get(name)); - if (!r6->isNullOrUndefined()) - R = r6->toQString(); + ScopedValue value(scope, typedArray->get(name)); + if (!value->isNullOrUndefined()) + result = value->toQString(); - ScopedValue r12(scope); - for (quint32 k = 1; k < r2; ++k) { - R += r4; + for (quint32 i = 1; i < length; ++i) { + result += separator; - name = Value::fromDouble(k).toString(scope.engine); - r12 = v->get(name); + name = Value::fromDouble(i).toString(scope.engine); + value = typedArray->get(name); CHECK_EXCEPTION(); - if (!r12->isNullOrUndefined()) - R += r12->toQString(); + if (!value->isNullOrUndefined()) + result += value->toQString(); } - return Encode(scope.engine->newString(R)); + 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()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); @@ -1132,7 +1097,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_lastIndexOf(const FunctionObj { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); @@ -1175,7 +1140,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_map(const FunctionObject *b, { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); @@ -1194,13 +1159,14 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_map(const FunctionObject *b, Value *arguments = scope.alloc(3); for (uint k = 0; k < len; ++k) { - if (instance->d()->buffer->isDetachedBuffer()) + if (instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); arguments[0] = instance->get(k); arguments[1] = Value::fromDouble(k); arguments[2] = instance; mapped = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); a->put(k, mapped); } return a->asReturnedValue(); @@ -1210,7 +1176,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reduce(const FunctionObject * { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); @@ -1240,7 +1206,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reduce(const FunctionObject * Value *arguments = scope.alloc(4); while (k < len) { - if (instance->d()->buffer->isDetachedBuffer()) + if (instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); bool kPresent; v = instance->get(k, &kPresent); @@ -1250,6 +1216,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reduce(const FunctionObject * arguments[2] = Value::fromDouble(k); arguments[3] = instance; acc = callback->call(nullptr, arguments, 4); + CHECK_EXCEPTION(); } ++k; } @@ -1260,7 +1227,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObj { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); @@ -1295,7 +1262,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObj Value *arguments = scope.alloc(4); while (k > 0) { - if (instance->d()->buffer->isDetachedBuffer()) + if (instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); bool kPresent; v = instance->get(k - 1, &kPresent); @@ -1305,6 +1272,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObj arguments[2] = Value::fromDouble(k - 1); arguments[3] = instance; acc = callback->call(nullptr, arguments, 4); + CHECK_EXCEPTION(); } --k; } @@ -1315,7 +1283,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reverse(const FunctionObject { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint length = instance->length(); @@ -1342,7 +1310,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_some(const FunctionObject *b, { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); @@ -1356,7 +1324,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_some(const FunctionObject *b, Value *arguments = scope.alloc(3); for (uint k = 0; k < len; ++k) { - if (instance->d()->buffer->isDetachedBuffer()) + if (instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); bool exists; arguments[0] = instance->get(k, &exists); @@ -1366,6 +1334,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_some(const FunctionObject *b, arguments[1] = Value::fromDouble(k); arguments[2] = instance; result = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); if (result->toBoolean()) return Encode(true); } @@ -1377,7 +1346,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_values(const FunctionObject * { Scope scope(b); Scoped<TypedArray> v(scope, thisObject); - if (!v || v->d()->buffer->isDetachedBuffer()) + if (!v || v->hasDetachedArrayData()) return scope.engine->throwTypeError(); Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v)); @@ -1394,45 +1363,46 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, Scoped<ArrayBuffer> buffer(scope, a->d()->buffer); double doffset = argc >= 2 ? argv[1].toInteger() : 0; - if (scope.engine->hasException) + if (scope.hasException()) RETURN_UNDEFINED(); - if (!buffer || buffer->isDetachedBuffer()) + if (!buffer || buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); if (doffset < 0 || doffset >= UINT_MAX) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); uint offset = (uint)doffset; - uint elementSize = a->d()->type->bytesPerElement; + uint elementSize = a->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) + if (scope.hasException() || !o) return scope.engine->throwTypeError(); double len = ScopedValue(scope, o->get(scope.engine->id_length()))->toNumber(); uint l = (uint)len; - if (scope.engine->hasException || l != len) + if (scope.hasException() || l != len) return scope.engine->throwTypeError(); - if (offset + l > a->length()) + 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()) + if (buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); - char *b = buffer->d()->data->data() + a->d()->byteOffset + offset*elementSize; + char *b = buffer->arrayData() + a->byteOffset() + offset*elementSize; ScopedValue val(scope); while (idx < l) { val = o->get(idx); if (scope.hasException()) return Encode::undefined(); val = val->convertedToNumber(); - if (scope.hasException() || buffer->isDetachedBuffer()) + if (scope.hasException() || buffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); a->d()->type->write(b, val); - if (scope.engine->hasException) + if (scope.hasException()) RETURN_UNDEFINED(); ++idx; b += elementSize; @@ -1442,31 +1412,33 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, // src is a typed array Scoped<ArrayBuffer> srcBuffer(scope, srcTypedArray->d()->buffer); - if (!srcBuffer || srcBuffer->isDetachedBuffer()) + if (!srcBuffer || srcBuffer->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint l = srcTypedArray->length(); - if (offset + l > a->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; + char *dest = buffer->arrayData() + a->byteOffset() + offset*elementSize; + const char *src = srcBuffer->d()->constArrayData() + srcTypedArray->byteOffset(); if (srcTypedArray->d()->type == a->d()->type) { // same type of typed arrays, use memmove (as srcbuffer and buffer could be the same) - memmove(dest, src, srcTypedArray->d()->byteLength); + memmove(dest, src, srcTypedArray->byteLength()); RETURN_UNDEFINED(); } char *srcCopy = nullptr; if (buffer->d() == srcBuffer->d()) { // same buffer, need to take a temporary copy, to not run into problems - srcCopy = new char[srcTypedArray->d()->byteLength]; - memcpy(srcCopy, src, srcTypedArray->d()->byteLength); + srcCopy = new char[srcTypedArray->byteLength()]; + memcpy(srcCopy, src, srcTypedArray->byteLength()); src = srcCopy; } // typed arrays of different kind, need to manually loop - uint srcElementSize = srcTypedArray->d()->type->bytesPerElement; + uint srcElementSize = srcTypedArray->bytesPerElement(); TypedArrayOperations::Read read = srcTypedArray->d()->type->read; TypedArrayOperations::Write write = a->d()->type->write; for (uint i = 0; i < l; ++i) { @@ -1485,7 +1457,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_slice(const FunctionObject *b { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); @@ -1517,10 +1489,10 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_slice(const FunctionObject *b ScopedValue v(scope); uint n = 0; for (uint i = start; i < end; ++i) { - if (instance->d()->buffer->isDetachedBuffer()) + if (instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); v = instance->get(i); - if (a->d()->buffer->isDetachedBuffer()) + if (a->hasDetachedArrayData()) return scope.engine->throwTypeError(); a->put(n, v); ++n; @@ -1552,7 +1524,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_subarray(const FunctionObject if (end < begin) end = begin; - if (scope.engine->hasException) + if (scope.hasException()) RETURN_UNDEFINED(); int newLen = end - begin; @@ -1563,10 +1535,10 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_subarray(const FunctionObject Value *arguments = scope.alloc(3); arguments[0] = buffer; - arguments[1] = Encode(a->d()->byteOffset + begin*a->d()->type->bytesPerElement); + arguments[1] = Encode(a->byteOffset() + begin * a->bytesPerElement()); arguments[2] = Encode(newLen); a = constructor->callAsConstructor(arguments, 3); - if (!a || a->d()->buffer->isDetachedBuffer()) + if (!a || a->hasDetachedArrayData()) return scope.engine->throwTypeError(); return a->asReturnedValue(); } @@ -1575,7 +1547,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_toLocaleString(const Function { Scope scope(b); Scoped<TypedArray> instance(scope, thisObject); - if (!instance || instance->d()->buffer->isDetachedBuffer()) + if (!instance || instance->hasDetachedArrayData()) return scope.engine->throwTypeError(); uint len = instance->length(); @@ -1586,14 +1558,29 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_toLocaleString(const Function ScopedValue v(scope); ScopedString s(scope); + ScopedPropertyKey tolocaleString(scope, scope.engine->id_toLocaleString()->toPropertyKey()); + Q_ASSERT(!scope.engine->hasException); + for (uint k = 0; k < len; ++k) { - if (instance->d()->buffer->isDetachedBuffer()) + if (instance->hasDetachedArrayData()) 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); + Q_ASSERT(!v->isNullOrUndefined()); // typed array cannot hold null or undefined + + ScopedObject valueAsObject(scope, v->toObject(scope.engine)); + Q_ASSERT(valueAsObject); // only null or undefined cannot be converted to object + + ScopedFunctionObject function(scope, valueAsObject->get(tolocaleString)); + if (!function) + return scope.engine->throwTypeError(); + + v = function->call(valueAsObject, nullptr, 0); + if (scope.hasException()) + return Encode::undefined(); + s = v->toString(scope.engine); if (scope.hasException()) return Encode::undefined(); @@ -1617,7 +1604,7 @@ static bool validateTypedArray(const Object *o) const TypedArray *a = o->as<TypedArray>(); if (!a) return false; - if (a->d()->buffer->isDetachedBuffer()) + if (a->hasDetachedArrayData()) return false; return true; } @@ -1648,6 +1635,157 @@ ReturnedValue IntrinsicTypedArrayCtor::method_of(const FunctionObject *f, const 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 error(scope, scope.engine->throwTypeError()); + return Runtime::IteratorClose::call(scope.engine, lengthIterator); + } + // Retrieve the next value. If the iteration ends, we're done here. + done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, lengthIterator, nextValue)); + if (scope.hasException()) + return Runtime::IteratorClose::call(scope.engine, lengthIterator); + if (done->toBoolean()) { + break; + } + iterableLength++; + } + + // Constructor validity check goes after we have calculated the length, because that calculation can throw + // errors that are not type errors and at least the tests expect those rather than type errors. + if (!C || !C->isConstructor()) + return scope.engine->throwTypeError(); + + ScopedObject iterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true)); + CHECK_EXCEPTION(); // symbol_iterator can throw. + if (!iterator) { + return scope.engine->throwTypeError(); // symbol_iterator wasn't an object. + } + + ScopedObject a(scope, Value::undefinedValue()); + ScopedValue ctorArgument(scope, Value::fromReturnedValue(QV4::Encode(int(iterableLength)))); + a = C->callAsConstructor(ctorArgument, 1); + CHECK_EXCEPTION(); + + // We check exceptions above, and only after doing so, check the array's validity after construction. + if (!::validateTypedArray(a) || (a->getLength() < iterableLength)) + return scope.engine->throwTypeError(); + + + // The loop below traverses the iterator, and puts elements into the created array. + ScopedValue mappedValue(scope, Value::undefinedValue()); + for (qint64 k = 0; k < iterableLength; ++k) { + done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, iterator, nextValue)); + if (scope.hasException()) + return Runtime::IteratorClose::call(scope.engine, iterator); + + if (mapfn) { + mapArguments[0] = *nextValue; + mapArguments[1] = Value::fromDouble(k); + mappedValue = mapfn->call(thisArg, mapArguments, 2); + if (scope.hasException()) + return Runtime::IteratorClose::call(scope.engine, iterator); + } else { + mappedValue = *nextValue; + } + + a->put(k, mappedValue); + if (scope.hasException()) + return Runtime::IteratorClose::call(scope.engine, iterator); + } + return a.asReturnedValue(); + } else { + // Array-like fallback. We request elements by index, and put them into the created array. + ScopedObject arrayLike(scope, argv[0].toObject(scope.engine)); + if (!arrayLike) + return scope.engine->throwTypeError(QString::fromLatin1("Cannot convert %1 to object").arg(argv[0].toQStringNoThrow())); + + int len = arrayLike->getLength(); + CHECK_EXCEPTION(); + + // Getting the length may throw, and must do so before we check the constructor validity. + if (!C || !C->isConstructor()) + return scope.engine->throwTypeError(); + + ScopedObject a(scope, Value::undefinedValue()); + ScopedValue ctorArgument(scope, Value::fromReturnedValue(QV4::Encode(len))); + a = C->callAsConstructor(ctorArgument, 1); + CHECK_EXCEPTION(); + + // We check exceptions above, and only after doing so, check the array's validity after construction. + if (!::validateTypedArray(a) || (a->getLength() < len)) + return scope.engine->throwTypeError(); + + ScopedValue mappedValue(scope, Value::undefinedValue()); + ScopedValue kValue(scope); + for (int k = 0; k < len; ++k) { + kValue = arrayLike->get(k); + CHECK_EXCEPTION(); + + if (mapfn) { + mapArguments[0] = kValue; + mapArguments[1] = Value::fromDouble(k); + mappedValue = mapfn->call(thisArg, mapArguments, 2); + CHECK_EXCEPTION(); + } else { + mappedValue = kValue; + } + + a->put(k, mappedValue); + CHECK_EXCEPTION(); + } + return a.asReturnedValue(); + } +} + void IntrinsicTypedArrayPrototype::init(ExecutionEngine *engine, IntrinsicTypedArrayCtor *ctor) { Scope scope(engine); @@ -1657,6 +1795,8 @@ void IntrinsicTypedArrayPrototype::init(ExecutionEngine *engine, IntrinsicTypedA 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); |