/**************************************************************************** ** ** 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 "qv4arraybuffer_p.h" #include "qv4string_p.h" #include using namespace QV4; DEFINE_OBJECT_VTABLE(TypedArrayCtor); DEFINE_OBJECT_VTABLE(TypedArrayPrototype); DEFINE_OBJECT_VTABLE(TypedArray); Q_STATIC_ASSERT((int)ExecutionEngine::NTypedArrayTypes == (int)Heap::TypedArray::NTypes); ReturnedValue Int8ArrayRead(const char *data, int index) { return Encode((int)(signed char)data[index]); } void Int8ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { signed char v = (signed char)value.toUInt32(); if (e->hasException) return; data[index] = v; } ReturnedValue UInt8ArrayRead(const char *data, int index) { return Encode((int)(unsigned char)data[index]); } void UInt8ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { unsigned char v = (unsigned char)value.toUInt32(); if (e->hasException) return; data[index] = v; } void UInt8ClampedArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { if (value.isInteger()) { data[index] = (char)(unsigned char)qBound(0, value.integerValue(), 255); return; } double d = value.toNumber(); if (e->hasException) return; // ### is there a way to optimise this? if (d <= 0 || std::isnan(d)) { data[index] = 0; return; } if (d >= 255) { data[index] = (char)(255); return; } double f = std::floor(d); if (f + 0.5 < d) { data[index] = (unsigned char)(f + 1); return; } if (d < f + 0.5) { data[index] = (unsigned char)(f); return; } if (int(f) % 2) { // odd number data[index] = (unsigned char)(f + 1); return; } data[index] = (unsigned char)(f); } ReturnedValue Int16ArrayRead(const char *data, int index) { return Encode((int)*(const short *)(data + index)); } void Int16ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { short v = (short)value.toInt32(); if (e->hasException) return; *(short *)(data + index) = v; } ReturnedValue UInt16ArrayRead(const char *data, int index) { return Encode((int)*(const unsigned short *)(data + index)); } void UInt16ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { unsigned short v = (unsigned short)value.toInt32(); if (e->hasException) return; *(unsigned short *)(data + index) = v; } ReturnedValue Int32ArrayRead(const char *data, int index) { return Encode(*(const int *)(data + index)); } void Int32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { int v = (int)value.toInt32(); if (e->hasException) return; *(int *)(data + index) = v; } ReturnedValue UInt32ArrayRead(const char *data, int index) { return Encode(*(const unsigned int *)(data + index)); } void UInt32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { unsigned int v = (unsigned int)value.toUInt32(); if (e->hasException) return; *(unsigned int *)(data + index) = v; } ReturnedValue Float32ArrayRead(const char *data, int index) { return Encode(*(const float *)(data + index)); } void Float32ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { float v = value.toNumber(); if (e->hasException) return; *(float *)(data + index) = v; } ReturnedValue Float64ArrayRead(const char *data, int index) { return Encode(*(const double *)(data + index)); } void Float64ArrayWrite(ExecutionEngine *e, char *data, int index, const Value &value) { double v = value.toNumber(); if (e->hasException) return; *(double *)(data + index) = v; } const TypedArrayOperations operations[Heap::TypedArray::NTypes] = { { 1, "Int8Array", Int8ArrayRead, Int8ArrayWrite }, { 1, "Uint8Array", UInt8ArrayRead, UInt8ArrayWrite }, { 1, "Uint8ClampedArray", UInt8ArrayRead, UInt8ClampedArrayWrite }, { 2, "Int16Array", Int16ArrayRead, Int16ArrayWrite }, { 2, "Uint16Array", UInt16ArrayRead, UInt16ArrayWrite }, { 4, "Int32Array", Int32ArrayRead, Int32ArrayWrite }, { 4, "Uint32Array", UInt32ArrayRead, UInt32ArrayWrite }, { 4, "Float32Array", Float32ArrayRead, Float32ArrayWrite }, { 8, "Float64Array", Float64ArrayRead, Float64ArrayWrite }, }; void Heap::TypedArrayCtor::init(QV4::ExecutionContext *scope, TypedArray::Type t) { Heap::FunctionObject::init(scope, QLatin1String(operations[t].name)); type = t; } void TypedArrayCtor::construct(const Managed *m, Scope &scope, CallData *callData) { Scoped that(scope, static_cast(m)); if (!callData->argc || !callData->args[0].isObject()) { // ECMA 6 22.2.1.1 double l = callData->argc ? callData->args[0].toNumber() : 0; if (scope.engine->hasException) { scope.result = Encode::undefined(); return; } 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 buffer(scope, scope.engine->newArrayBuffer(byteLength)); if (scope.engine->hasException) { scope.result = Encode::undefined(); return; } Scoped array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer = buffer->d(); array->d()->byteLength = byteLength; array->d()->byteOffset = 0; scope.result = array.asReturnedValue(); return; } Scoped typedArray(scope, callData->argument(0)); if (!!typedArray) { // ECMA 6 22.2.1.2 Scoped buffer(scope, typedArray->d()->buffer); uint srcElementSize = typedArray->d()->type->bytesPerElement; uint destElementSize = operations[that->d()->type].bytesPerElement; uint byteLength = typedArray->d()->byteLength; uint destByteLength = byteLength*destElementSize/srcElementSize; Scoped newBuffer(scope, scope.engine->newArrayBuffer(destByteLength)); if (scope.engine->hasException) { scope.result = Encode::undefined(); return; } Scoped array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer = 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(); TypedArrayRead read = typedArray->d()->type->read; TypedArrayWrite write =array->d()->type->write; for (uint i = 0; i < l; ++i) { Primitive val; val.setRawValue(read(src, i*srcElementSize)); write(scope.engine, dest, i*destElementSize, val); } } scope.result = array.asReturnedValue(); return; } Scoped buffer(scope, callData->argument(0)); if (!!buffer) { // ECMA 6 22.2.1.4 double dbyteOffset = callData->argc > 1 ? callData->args[1].toInteger() : 0; uint byteOffset = (uint)dbyteOffset; uint elementSize = operations[that->d()->type].bytesPerElement; if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->byteLength()) { scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset")); return; } uint byteLength; if (callData->argc < 3 || callData->args[2].isUndefined()) { byteLength = buffer->byteLength() - byteOffset; if (buffer->byteLength() < byteOffset || byteLength % elementSize) { scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); return; } } else { double l = qBound(0., callData->args[2].toInteger(), (double)UINT_MAX); if (scope.engine->hasException) { scope.result = Encode::undefined(); return; } l *= elementSize; if (buffer->byteLength() - byteOffset < l) { scope.result = scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length")); return; } byteLength = (uint)l; } Scoped array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer = buffer->d(); array->d()->byteLength = byteLength; array->d()->byteOffset = byteOffset; scope.result = array.asReturnedValue(); return; } // ECMA 6 22.2.1.3 ScopedObject o(scope, callData->argument(0)); uint l = (uint) qBound(0., ScopedValue(scope, o->get(scope.engine->id_length()))->toInteger(), (double)UINT_MAX); if (scope.engine->hasException) { scope.result = scope.engine->throwTypeError(); return; } uint elementSize = operations[that->d()->type].bytesPerElement; Scoped newBuffer(scope, scope.engine->newArrayBuffer(l * elementSize)); if (scope.engine->hasException) { scope.result = Encode::undefined(); return; } Scoped array(scope, TypedArray::create(scope.engine, that->d()->type)); array->d()->buffer = 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->getIndexed(idx); array->d()->type->write(scope.engine, b, 0, val); if (scope.engine->hasException) { scope.result = Encode::undefined(); return; } ++idx; b += elementSize; } scope.result = array.asReturnedValue(); } void TypedArrayCtor::call(const Managed *that, Scope &scope, CallData *callData) { construct(that, scope, callData); } void Heap::TypedArray::init(Type t) { Object::init(); type = operations + t; arrayType = t; } Heap::TypedArray *TypedArray::create(ExecutionEngine *e, Heap::TypedArray::Type t) { QV4::InternalClass *ic = e->internalClasses[EngineBase::Class_Empty]->changeVTable(staticVTable()); ic = ic->changePrototype(e->typedArrayPrototype[t].d()); return e->memoryManager->allocObject(ic, e->typedArrayPrototype + t, t); } void TypedArray::markObjects(Heap::Base *that, ExecutionEngine *e) { static_cast(that)->buffer->mark(e); Object::markObjects(that, e); } ReturnedValue TypedArray::getIndexed(const Managed *m, uint index, bool *hasProperty) { Scope scope(static_cast(m)->engine()); Scoped a(scope, static_cast(m)); uint bytesPerElement = a->d()->type->bytesPerElement; uint byteOffset = a->d()->byteOffset + index * bytesPerElement; if (byteOffset + bytesPerElement > (uint)a->d()->buffer->byteLength()) { if (hasProperty) *hasProperty = false; return Encode::undefined(); } if (hasProperty) *hasProperty = true; return a->d()->type->read(a->d()->buffer->data->data(), byteOffset); } void TypedArray::putIndexed(Managed *m, uint index, const Value &value) { ExecutionEngine *v4 = static_cast(m)->engine(); if (v4->hasException) return; Scope scope(v4); Scoped a(scope, static_cast(m)); uint bytesPerElement = a->d()->type->bytesPerElement; uint byteOffset = a->d()->byteOffset + index * bytesPerElement; if (byteOffset + bytesPerElement > (uint)a->d()->buffer->byteLength()) goto reject; a->d()->type->write(scope.engine, a->d()->buffer->data->data(), byteOffset, value); return; reject: if (scope.engine->current->strictMode) scope.engine->throwTypeError(); } void TypedArrayPrototype::init(ExecutionEngine *engine, TypedArrayCtor *ctor) { Scope scope(engine); ScopedObject o(scope); ctor->defineReadonlyProperty(engine->id_length(), Primitive::fromInt32(3)); ctor->defineReadonlyProperty(engine->id_prototype(), (o = this)); ctor->defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Primitive::fromInt32(operations[ctor->d()->type].bytesPerElement)); defineDefaultProperty(engine->id_constructor(), (o = ctor)); defineAccessorProperty(QStringLiteral("buffer"), method_get_buffer, 0); defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, 0); defineAccessorProperty(QStringLiteral("byteOffset"), method_get_byteOffset, 0); defineAccessorProperty(QStringLiteral("length"), method_get_length, 0); defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Primitive::fromInt32(operations[ctor->d()->type].bytesPerElement)); defineDefaultProperty(QStringLiteral("set"), method_set, 1); defineDefaultProperty(QStringLiteral("subarray"), method_subarray, 0); } void TypedArrayPrototype::method_get_buffer(const BuiltinFunction *, Scope &scope, CallData *callData) { Scoped v(scope, callData->thisObject); if (!v) THROW_TYPE_ERROR(); scope.result = v->d()->buffer; } void TypedArrayPrototype::method_get_byteLength(const BuiltinFunction *, Scope &scope, CallData *callData) { Scoped v(scope, callData->thisObject); if (!v) THROW_TYPE_ERROR(); scope.result = Encode(v->d()->byteLength); } void TypedArrayPrototype::method_get_byteOffset(const BuiltinFunction *, Scope &scope, CallData *callData) { Scoped v(scope, callData->thisObject); if (!v) THROW_TYPE_ERROR(); scope.result = Encode(v->d()->byteOffset); } void TypedArrayPrototype::method_get_length(const BuiltinFunction *, Scope &scope, CallData *callData) { Scoped v(scope, callData->thisObject); if (!v) THROW_TYPE_ERROR(); scope.result = Encode(v->d()->byteLength/v->d()->type->bytesPerElement); } void TypedArrayPrototype::method_set(const BuiltinFunction *, Scope &scope, CallData *callData) { Scoped a(scope, callData->thisObject); if (!a) THROW_TYPE_ERROR(); Scoped buffer(scope, a->d()->buffer); if (!buffer) scope.engine->throwTypeError(); double doffset = callData->argc >= 2 ? callData->args[1].toInteger() : 0; if (scope.engine->hasException) RETURN_UNDEFINED(); 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 srcTypedArray(scope, callData->args[0]); if (!srcTypedArray) { // src is a regular object ScopedObject o(scope, callData->args[0].toObject(scope.engine)); if (scope.engine->hasException || !o) THROW_TYPE_ERROR(); double len = ScopedValue(scope, o->get(scope.engine->id_length()))->toNumber(); uint l = (uint)len; if (scope.engine->hasException || l != len) THROW_TYPE_ERROR(); if (offset + l > a->length()) RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range"))); uint idx = 0; char *b = buffer->d()->data->data() + a->d()->byteOffset + offset*elementSize; ScopedValue val(scope); while (idx < l) { val = o->getIndexed(idx); a->d()->type->write(scope.engine, b, 0, val); if (scope.engine->hasException) RETURN_UNDEFINED(); ++idx; b += elementSize; } RETURN_UNDEFINED(); } // src is a typed array Scoped srcBuffer(scope, srcTypedArray->d()->buffer); if (!srcBuffer) THROW_TYPE_ERROR(); uint l = srcTypedArray->length(); if (offset + l > a->length()) 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 = 0; 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; TypedArrayRead read = srcTypedArray->d()->type->read; TypedArrayWrite write = a->d()->type->write; for (uint i = 0; i < l; ++i) { Primitive val; val.setRawValue(read(src, i*srcElementSize)); write(scope.engine, dest, i*elementSize, val); } if (srcCopy) delete [] srcCopy; RETURN_UNDEFINED(); } void TypedArrayPrototype::method_subarray(const BuiltinFunction *, Scope &scope, CallData *callData) { Scoped a(scope, callData->thisObject); if (!a) THROW_TYPE_ERROR(); Scoped buffer(scope, a->d()->buffer); if (!buffer) THROW_TYPE_ERROR(); int len = a->length(); double b = callData->argc > 0 ? callData->args[0].toInteger() : 0; if (b < 0) b = len + b; uint begin = (uint)qBound(0., b, (double)len); double e = callData->argc < 2 || callData->args[1].isUndefined() ? len : callData->args[1].toInteger(); 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->get(scope.engine->id_constructor())); if (!constructor) THROW_TYPE_ERROR(); ScopedCallData cData(scope, 3); cData->args[0] = buffer; cData->args[1] = Encode(a->d()->byteOffset + begin*a->d()->type->bytesPerElement); cData->args[2] = Encode(newLen); constructor->construct(scope, cData); }