1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qv4typedarray_p.h" |
41 | #include "qv4arrayiterator_p.h" |
42 | #include "qv4arraybuffer_p.h" |
43 | #include "qv4string_p.h" |
44 | #include "qv4jscall_p.h" |
45 | #include "qv4symbol_p.h" |
46 | #include "qv4runtime_p.h" |
47 | #include <QtCore/qatomic.h> |
48 | |
49 | #include <cmath> |
50 | |
51 | using namespace QV4; |
52 | |
53 | DEFINE_OBJECT_VTABLE(IntrinsicTypedArrayCtor); |
54 | DEFINE_OBJECT_VTABLE(IntrinsicTypedArrayPrototype); |
55 | DEFINE_OBJECT_VTABLE(TypedArrayCtor); |
56 | DEFINE_OBJECT_VTABLE(TypedArrayPrototype); |
57 | DEFINE_OBJECT_VTABLE(TypedArray); |
58 | |
59 | Q_STATIC_ASSERT((int)ExecutionEngine::NTypedArrayTypes == (int)NTypedArrayTypes); |
60 | |
61 | static inline int toInt32(Value v) |
62 | { |
63 | Q_ASSERT(v.isNumber()); |
64 | if (v.isInteger()) |
65 | return v.integerValue(); |
66 | return Double::toInt32(d: v.doubleValue()); |
67 | } |
68 | |
69 | static inline double toDouble(Value v) |
70 | { |
71 | Q_ASSERT(v.isNumber()); |
72 | if (v.isInteger()) |
73 | return v.integerValue(); |
74 | return v.doubleValue(); |
75 | } |
76 | |
77 | struct ClampedUInt8 { |
78 | quint8 c; |
79 | }; |
80 | |
81 | template <typename T> |
82 | ReturnedValue typeToValue(T t) { |
83 | return Encode(t); |
84 | } |
85 | |
86 | template <> |
87 | ReturnedValue typeToValue(ClampedUInt8 t) { |
88 | return Encode(t.c); |
89 | } |
90 | |
91 | template <typename T> |
92 | T valueToType(Value value) |
93 | { |
94 | Q_ASSERT(value.isNumber()); |
95 | int n = toInt32(v: value); |
96 | return static_cast<T>(n); |
97 | } |
98 | |
99 | template <> |
100 | ClampedUInt8 valueToType(Value value) |
101 | { |
102 | Q_ASSERT(value.isNumber()); |
103 | if (value.isInteger()) |
104 | return { .c: static_cast<quint8>(qBound(min: 0, val: value.integerValue(), max: 255)) }; |
105 | Q_ASSERT(value.isDouble()); |
106 | double d = value.doubleValue(); |
107 | // ### is there a way to optimise this? |
108 | if (d <= 0 || std::isnan(x: d)) |
109 | return { .c: 0 }; |
110 | if (d >= 255) |
111 | return { .c: 255 }; |
112 | double f = std::floor(x: d); |
113 | if (f + 0.5 < d) |
114 | return { .c: (quint8)(f + 1) }; |
115 | if (d < f + 0.5) |
116 | return { .c: (quint8)(f) }; |
117 | if (int(f) % 2) |
118 | // odd number |
119 | return { .c: (quint8)(f + 1) }; |
120 | return { .c: (quint8)(f) }; |
121 | } |
122 | |
123 | template <> |
124 | float valueToType(Value value) |
125 | { |
126 | Q_ASSERT(value.isNumber()); |
127 | double d = toDouble(v: value); |
128 | return static_cast<float>(d); |
129 | } |
130 | |
131 | template <> |
132 | double valueToType(Value value) |
133 | { |
134 | Q_ASSERT(value.isNumber()); |
135 | return toDouble(v: value); |
136 | } |
137 | |
138 | template <typename T> |
139 | ReturnedValue read(const char *data) { |
140 | return typeToValue(*reinterpret_cast<const T *>(data)); |
141 | } |
142 | template <typename T> |
143 | void write(char *data, Value value) |
144 | { |
145 | *reinterpret_cast<T *>(data) = valueToType<T>(value); |
146 | } |
147 | |
148 | template <typename T> |
149 | ReturnedValue atomicAdd(char *data, Value v) |
150 | { |
151 | T value = valueToType<T>(v); |
152 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
153 | value = QAtomicOps<T>::fetchAndAddOrdered(*mem, value); |
154 | return typeToValue(value); |
155 | } |
156 | |
157 | template <typename T> |
158 | ReturnedValue atomicAnd(char *data, Value v) |
159 | { |
160 | T value = valueToType<T>(v); |
161 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
162 | value = QAtomicOps<T>::fetchAndAndOrdered(*mem, value); |
163 | return typeToValue(value); |
164 | } |
165 | |
166 | template <typename T> |
167 | ReturnedValue atomicExchange(char *data, Value v) |
168 | { |
169 | T value = valueToType<T>(v); |
170 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
171 | value = QAtomicOps<T>::fetchAndStoreOrdered(*mem, value); |
172 | return typeToValue(value); |
173 | } |
174 | |
175 | template <typename T> |
176 | ReturnedValue atomicOr(char *data, Value v) |
177 | { |
178 | T value = valueToType<T>(v); |
179 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
180 | value = QAtomicOps<T>::fetchAndOrOrdered(*mem, value); |
181 | return typeToValue(value); |
182 | } |
183 | |
184 | template <typename T> |
185 | ReturnedValue atomicSub(char *data, Value v) |
186 | { |
187 | T value = valueToType<T>(v); |
188 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
189 | value = QAtomicOps<T>::fetchAndSubOrdered(*mem, value); |
190 | return typeToValue(value); |
191 | } |
192 | |
193 | template <typename T> |
194 | ReturnedValue atomicXor(char *data, Value v) |
195 | { |
196 | T value = valueToType<T>(v); |
197 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
198 | value = QAtomicOps<T>::fetchAndXorOrdered(*mem, value); |
199 | return typeToValue(value); |
200 | } |
201 | |
202 | template <typename T> |
203 | ReturnedValue atomicCompareExchange(char *data, Value expected, Value v) |
204 | { |
205 | T value = valueToType<T>(v); |
206 | T exp = valueToType<T>(expected); |
207 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
208 | T old; |
209 | QAtomicOps<T>::testAndSetOrdered(*mem, exp, value, &old); |
210 | return typeToValue(old); |
211 | } |
212 | |
213 | template <typename T> |
214 | ReturnedValue atomicLoad(char *data) |
215 | { |
216 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
217 | T val = QAtomicOps<T>::loadRelaxed(*mem); |
218 | return typeToValue(val); |
219 | } |
220 | |
221 | template <typename T> |
222 | ReturnedValue atomicStore(char *data, Value v) |
223 | { |
224 | T value = valueToType<T>(v); |
225 | typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data); |
226 | QAtomicOps<T>::storeRelaxed(*mem, value); |
227 | return typeToValue(value); |
228 | } |
229 | |
230 | |
231 | template<typename T> |
232 | constexpr TypedArrayOperations TypedArrayOperations::create(const char *name) |
233 | { |
234 | return { sizeof(T), |
235 | name, |
236 | ::read<T>, |
237 | ::write<T>, |
238 | { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }, |
239 | nullptr, |
240 | nullptr, |
241 | nullptr |
242 | }; |
243 | } |
244 | |
245 | template<typename T> |
246 | constexpr TypedArrayOperations TypedArrayOperations::createWithAtomics(const char *name) |
247 | { |
248 | return { sizeof(T), |
249 | name, |
250 | ::read<T>, |
251 | ::write<T>, |
252 | { ::atomicAdd<T>, ::atomicAnd<T>, ::atomicExchange<T>, ::atomicOr<T>, ::atomicSub<T>, ::atomicXor<T> }, |
253 | ::atomicCompareExchange<T>, |
254 | ::atomicLoad<T>, |
255 | ::atomicStore<T> |
256 | }; |
257 | } |
258 | |
259 | const TypedArrayOperations operations[NTypedArrayTypes] = { |
260 | #ifdef Q_ATOMIC_INT8_IS_SUPPORTED |
261 | TypedArrayOperations::createWithAtomics<qint8>(name: "Int8Array" ), |
262 | TypedArrayOperations::createWithAtomics<quint8>(name: "Uint8Array" ), |
263 | #else |
264 | TypedArrayOperations::create<qint8>("Int8Array" ), |
265 | TypedArrayOperations::create<quint8>("Uint8Array" ), |
266 | #endif |
267 | TypedArrayOperations::createWithAtomics<qint16>(name: "Int16Array" ), |
268 | TypedArrayOperations::createWithAtomics<quint16>(name: "Uint16Array" ), |
269 | TypedArrayOperations::createWithAtomics<qint32>(name: "Int32Array" ), |
270 | TypedArrayOperations::createWithAtomics<quint32>(name: "Uint32Array" ), |
271 | TypedArrayOperations::create<ClampedUInt8>(name: "Uint8ClampedArray" ), |
272 | TypedArrayOperations::create<float>(name: "Float32Array" ), |
273 | TypedArrayOperations::create<double>(name: "Float64Array" ) |
274 | }; |
275 | |
276 | |
277 | void Heap::TypedArrayCtor::init(QV4::ExecutionContext *scope, TypedArray::Type t) |
278 | { |
279 | Heap::FunctionObject::init(scope, name: QLatin1String(operations[t].name)); |
280 | type = t; |
281 | } |
282 | |
283 | ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
284 | { |
285 | Scope scope(f->engine()); |
286 | const TypedArrayCtor *that = static_cast<const TypedArrayCtor *>(f); |
287 | |
288 | auto updateProto = [=](Scope &scope, Scoped<TypedArray> &a) { |
289 | if (newTarget->heapObject() != f->heapObject() && newTarget->isFunctionObject()) { |
290 | const FunctionObject *nt = static_cast<const FunctionObject *>(newTarget); |
291 | ScopedObject o(scope, nt->protoProperty()); |
292 | if (o) |
293 | a->setPrototypeOf(o); |
294 | } |
295 | }; |
296 | |
297 | if (!argc || !argv[0].isObject()) { |
298 | // ECMA 6 22.2.1.1 |
299 | qint64 l = argc ? argv[0].toIndex() : 0; |
300 | if (scope.engine->hasException) |
301 | return Encode::undefined(); |
302 | // ### lift UINT_MAX restriction |
303 | if (l < 0 || l > UINT_MAX) |
304 | return scope.engine->throwRangeError(message: QLatin1String("Index out of range." )); |
305 | uint len = (uint)l; |
306 | if (l != len) |
307 | scope.engine->throwRangeError(QStringLiteral("Non integer length for typed array." )); |
308 | uint byteLength = len * operations[that->d()->type].bytesPerElement; |
309 | Scoped<ArrayBuffer> buffer(scope, scope.engine->newArrayBuffer(length: byteLength)); |
310 | if (scope.engine->hasException) |
311 | return Encode::undefined(); |
312 | |
313 | Scoped<TypedArray> array(scope, TypedArray::create(e: scope.engine, t: that->d()->type)); |
314 | array->d()->buffer.set(e: scope.engine, newVal: buffer->d()); |
315 | array->d()->byteLength = byteLength; |
316 | array->d()->byteOffset = 0; |
317 | |
318 | updateProto(scope, array); |
319 | return array.asReturnedValue(); |
320 | } |
321 | Scoped<TypedArray> typedArray(scope, argc ? argv[0] : Value::undefinedValue()); |
322 | if (!!typedArray) { |
323 | // ECMA 6 22.2.1.2 |
324 | Scoped<ArrayBuffer> buffer(scope, typedArray->d()->buffer); |
325 | if (!buffer || buffer->isDetachedBuffer()) |
326 | return scope.engine->throwTypeError(); |
327 | uint srcElementSize = typedArray->d()->type->bytesPerElement; |
328 | uint destElementSize = operations[that->d()->type].bytesPerElement; |
329 | uint byteLength = typedArray->d()->byteLength; |
330 | uint destByteLength = byteLength*destElementSize/srcElementSize; |
331 | |
332 | Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(length: destByteLength)); |
333 | if (scope.engine->hasException) |
334 | return Encode::undefined(); |
335 | |
336 | Scoped<TypedArray> array(scope, TypedArray::create(e: scope.engine, t: that->d()->type)); |
337 | array->d()->buffer.set(e: scope.engine, newVal: newBuffer->d()); |
338 | array->d()->byteLength = destByteLength; |
339 | array->d()->byteOffset = 0; |
340 | |
341 | const char *src = buffer->d()->data->data() + typedArray->d()->byteOffset; |
342 | char *dest = newBuffer->d()->data->data(); |
343 | |
344 | // check if src and new type have the same size. In that case we can simply memcpy the data |
345 | if (srcElementSize == destElementSize) { |
346 | memcpy(dest: dest, src: src, n: byteLength); |
347 | } else { |
348 | // not same size, we need to loop |
349 | uint l = typedArray->length(); |
350 | TypedArrayOperations::Read read = typedArray->d()->type->read; |
351 | TypedArrayOperations::Write write =array->d()->type->write; |
352 | for (uint i = 0; i < l; ++i) { |
353 | Value val; |
354 | val.setRawValue(read(src + i*srcElementSize)); |
355 | write(dest + i*destElementSize, val); |
356 | } |
357 | } |
358 | |
359 | updateProto(scope, array); |
360 | return array.asReturnedValue(); |
361 | } |
362 | Scoped<ArrayBuffer> buffer(scope, argc ? argv[0] : Value::undefinedValue()); |
363 | if (!!buffer) { |
364 | // ECMA 6 22.2.1.4 |
365 | |
366 | double dbyteOffset = argc > 1 ? argv[1].toInteger() : 0; |
367 | |
368 | if (buffer->isDetachedBuffer()) |
369 | return scope.engine->throwTypeError(); |
370 | |
371 | uint byteOffset = (uint)dbyteOffset; |
372 | uint elementSize = operations[that->d()->type].bytesPerElement; |
373 | if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->byteLength()) |
374 | return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset" )); |
375 | |
376 | uint byteLength; |
377 | if (argc < 3 || argv[2].isUndefined()) { |
378 | byteLength = buffer->byteLength() - byteOffset; |
379 | if (buffer->byteLength() < byteOffset || byteLength % elementSize) |
380 | return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length" )); |
381 | } else { |
382 | double l = qBound(min: 0., val: argv[2].toInteger(), max: (double)UINT_MAX); |
383 | if (scope.engine->hasException) |
384 | return Encode::undefined(); |
385 | if (buffer->isDetachedBuffer()) |
386 | return scope.engine->throwTypeError(); |
387 | l *= elementSize; |
388 | if (buffer->byteLength() - byteOffset < l) |
389 | return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length" )); |
390 | byteLength = (uint)l; |
391 | } |
392 | |
393 | Scoped<TypedArray> array(scope, TypedArray::create(e: scope.engine, t: that->d()->type)); |
394 | array->d()->buffer.set(e: scope.engine, newVal: buffer->d()); |
395 | array->d()->byteLength = byteLength; |
396 | array->d()->byteOffset = byteOffset; |
397 | |
398 | updateProto(scope, array); |
399 | return array.asReturnedValue(); |
400 | } |
401 | |
402 | // ECMA 6 22.2.1.3 |
403 | |
404 | ScopedObject o(scope, argc ? argv[0] : Value::undefinedValue()); |
405 | uint l = (uint) qBound(min: 0., val: ScopedValue(scope, o->get(name: scope.engine->id_length()))->toInteger(), max: (double)UINT_MAX); |
406 | if (scope.engine->hasException) |
407 | return scope.engine->throwTypeError(); |
408 | |
409 | uint elementSize = operations[that->d()->type].bytesPerElement; |
410 | size_t bufferSize; |
411 | if (mul_overflow(v1: size_t(l), v2: size_t(elementSize), r: &bufferSize)) |
412 | return scope.engine->throwRangeError(message: QLatin1String("new TypedArray: invalid length" )); |
413 | Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(length: bufferSize)); |
414 | if (scope.engine->hasException) |
415 | return Encode::undefined(); |
416 | |
417 | Scoped<TypedArray> array(scope, TypedArray::create(e: scope.engine, t: that->d()->type)); |
418 | array->d()->buffer.set(e: scope.engine, newVal: newBuffer->d()); |
419 | array->d()->byteLength = l * elementSize; |
420 | array->d()->byteOffset = 0; |
421 | |
422 | uint idx = 0; |
423 | char *b = newBuffer->d()->data->data(); |
424 | ScopedValue val(scope); |
425 | while (idx < l) { |
426 | val = o->get(idx); |
427 | val = val->convertedToNumber(); |
428 | if (scope.engine->hasException) |
429 | return Encode::undefined(); |
430 | array->d()->type->write(b, val); |
431 | if (scope.engine->hasException) |
432 | return Encode::undefined(); |
433 | ++idx; |
434 | b += elementSize; |
435 | } |
436 | |
437 | updateProto(scope, array); |
438 | return array.asReturnedValue(); |
439 | } |
440 | |
441 | ReturnedValue TypedArrayCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) |
442 | { |
443 | return f->engine()->throwTypeError(QStringLiteral("calling a TypedArray constructor without new is invalid" )); |
444 | } |
445 | |
446 | void Heap::TypedArray::init(Type t) |
447 | { |
448 | Object::init(); |
449 | type = operations + static_cast<int>(t); |
450 | arrayType = static_cast<int>(t); |
451 | } |
452 | |
453 | Heap::TypedArray *TypedArray::create(ExecutionEngine *e, Heap::TypedArray::Type t) |
454 | { |
455 | Scope scope(e); |
456 | Scoped<InternalClass> ic(scope, e->newInternalClass(vtable: staticVTable(), prototype: e->typedArrayPrototype + static_cast<int>(t))); |
457 | return e->memoryManager->allocObject<TypedArray>(ic: ic->d(), args: t); |
458 | } |
459 | |
460 | ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) |
461 | { |
462 | const bool isArrayIndex = id.isArrayIndex(); |
463 | if (!isArrayIndex && !id.isCanonicalNumericIndexString()) |
464 | return Object::virtualGet(m, id, receiver, hasProperty); |
465 | |
466 | Scope scope(static_cast<const Object *>(m)->engine()); |
467 | Scoped<TypedArray> a(scope, static_cast<const TypedArray *>(m)); |
468 | if (a->d()->buffer->isDetachedBuffer()) |
469 | return scope.engine->throwTypeError(); |
470 | |
471 | if (!isArrayIndex || id.asArrayIndex() >= a->length()) { |
472 | if (hasProperty) |
473 | *hasProperty = false; |
474 | return Encode::undefined(); |
475 | } |
476 | |
477 | uint bytesPerElement = a->d()->type->bytesPerElement; |
478 | uint byteOffset = a->d()->byteOffset + id.asArrayIndex() * bytesPerElement; |
479 | Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); |
480 | |
481 | if (hasProperty) |
482 | *hasProperty = true; |
483 | return a->d()->type->read(a->d()->buffer->data->data() + byteOffset); |
484 | } |
485 | |
486 | bool TypedArray::virtualHasProperty(const Managed *m, PropertyKey id) |
487 | { |
488 | const bool isArrayIndex = id.isArrayIndex(); |
489 | if (!isArrayIndex && !id.isCanonicalNumericIndexString()) |
490 | return Object::virtualHasProperty(m, id); |
491 | |
492 | const TypedArray *a = static_cast<const TypedArray *>(m); |
493 | if (a->d()->buffer->isDetachedBuffer()) { |
494 | a->engine()->throwTypeError(); |
495 | return false; |
496 | } |
497 | return isArrayIndex && id.asArrayIndex() < a->length(); |
498 | } |
499 | |
500 | PropertyAttributes TypedArray::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) |
501 | { |
502 | if (!id.isArrayIndex() && !id.isCanonicalNumericIndexString()) |
503 | return Object::virtualGetOwnProperty(m, id, p); |
504 | |
505 | bool hasProperty = false; |
506 | ReturnedValue v = virtualGet(m, id, receiver: m, hasProperty: &hasProperty); |
507 | if (p) |
508 | p->value = v; |
509 | return hasProperty ? Attr_NotConfigurable : PropertyAttributes(); |
510 | } |
511 | |
512 | bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) |
513 | { |
514 | const bool isArrayIndex = id.isArrayIndex(); |
515 | if (!isArrayIndex && !id.isCanonicalNumericIndexString()) |
516 | return Object::virtualPut(m, id, value, receiver); |
517 | |
518 | ExecutionEngine *v4 = static_cast<Object *>(m)->engine(); |
519 | if (v4->hasException) |
520 | return false; |
521 | |
522 | Scope scope(v4); |
523 | Scoped<TypedArray> a(scope, static_cast<TypedArray *>(m)); |
524 | if (a->d()->buffer->isDetachedBuffer()) |
525 | return scope.engine->throwTypeError(); |
526 | |
527 | if (!isArrayIndex) |
528 | return false; |
529 | |
530 | const uint index = id.asArrayIndex(); |
531 | if (index >= a->length()) |
532 | return false; |
533 | |
534 | uint bytesPerElement = a->d()->type->bytesPerElement; |
535 | uint byteOffset = a->d()->byteOffset + index * bytesPerElement; |
536 | Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); |
537 | |
538 | Value v = Value::fromReturnedValue(val: value.convertedToNumber()); |
539 | if (scope.hasException() || a->d()->buffer->isDetachedBuffer()) |
540 | return scope.engine->throwTypeError(); |
541 | a->d()->type->write(a->d()->buffer->data->data() + byteOffset, v); |
542 | return true; |
543 | } |
544 | |
545 | bool TypedArray::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) |
546 | { |
547 | if (!id.isArrayIndex()) { |
548 | return !id.isCanonicalNumericIndexString() |
549 | && Object::virtualDefineOwnProperty(m, id, p, attrs); |
550 | } |
551 | |
552 | const uint index = id.asArrayIndex(); |
553 | TypedArray *a = static_cast<TypedArray *>(m); |
554 | if (index >= a->length() || attrs.isAccessor()) |
555 | return false; |
556 | |
557 | if (attrs.hasConfigurable() && attrs.isConfigurable()) |
558 | return false; |
559 | if (attrs.hasEnumerable() && !attrs.isEnumerable()) |
560 | return false; |
561 | if (attrs.hasWritable() && !attrs.isWritable()) |
562 | return false; |
563 | if (!p->value.isEmpty()) { |
564 | ExecutionEngine *engine = a->engine(); |
565 | |
566 | Value v = Value::fromReturnedValue(val: p->value.convertedToNumber()); |
567 | if (engine->hasException || a->d()->buffer->isDetachedBuffer()) |
568 | return engine->throwTypeError(); |
569 | uint bytesPerElement = a->d()->type->bytesPerElement; |
570 | uint byteOffset = a->d()->byteOffset + index * bytesPerElement; |
571 | Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); |
572 | a->d()->type->write(a->d()->buffer->data->data() + byteOffset, v); |
573 | } |
574 | return true; |
575 | } |
576 | |
577 | struct TypedArrayOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator |
578 | { |
579 | ~TypedArrayOwnPropertyKeyIterator() override = default; |
580 | PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; |
581 | |
582 | }; |
583 | |
584 | PropertyKey TypedArrayOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) |
585 | { |
586 | const TypedArray *a = static_cast<const TypedArray *>(o); |
587 | if (arrayIndex < a->length()) { |
588 | if (attrs) |
589 | *attrs = Attr_NotConfigurable; |
590 | PropertyKey id = PropertyKey::fromArrayIndex(idx: arrayIndex); |
591 | if (pd) { |
592 | bool hasProperty = false; |
593 | pd->value = TypedArray::virtualGet(m: a, id, receiver: a, hasProperty: &hasProperty); |
594 | } |
595 | ++arrayIndex; |
596 | return id; |
597 | } |
598 | |
599 | arrayIndex = UINT_MAX; |
600 | return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); |
601 | } |
602 | |
603 | OwnPropertyKeyIterator *TypedArray::virtualOwnPropertyKeys(const Object *m, Value *target) |
604 | { |
605 | *target = *m; |
606 | return new TypedArrayOwnPropertyKeyIterator(); |
607 | } |
608 | |
609 | void TypedArrayPrototype::init(ExecutionEngine *engine, TypedArrayCtor *ctor) |
610 | { |
611 | Scope scope(engine); |
612 | ScopedObject o(scope); |
613 | |
614 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 3)); |
615 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: *this); |
616 | ctor->defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT" ), value: Value::fromInt32(i: operations[static_cast<int>(ctor->d()->type)].bytesPerElement)); |
617 | ctor->setPrototypeOf(engine->intrinsicTypedArrayCtor()); |
618 | |
619 | setPrototypeOf(engine->intrinsicTypedArrayPrototype()); |
620 | defineDefaultProperty(name: engine->id_constructor(), value: (o = ctor)); |
621 | defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT" ), value: Value::fromInt32(i: operations[static_cast<int>(ctor->d()->type)].bytesPerElement)); |
622 | } |
623 | |
624 | ReturnedValue IntrinsicTypedArrayPrototype::method_get_buffer(const FunctionObject *b, const Value *thisObject, const Value *, int) |
625 | { |
626 | ExecutionEngine *v4 = b->engine(); |
627 | const TypedArray *v = thisObject->as<TypedArray>(); |
628 | if (!v) |
629 | return v4->throwTypeError(); |
630 | |
631 | return v->d()->buffer->asReturnedValue(); |
632 | } |
633 | |
634 | ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteLength(const FunctionObject *b, const Value *thisObject, const Value *, int) |
635 | { |
636 | ExecutionEngine *v4 = b->engine(); |
637 | const TypedArray *v = thisObject->as<TypedArray>(); |
638 | if (!v) |
639 | return v4->throwTypeError(); |
640 | |
641 | if (v->d()->buffer->isDetachedBuffer()) |
642 | return Encode(0); |
643 | |
644 | return Encode(v->d()->byteLength); |
645 | } |
646 | |
647 | ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteOffset(const FunctionObject *b, const Value *thisObject, const Value *, int) |
648 | { |
649 | ExecutionEngine *v4 = b->engine(); |
650 | const TypedArray *v = thisObject->as<TypedArray>(); |
651 | if (!v) |
652 | return v4->throwTypeError(); |
653 | |
654 | if (v->d()->buffer->isDetachedBuffer()) |
655 | return Encode(0); |
656 | |
657 | return Encode(v->d()->byteOffset); |
658 | } |
659 | |
660 | ReturnedValue IntrinsicTypedArrayPrototype::method_get_length(const FunctionObject *b, const Value *thisObject, const Value *, int) |
661 | { |
662 | ExecutionEngine *v4 = b->engine(); |
663 | const TypedArray *v = thisObject->as<TypedArray>(); |
664 | if (!v) |
665 | return v4->throwTypeError(); |
666 | |
667 | if (v->d()->buffer->isDetachedBuffer()) |
668 | return Encode(0); |
669 | |
670 | return Encode(v->d()->byteLength/v->d()->type->bytesPerElement); |
671 | } |
672 | |
673 | ReturnedValue IntrinsicTypedArrayPrototype::method_copyWithin(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
674 | { |
675 | Scope scope(f); |
676 | Scoped<TypedArray> O(scope, thisObject); |
677 | if (!O || O->d()->buffer->isDetachedBuffer()) |
678 | return scope.engine->throwTypeError(); |
679 | |
680 | if (!argc) |
681 | return O->asReturnedValue(); |
682 | |
683 | qint64 len = static_cast<uint>(O->length()); |
684 | |
685 | qint64 to = static_cast<qint64>(argv[0].toInteger()); |
686 | if (to < 0) |
687 | to = qMax(a: len + to, b: 0ll); |
688 | else |
689 | to = qMin(a: to, b: len); |
690 | |
691 | qint64 from = (argc > 1) ? static_cast<qint64>(argv[1].toInteger()) : 0ll; |
692 | if (from < 0) |
693 | from = qMax(a: len + from, b: 0ll); |
694 | else |
695 | from = qMin(a: from, b: len); |
696 | |
697 | double fend = argv[2].toInteger(); |
698 | if (fend > len) |
699 | fend = len; |
700 | qint64 end = (argc > 2 && !argv[2].isUndefined()) ? static_cast<qint64>(fend) : len; |
701 | if (end < 0) |
702 | end = qMax(a: len + end, b: 0ll); |
703 | else |
704 | end = qMin(a: end, b: len); |
705 | |
706 | qint64 count = qMin(a: end - from, b: len - to); |
707 | |
708 | if (count <= 0) |
709 | return O->asReturnedValue(); |
710 | |
711 | if (O->d()->buffer->isDetachedBuffer()) |
712 | return scope.engine->throwTypeError(); |
713 | |
714 | if (from != to) { |
715 | int elementSize = O->d()->type->bytesPerElement; |
716 | char *data = O->d()->buffer->data->data() + O->d()->byteOffset; |
717 | memmove(dest: data + to*elementSize, src: data + from*elementSize, n: count*elementSize); |
718 | } |
719 | |
720 | return O->asReturnedValue(); |
721 | } |
722 | |
723 | ReturnedValue IntrinsicTypedArrayPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) |
724 | { |
725 | Scope scope(b); |
726 | Scoped<TypedArray> v(scope, thisObject); |
727 | if (!v || v->d()->buffer->isDetachedBuffer()) |
728 | return scope.engine->throwTypeError(); |
729 | |
730 | Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(o: v)); |
731 | ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; |
732 | return ao->asReturnedValue(); |
733 | } |
734 | |
735 | ReturnedValue IntrinsicTypedArrayPrototype::method_every(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
736 | { |
737 | Scope scope(b); |
738 | Scoped<TypedArray> v(scope, thisObject); |
739 | if (!v || v->d()->buffer->isDetachedBuffer()) |
740 | return scope.engine->throwTypeError(); |
741 | |
742 | uint len = v->length(); |
743 | |
744 | if (!argc || !argv->isFunctionObject()) |
745 | THROW_TYPE_ERROR(); |
746 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
747 | |
748 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
749 | ScopedValue r(scope); |
750 | Value *arguments = scope.alloc(nValues: 3); |
751 | |
752 | const char *data = v->d()->buffer->data->data(); |
753 | uint bytesPerElement = v->d()->type->bytesPerElement; |
754 | uint byteOffset = v->d()->byteOffset; |
755 | |
756 | bool ok = true; |
757 | for (uint k = 0; ok && k < len; ++k) { |
758 | if (v->d()->buffer->isDetachedBuffer()) |
759 | return scope.engine->throwTypeError(); |
760 | |
761 | arguments[0] = v->d()->type->read(data + byteOffset + k * bytesPerElement); |
762 | |
763 | arguments[1] = Value::fromDouble(d: k); |
764 | arguments[2] = v; |
765 | r = callback->call(thisObject: that, argv: arguments, argc: 3); |
766 | CHECK_EXCEPTION(); |
767 | ok = r->toBoolean(); |
768 | } |
769 | return Encode(ok); |
770 | } |
771 | |
772 | ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
773 | { |
774 | Scope scope(b); |
775 | Scoped<TypedArray> v(scope, thisObject); |
776 | if (!v || v->d()->buffer->isDetachedBuffer()) |
777 | return scope.engine->throwTypeError(); |
778 | |
779 | uint len = v->length(); |
780 | double dlen = len; |
781 | double relativeStart = argc > 1 ? argv[1].toInteger() : 0.; |
782 | double relativeEnd = len; |
783 | if (argc > 2 && !argv[2].isUndefined()) |
784 | relativeEnd = argv[2].toInteger(); |
785 | |
786 | uint k = 0; |
787 | uint fin = 0; |
788 | |
789 | if (relativeStart < 0) { |
790 | k = static_cast<uint>(std::max(a: len+relativeStart, b: 0.)); |
791 | } else { |
792 | k = static_cast<uint>(std::min(a: relativeStart, b: dlen)); |
793 | } |
794 | |
795 | if (relativeEnd < 0) { |
796 | fin = static_cast<uint>(std::max(a: len + relativeEnd, b: 0.)); |
797 | } else { |
798 | fin = static_cast<uint>(std::min(a: relativeEnd, b: dlen)); |
799 | } |
800 | |
801 | double val = argc ? argv[0].toNumber() : std::numeric_limits<double>::quiet_NaN(); |
802 | Value value = Value::fromDouble(d: val); |
803 | if (scope.hasException() || v->d()->buffer->isDetachedBuffer()) |
804 | return scope.engine->throwTypeError(); |
805 | |
806 | char *data = v->d()->buffer->data->data(); |
807 | uint bytesPerElement = v->d()->type->bytesPerElement; |
808 | uint byteOffset = v->d()->byteOffset; |
809 | |
810 | while (k < fin) { |
811 | v->d()->type->write(data + byteOffset + k * bytesPerElement, value); |
812 | k++; |
813 | } |
814 | |
815 | return v.asReturnedValue(); |
816 | } |
817 | |
818 | static TypedArray *typedArraySpeciesCreate(Scope &scope, const TypedArray *instance, uint len) |
819 | { |
820 | const FunctionObject *constructor = instance->speciesConstructor(scope, defaultConstructor: scope.engine->typedArrayCtors + instance->d()->arrayType); |
821 | if (!constructor) { |
822 | scope.engine->throwTypeError(); |
823 | return nullptr; |
824 | } |
825 | |
826 | Value *arguments = scope.alloc(nValues: 1); |
827 | arguments[0] = Encode(len); |
828 | Scoped<TypedArray> a(scope, constructor->callAsConstructor(argv: arguments, argc: 1)); |
829 | if (!a || a->d()->buffer->isDetachedBuffer() || a->length() < len) { |
830 | scope.engine->throwTypeError(); |
831 | return nullptr; |
832 | } |
833 | return a; |
834 | } |
835 | |
836 | ReturnedValue IntrinsicTypedArrayPrototype::method_filter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
837 | { |
838 | Scope scope(b); |
839 | Scoped<TypedArray> instance(scope, thisObject); |
840 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
841 | return scope.engine->throwTypeError(); |
842 | |
843 | uint len = instance->length(); |
844 | |
845 | if (!argc || !argv->isFunctionObject()) |
846 | THROW_TYPE_ERROR(); |
847 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
848 | |
849 | ScopedValue selected(scope); |
850 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
851 | Value *arguments = scope.alloc(nValues: 3); |
852 | Value *list = arguments; |
853 | |
854 | uint to = 0; |
855 | for (uint k = 0; k < len; ++k) { |
856 | if (instance->d()->buffer->isDetachedBuffer()) |
857 | return scope.engine->throwTypeError(); |
858 | bool exists; |
859 | arguments[0] = instance->get(idx: k, hasProperty: &exists); |
860 | if (!exists) |
861 | continue; |
862 | |
863 | arguments[1] = Value::fromDouble(d: k); |
864 | arguments[2] = instance; |
865 | selected = callback->call(thisObject: that, argv: arguments, argc: 3); |
866 | CHECK_EXCEPTION(); |
867 | if (selected->toBoolean()) { |
868 | ++arguments; |
869 | scope.alloc(nValues: 1); |
870 | ++to; |
871 | } |
872 | } |
873 | |
874 | TypedArray *a = typedArraySpeciesCreate(scope, instance, len: to); |
875 | if (!a) |
876 | return Encode::undefined(); |
877 | |
878 | for (uint i = 0; i < to; ++i) |
879 | a->put(idx: i, v: list[i]); |
880 | |
881 | return a->asReturnedValue(); |
882 | } |
883 | |
884 | ReturnedValue IntrinsicTypedArrayPrototype::method_find(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
885 | { |
886 | Scope scope(b); |
887 | Scoped<TypedArray> v(scope, thisObject); |
888 | if (!v || v->d()->buffer->isDetachedBuffer()) |
889 | return scope.engine->throwTypeError(); |
890 | |
891 | uint len = v->length(); |
892 | |
893 | if (!argc || !argv[0].isFunctionObject()) |
894 | THROW_TYPE_ERROR(); |
895 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
896 | |
897 | ScopedValue result(scope); |
898 | Value *arguments = scope.alloc(nValues: 3); |
899 | |
900 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
901 | |
902 | for (uint k = 0; k < len; ++k) { |
903 | if (v->d()->buffer->isDetachedBuffer()) |
904 | return scope.engine->throwTypeError(); |
905 | arguments[0] = v->get(idx: k); |
906 | CHECK_EXCEPTION(); |
907 | |
908 | arguments[1] = Value::fromDouble(d: k); |
909 | arguments[2] = v; |
910 | result = callback->call(thisObject: that, argv: arguments, argc: 3); |
911 | |
912 | CHECK_EXCEPTION(); |
913 | if (result->toBoolean()) |
914 | return arguments[0].asReturnedValue(); |
915 | } |
916 | |
917 | RETURN_UNDEFINED(); |
918 | } |
919 | |
920 | ReturnedValue IntrinsicTypedArrayPrototype::method_findIndex(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
921 | { |
922 | Scope scope(b); |
923 | Scoped<TypedArray> v(scope, thisObject); |
924 | if (!v || v->d()->buffer->isDetachedBuffer()) |
925 | return scope.engine->throwTypeError(); |
926 | |
927 | uint len = v->length(); |
928 | |
929 | if (!argc || !argv[0].isFunctionObject()) |
930 | THROW_TYPE_ERROR(); |
931 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
932 | |
933 | ScopedValue result(scope); |
934 | Value *arguments = scope.alloc(nValues: 3); |
935 | |
936 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
937 | |
938 | for (uint k = 0; k < len; ++k) { |
939 | if (v->d()->buffer->isDetachedBuffer()) |
940 | return scope.engine->throwTypeError(); |
941 | arguments[0] = v->get(idx: k); |
942 | CHECK_EXCEPTION(); |
943 | |
944 | arguments[1] = Value::fromDouble(d: k); |
945 | arguments[2] = v; |
946 | result = callback->call(thisObject: that, argv: arguments, argc: 3); |
947 | |
948 | CHECK_EXCEPTION(); |
949 | if (result->toBoolean()) |
950 | return Encode(k); |
951 | } |
952 | |
953 | return Encode(-1); |
954 | } |
955 | |
956 | ReturnedValue IntrinsicTypedArrayPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
957 | { |
958 | Scope scope(b); |
959 | Scoped<TypedArray> v(scope, thisObject); |
960 | if (!v || v->d()->buffer->isDetachedBuffer()) |
961 | return scope.engine->throwTypeError(); |
962 | |
963 | uint len = v->length(); |
964 | |
965 | if (!argc || !argv->isFunctionObject()) |
966 | THROW_TYPE_ERROR(); |
967 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
968 | |
969 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
970 | Value *arguments = scope.alloc(nValues: 3); |
971 | |
972 | for (uint k = 0; k < len; ++k) { |
973 | if (v->d()->buffer->isDetachedBuffer()) |
974 | return scope.engine->throwTypeError(); |
975 | bool exists; |
976 | arguments[0] = v->get(idx: k, hasProperty: &exists); |
977 | if (!exists) |
978 | continue; |
979 | |
980 | arguments[1] = Value::fromDouble(d: k); |
981 | arguments[2] = v; |
982 | callback->call(thisObject: that, argv: arguments, argc: 3); |
983 | } |
984 | RETURN_UNDEFINED(); |
985 | } |
986 | |
987 | |
988 | ReturnedValue IntrinsicTypedArrayPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
989 | { |
990 | Scope scope(b); |
991 | Scoped<TypedArray> v(scope, thisObject); |
992 | if (!v || v->d()->buffer->isDetachedBuffer()) |
993 | return scope.engine->throwTypeError(); |
994 | |
995 | uint len = v->length(); |
996 | if (len == 0) { |
997 | return Encode(false); |
998 | } |
999 | |
1000 | double n = 0; |
1001 | if (argc > 1 && !argv[1].isUndefined()) { |
1002 | n = argv[1].toInteger(); |
1003 | } |
1004 | |
1005 | double k = 0; |
1006 | if (n >= 0) { |
1007 | k = n; |
1008 | } else { |
1009 | k = len + n; |
1010 | if (k < 0) { |
1011 | k = 0; |
1012 | } |
1013 | } |
1014 | |
1015 | while (k < len) { |
1016 | ScopedValue val(scope, v->get(idx: k)); |
1017 | if (val->sameValueZero(other: argv[0])) { |
1018 | return Encode(true); |
1019 | } |
1020 | k++; |
1021 | } |
1022 | |
1023 | return Encode(false); |
1024 | } |
1025 | |
1026 | ReturnedValue IntrinsicTypedArrayPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1027 | { |
1028 | Scope scope(b); |
1029 | Scoped<TypedArray> v(scope, thisObject); |
1030 | if (!v || v->d()->buffer->isDetachedBuffer()) |
1031 | return scope.engine->throwTypeError(); |
1032 | |
1033 | uint len = v->length(); |
1034 | if (!len) |
1035 | return Encode(-1); |
1036 | |
1037 | ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue()); |
1038 | uint fromIndex = 0; |
1039 | |
1040 | if (argc >= 2) { |
1041 | double f = argv[1].toInteger(); |
1042 | CHECK_EXCEPTION(); |
1043 | if (f >= len) |
1044 | return Encode(-1); |
1045 | if (f < 0) |
1046 | f = qMax(a: len + f, b: 0.); |
1047 | fromIndex = (uint) f; |
1048 | } |
1049 | |
1050 | if (v->isStringObject()) { |
1051 | ScopedValue value(scope); |
1052 | for (uint k = fromIndex; k < len; ++k) { |
1053 | bool exists; |
1054 | value = v->get(idx: k, hasProperty: &exists); |
1055 | if (exists && RuntimeHelpers::strictEqual(x: value, y: searchValue)) |
1056 | return Encode(k); |
1057 | } |
1058 | return Encode(-1); |
1059 | } |
1060 | |
1061 | ScopedValue value(scope); |
1062 | |
1063 | for (uint i = fromIndex; i < len; ++i) { |
1064 | bool exists; |
1065 | value = v->get(idx: i, hasProperty: &exists); |
1066 | CHECK_EXCEPTION(); |
1067 | if (exists && RuntimeHelpers::strictEqual(x: value, y: searchValue)) |
1068 | return Encode(i); |
1069 | } |
1070 | return Encode(-1); |
1071 | } |
1072 | |
1073 | ReturnedValue IntrinsicTypedArrayPrototype::method_join( |
1074 | const FunctionObject *functionObject, const Value *thisObject, const Value *argv, int argc) |
1075 | { |
1076 | Scope scope(functionObject); |
1077 | Scoped<TypedArray> typedArray(scope, thisObject); |
1078 | if (!typedArray || typedArray->d()->buffer->isDetachedBuffer()) |
1079 | return scope.engine->throwTypeError(); |
1080 | |
1081 | // We cannot optimize the resolution of the argument away if length is 0. |
1082 | // It may have side effects. |
1083 | ScopedValue argument(scope, argc ? argv[0] : Value::undefinedValue()); |
1084 | const QString separator = argument->isUndefined() |
1085 | ? QStringLiteral("," ) |
1086 | : argument->toQString(); |
1087 | |
1088 | const quint32 length = typedArray->length(); |
1089 | if (!length) |
1090 | return Encode(scope.engine->newString()); |
1091 | |
1092 | QString result; |
1093 | |
1094 | ScopedString name(scope, scope.engine->newString(QStringLiteral("0" ))); |
1095 | ScopedValue value(scope, typedArray->get(name)); |
1096 | if (!value->isNullOrUndefined()) |
1097 | result = value->toQString(); |
1098 | |
1099 | for (quint32 i = 1; i < length; ++i) { |
1100 | result += separator; |
1101 | |
1102 | name = Value::fromDouble(d: i).toString(e: scope.engine); |
1103 | value = typedArray->get(name); |
1104 | CHECK_EXCEPTION(); |
1105 | |
1106 | if (!value->isNullOrUndefined()) |
1107 | result += value->toQString(); |
1108 | } |
1109 | |
1110 | return Encode(scope.engine->newString(s: result)); |
1111 | } |
1112 | |
1113 | ReturnedValue IntrinsicTypedArrayPrototype::method_keys(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1114 | { |
1115 | Scope scope(b); |
1116 | Scoped<TypedArray> v(scope, thisObject); |
1117 | if (!v || v->d()->buffer->isDetachedBuffer()) |
1118 | return scope.engine->throwTypeError(); |
1119 | |
1120 | Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(o: v)); |
1121 | ao->d()->iterationKind = IteratorKind::KeyIteratorKind; |
1122 | return ao->asReturnedValue(); |
1123 | } |
1124 | |
1125 | |
1126 | ReturnedValue IntrinsicTypedArrayPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1127 | { |
1128 | Scope scope(b); |
1129 | Scoped<TypedArray> instance(scope, thisObject); |
1130 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
1131 | return scope.engine->throwTypeError(); |
1132 | |
1133 | uint len = instance->length(); |
1134 | if (!len) |
1135 | return Encode(-1); |
1136 | |
1137 | ScopedValue searchValue(scope); |
1138 | uint fromIndex = len; |
1139 | |
1140 | if (argc >= 1) |
1141 | searchValue = argv[0]; |
1142 | else |
1143 | searchValue = Value::undefinedValue(); |
1144 | |
1145 | if (argc >= 2) { |
1146 | double f = argv[1].toInteger(); |
1147 | CHECK_EXCEPTION(); |
1148 | if (f > 0) |
1149 | f = qMin(a: f, b: (double)(len - 1)); |
1150 | else if (f < 0) { |
1151 | f = len + f; |
1152 | if (f < 0) |
1153 | return Encode(-1); |
1154 | } |
1155 | fromIndex = (uint) f + 1; |
1156 | } |
1157 | |
1158 | ScopedValue value(scope); |
1159 | for (uint k = fromIndex; k > 0;) { |
1160 | --k; |
1161 | bool exists; |
1162 | value = instance->get(idx: k, hasProperty: &exists); |
1163 | if (exists && RuntimeHelpers::strictEqual(x: value, y: searchValue)) |
1164 | return Encode(k); |
1165 | } |
1166 | return Encode(-1); |
1167 | } |
1168 | |
1169 | ReturnedValue IntrinsicTypedArrayPrototype::method_map(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1170 | { |
1171 | Scope scope(b); |
1172 | Scoped<TypedArray> instance(scope, thisObject); |
1173 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
1174 | return scope.engine->throwTypeError(); |
1175 | |
1176 | uint len = instance->length(); |
1177 | |
1178 | if (!argc || !argv->isFunctionObject()) |
1179 | THROW_TYPE_ERROR(); |
1180 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1181 | |
1182 | TypedArray *a = typedArraySpeciesCreate(scope, instance, len); |
1183 | if (!a) |
1184 | return Encode::undefined(); |
1185 | |
1186 | ScopedValue v(scope); |
1187 | ScopedValue mapped(scope); |
1188 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
1189 | Value *arguments = scope.alloc(nValues: 3); |
1190 | |
1191 | for (uint k = 0; k < len; ++k) { |
1192 | if (instance->d()->buffer->isDetachedBuffer()) |
1193 | return scope.engine->throwTypeError(); |
1194 | arguments[0] = instance->get(idx: k); |
1195 | |
1196 | arguments[1] = Value::fromDouble(d: k); |
1197 | arguments[2] = instance; |
1198 | mapped = callback->call(thisObject: that, argv: arguments, argc: 3); |
1199 | CHECK_EXCEPTION(); |
1200 | a->put(idx: k, v: mapped); |
1201 | } |
1202 | return a->asReturnedValue(); |
1203 | } |
1204 | |
1205 | ReturnedValue IntrinsicTypedArrayPrototype::method_reduce(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1206 | { |
1207 | Scope scope(b); |
1208 | Scoped<TypedArray> instance(scope, thisObject); |
1209 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
1210 | return scope.engine->throwTypeError(); |
1211 | |
1212 | uint len = instance->length(); |
1213 | |
1214 | if (!argc || !argv->isFunctionObject()) |
1215 | THROW_TYPE_ERROR(); |
1216 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1217 | |
1218 | uint k = 0; |
1219 | ScopedValue acc(scope); |
1220 | ScopedValue v(scope); |
1221 | |
1222 | if (argc > 1) { |
1223 | acc = argv[1]; |
1224 | } else { |
1225 | bool kPresent = false; |
1226 | while (k < len && !kPresent) { |
1227 | v = instance->get(idx: k, hasProperty: &kPresent); |
1228 | if (kPresent) |
1229 | acc = v; |
1230 | ++k; |
1231 | } |
1232 | if (!kPresent) |
1233 | THROW_TYPE_ERROR(); |
1234 | } |
1235 | |
1236 | Value *arguments = scope.alloc(nValues: 4); |
1237 | |
1238 | while (k < len) { |
1239 | if (instance->d()->buffer->isDetachedBuffer()) |
1240 | return scope.engine->throwTypeError(); |
1241 | bool kPresent; |
1242 | v = instance->get(idx: k, hasProperty: &kPresent); |
1243 | if (kPresent) { |
1244 | arguments[0] = acc; |
1245 | arguments[1] = v; |
1246 | arguments[2] = Value::fromDouble(d: k); |
1247 | arguments[3] = instance; |
1248 | acc = callback->call(thisObject: nullptr, argv: arguments, argc: 4); |
1249 | CHECK_EXCEPTION(); |
1250 | } |
1251 | ++k; |
1252 | } |
1253 | return acc->asReturnedValue(); |
1254 | } |
1255 | |
1256 | ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1257 | { |
1258 | Scope scope(b); |
1259 | Scoped<TypedArray> instance(scope, thisObject); |
1260 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
1261 | return scope.engine->throwTypeError(); |
1262 | |
1263 | uint len = instance->length(); |
1264 | |
1265 | if (!argc || !argv->isFunctionObject()) |
1266 | THROW_TYPE_ERROR(); |
1267 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1268 | |
1269 | if (len == 0) { |
1270 | if (argc == 1) |
1271 | THROW_TYPE_ERROR(); |
1272 | return argv[1].asReturnedValue(); |
1273 | } |
1274 | |
1275 | uint k = len; |
1276 | ScopedValue acc(scope); |
1277 | ScopedValue v(scope); |
1278 | if (argc > 1) { |
1279 | acc = argv[1]; |
1280 | } else { |
1281 | bool kPresent = false; |
1282 | while (k > 0 && !kPresent) { |
1283 | v = instance->get(idx: k - 1, hasProperty: &kPresent); |
1284 | if (kPresent) |
1285 | acc = v; |
1286 | --k; |
1287 | } |
1288 | if (!kPresent) |
1289 | THROW_TYPE_ERROR(); |
1290 | } |
1291 | |
1292 | Value *arguments = scope.alloc(nValues: 4); |
1293 | |
1294 | while (k > 0) { |
1295 | if (instance->d()->buffer->isDetachedBuffer()) |
1296 | return scope.engine->throwTypeError(); |
1297 | bool kPresent; |
1298 | v = instance->get(idx: k - 1, hasProperty: &kPresent); |
1299 | if (kPresent) { |
1300 | arguments[0] = acc; |
1301 | arguments[1] = v; |
1302 | arguments[2] = Value::fromDouble(d: k - 1); |
1303 | arguments[3] = instance; |
1304 | acc = callback->call(thisObject: nullptr, argv: arguments, argc: 4); |
1305 | CHECK_EXCEPTION(); |
1306 | } |
1307 | --k; |
1308 | } |
1309 | return acc->asReturnedValue(); |
1310 | } |
1311 | |
1312 | ReturnedValue IntrinsicTypedArrayPrototype::method_reverse(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1313 | { |
1314 | Scope scope(b); |
1315 | Scoped<TypedArray> instance(scope, thisObject); |
1316 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
1317 | return scope.engine->throwTypeError(); |
1318 | |
1319 | uint length = instance->length(); |
1320 | |
1321 | int lo = 0, hi = length - 1; |
1322 | |
1323 | ScopedValue lval(scope); |
1324 | ScopedValue hval(scope); |
1325 | for (; lo < hi; ++lo, --hi) { |
1326 | bool loExists, hiExists; |
1327 | lval = instance->get(idx: lo, hasProperty: &loExists); |
1328 | hval = instance->get(idx: hi, hasProperty: &hiExists); |
1329 | Q_ASSERT(hiExists && loExists); |
1330 | bool ok; |
1331 | ok = instance->put(idx: lo, v: hval); |
1332 | Q_ASSERT(ok); |
1333 | ok = instance->put(idx: hi, v: lval); |
1334 | Q_ASSERT(ok); |
1335 | } |
1336 | return instance->asReturnedValue(); |
1337 | } |
1338 | |
1339 | ReturnedValue IntrinsicTypedArrayPrototype::method_some(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1340 | { |
1341 | Scope scope(b); |
1342 | Scoped<TypedArray> instance(scope, thisObject); |
1343 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
1344 | return scope.engine->throwTypeError(); |
1345 | |
1346 | uint len = instance->length(); |
1347 | |
1348 | if (!argc || !argv->isFunctionObject()) |
1349 | THROW_TYPE_ERROR(); |
1350 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1351 | |
1352 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
1353 | ScopedValue result(scope); |
1354 | Value *arguments = scope.alloc(nValues: 3); |
1355 | |
1356 | for (uint k = 0; k < len; ++k) { |
1357 | if (instance->d()->buffer->isDetachedBuffer()) |
1358 | return scope.engine->throwTypeError(); |
1359 | bool exists; |
1360 | arguments[0] = instance->get(idx: k, hasProperty: &exists); |
1361 | if (!exists) |
1362 | continue; |
1363 | |
1364 | arguments[1] = Value::fromDouble(d: k); |
1365 | arguments[2] = instance; |
1366 | result = callback->call(thisObject: that, argv: arguments, argc: 3); |
1367 | CHECK_EXCEPTION(); |
1368 | if (result->toBoolean()) |
1369 | return Encode(true); |
1370 | } |
1371 | return Encode(false); |
1372 | } |
1373 | |
1374 | |
1375 | ReturnedValue IntrinsicTypedArrayPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1376 | { |
1377 | Scope scope(b); |
1378 | Scoped<TypedArray> v(scope, thisObject); |
1379 | if (!v || v->d()->buffer->isDetachedBuffer()) |
1380 | return scope.engine->throwTypeError(); |
1381 | |
1382 | Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(o: v)); |
1383 | ao->d()->iterationKind = IteratorKind::ValueIteratorKind; |
1384 | return ao->asReturnedValue(); |
1385 | } |
1386 | |
1387 | ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1388 | { |
1389 | Scope scope(b); |
1390 | Scoped<TypedArray> a(scope, *thisObject); |
1391 | if (!a) |
1392 | return scope.engine->throwTypeError(); |
1393 | Scoped<ArrayBuffer> buffer(scope, a->d()->buffer); |
1394 | |
1395 | double doffset = argc >= 2 ? argv[1].toInteger() : 0; |
1396 | if (scope.engine->hasException) |
1397 | RETURN_UNDEFINED(); |
1398 | if (!buffer || buffer->isDetachedBuffer()) |
1399 | return scope.engine->throwTypeError(); |
1400 | |
1401 | if (doffset < 0 || doffset >= UINT_MAX) |
1402 | RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range" ))); |
1403 | uint offset = (uint)doffset; |
1404 | uint elementSize = a->d()->type->bytesPerElement; |
1405 | |
1406 | Scoped<TypedArray> srcTypedArray(scope, argv[0]); |
1407 | if (!srcTypedArray) { |
1408 | // src is a regular object |
1409 | ScopedObject o(scope, argv[0].toObject(e: scope.engine)); |
1410 | if (scope.engine->hasException || !o) |
1411 | return scope.engine->throwTypeError(); |
1412 | |
1413 | double len = ScopedValue(scope, o->get(name: scope.engine->id_length()))->toNumber(); |
1414 | uint l = (uint)len; |
1415 | if (scope.engine->hasException || l != len) |
1416 | return scope.engine->throwTypeError(); |
1417 | |
1418 | const uint aLength = a->length(); |
1419 | if (offset > aLength || l > aLength - offset) |
1420 | RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range" ))); |
1421 | |
1422 | uint idx = 0; |
1423 | if (buffer->isDetachedBuffer()) |
1424 | return scope.engine->throwTypeError(); |
1425 | char *b = buffer->d()->data->data() + a->d()->byteOffset + offset*elementSize; |
1426 | ScopedValue val(scope); |
1427 | while (idx < l) { |
1428 | val = o->get(idx); |
1429 | if (scope.hasException()) |
1430 | return Encode::undefined(); |
1431 | val = val->convertedToNumber(); |
1432 | if (scope.hasException() || buffer->isDetachedBuffer()) |
1433 | return scope.engine->throwTypeError(); |
1434 | a->d()->type->write(b, val); |
1435 | if (scope.engine->hasException) |
1436 | RETURN_UNDEFINED(); |
1437 | ++idx; |
1438 | b += elementSize; |
1439 | } |
1440 | RETURN_UNDEFINED(); |
1441 | } |
1442 | |
1443 | // src is a typed array |
1444 | Scoped<ArrayBuffer> srcBuffer(scope, srcTypedArray->d()->buffer); |
1445 | if (!srcBuffer || srcBuffer->isDetachedBuffer()) |
1446 | return scope.engine->throwTypeError(); |
1447 | |
1448 | uint l = srcTypedArray->length(); |
1449 | |
1450 | const uint aLength = a->length(); |
1451 | if (offset > aLength || l > aLength - offset) |
1452 | RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range" ))); |
1453 | |
1454 | char *dest = buffer->d()->data->data() + a->d()->byteOffset + offset*elementSize; |
1455 | const char *src = srcBuffer->d()->data->data() + srcTypedArray->d()->byteOffset; |
1456 | if (srcTypedArray->d()->type == a->d()->type) { |
1457 | // same type of typed arrays, use memmove (as srcbuffer and buffer could be the same) |
1458 | memmove(dest: dest, src: src, n: srcTypedArray->d()->byteLength); |
1459 | RETURN_UNDEFINED(); |
1460 | } |
1461 | |
1462 | char *srcCopy = nullptr; |
1463 | if (buffer->d() == srcBuffer->d()) { |
1464 | // same buffer, need to take a temporary copy, to not run into problems |
1465 | srcCopy = new char[srcTypedArray->d()->byteLength]; |
1466 | memcpy(dest: srcCopy, src: src, n: srcTypedArray->d()->byteLength); |
1467 | src = srcCopy; |
1468 | } |
1469 | |
1470 | // typed arrays of different kind, need to manually loop |
1471 | uint srcElementSize = srcTypedArray->d()->type->bytesPerElement; |
1472 | TypedArrayOperations::Read read = srcTypedArray->d()->type->read; |
1473 | TypedArrayOperations::Write write = a->d()->type->write; |
1474 | for (uint i = 0; i < l; ++i) { |
1475 | Value val; |
1476 | val.setRawValue(read(src + i*srcElementSize)); |
1477 | write(dest + i*elementSize, val); |
1478 | } |
1479 | |
1480 | if (srcCopy) |
1481 | delete [] srcCopy; |
1482 | |
1483 | RETURN_UNDEFINED(); |
1484 | } |
1485 | |
1486 | ReturnedValue IntrinsicTypedArrayPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1487 | { |
1488 | Scope scope(b); |
1489 | Scoped<TypedArray> instance(scope, thisObject); |
1490 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
1491 | return scope.engine->throwTypeError(); |
1492 | |
1493 | uint len = instance->length(); |
1494 | |
1495 | double s = (argc ? argv[0] : Value::undefinedValue()).toInteger(); |
1496 | uint start; |
1497 | if (s < 0) |
1498 | start = (uint)qMax(a: len + s, b: 0.); |
1499 | else if (s > len) |
1500 | start = len; |
1501 | else |
1502 | start = (uint) s; |
1503 | uint end = len; |
1504 | if (argc > 1 && !argv[1].isUndefined()) { |
1505 | double e = argv[1].toInteger(); |
1506 | if (e < 0) |
1507 | end = (uint)qMax(a: len + e, b: 0.); |
1508 | else if (e > len) |
1509 | end = len; |
1510 | else |
1511 | end = (uint) e; |
1512 | } |
1513 | uint count = start > end ? 0 : end - start; |
1514 | |
1515 | TypedArray *a = typedArraySpeciesCreate(scope, instance, len: count); |
1516 | if (!a) |
1517 | return Encode::undefined(); |
1518 | |
1519 | ScopedValue v(scope); |
1520 | uint n = 0; |
1521 | for (uint i = start; i < end; ++i) { |
1522 | if (instance->d()->buffer->isDetachedBuffer()) |
1523 | return scope.engine->throwTypeError(); |
1524 | v = instance->get(idx: i); |
1525 | if (a->d()->buffer->isDetachedBuffer()) |
1526 | return scope.engine->throwTypeError(); |
1527 | a->put(idx: n, v); |
1528 | ++n; |
1529 | } |
1530 | return a->asReturnedValue(); |
1531 | } |
1532 | |
1533 | ReturnedValue IntrinsicTypedArrayPrototype::method_subarray(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) |
1534 | { |
1535 | Scope scope(builtin); |
1536 | Scoped<TypedArray> a(scope, *thisObject); |
1537 | |
1538 | if (!a) |
1539 | return scope.engine->throwTypeError(); |
1540 | |
1541 | Scoped<ArrayBuffer> buffer(scope, a->d()->buffer); |
1542 | Q_ASSERT(buffer); |
1543 | |
1544 | int len = a->length(); |
1545 | double b = argc > 0 ? argv[0].toInteger() : 0; |
1546 | if (b < 0) |
1547 | b = len + b; |
1548 | uint begin = (uint)qBound(min: 0., val: b, max: (double)len); |
1549 | |
1550 | double e = argc < 2 || argv[1].isUndefined() ? len : argv[1].toInteger(); |
1551 | if (e < 0) |
1552 | e = len + e; |
1553 | uint end = (uint)qBound(min: 0., val: e, max: (double)len); |
1554 | if (end < begin) |
1555 | end = begin; |
1556 | |
1557 | if (scope.engine->hasException) |
1558 | RETURN_UNDEFINED(); |
1559 | |
1560 | int newLen = end - begin; |
1561 | |
1562 | ScopedFunctionObject constructor(scope, a->speciesConstructor(scope, defaultConstructor: scope.engine->typedArrayCtors + a->d()->arrayType)); |
1563 | if (!constructor) |
1564 | return scope.engine->throwTypeError(); |
1565 | |
1566 | Value *arguments = scope.alloc(nValues: 3); |
1567 | arguments[0] = buffer; |
1568 | arguments[1] = Encode(a->d()->byteOffset + begin*a->d()->type->bytesPerElement); |
1569 | arguments[2] = Encode(newLen); |
1570 | a = constructor->callAsConstructor(argv: arguments, argc: 3); |
1571 | if (!a || a->d()->buffer->isDetachedBuffer()) |
1572 | return scope.engine->throwTypeError(); |
1573 | return a->asReturnedValue(); |
1574 | } |
1575 | |
1576 | ReturnedValue IntrinsicTypedArrayPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1577 | { |
1578 | Scope scope(b); |
1579 | Scoped<TypedArray> instance(scope, thisObject); |
1580 | if (!instance || instance->d()->buffer->isDetachedBuffer()) |
1581 | return scope.engine->throwTypeError(); |
1582 | |
1583 | uint len = instance->length(); |
1584 | const QString separator = QStringLiteral("," ); |
1585 | |
1586 | QString R; |
1587 | |
1588 | ScopedValue v(scope); |
1589 | ScopedString s(scope); |
1590 | |
1591 | for (uint k = 0; k < len; ++k) { |
1592 | if (instance->d()->buffer->isDetachedBuffer()) |
1593 | return scope.engine->throwTypeError(); |
1594 | if (k) |
1595 | R += separator; |
1596 | |
1597 | v = instance->get(idx: k); |
1598 | v = Runtime::CallElement::call(scope.engine, v, *scope.engine->id_toLocaleString(), nullptr, 0); |
1599 | s = v->toString(e: scope.engine); |
1600 | if (scope.hasException()) |
1601 | return Encode::undefined(); |
1602 | |
1603 | R += s->toQString(); |
1604 | } |
1605 | return scope.engine->newString(s: R)->asReturnedValue(); |
1606 | } |
1607 | |
1608 | ReturnedValue IntrinsicTypedArrayPrototype::method_get_toStringTag(const FunctionObject *, const Value *thisObject, const Value *, int) |
1609 | { |
1610 | const TypedArray *a = thisObject->as<TypedArray>(); |
1611 | if (!a) |
1612 | return Encode::undefined(); |
1613 | |
1614 | return a->engine()->newString(s: QString::fromLatin1(str: a->d()->type->name))->asReturnedValue(); |
1615 | } |
1616 | |
1617 | static bool validateTypedArray(const Object *o) |
1618 | { |
1619 | const TypedArray *a = o->as<TypedArray>(); |
1620 | if (!a) |
1621 | return false; |
1622 | if (a->d()->buffer->isDetachedBuffer()) |
1623 | return false; |
1624 | return true; |
1625 | } |
1626 | |
1627 | ReturnedValue IntrinsicTypedArrayCtor::method_of(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
1628 | { |
1629 | Scope scope(f); |
1630 | int len = argc; |
1631 | const Value *items = argv; |
1632 | const FunctionObject *C = thisObject->as<FunctionObject>(); |
1633 | if (!C || !C->isConstructor()) |
1634 | return scope.engine->throwTypeError(); |
1635 | |
1636 | Value lenValue = Value::fromInt32(i: len); |
1637 | ScopedObject newObj(scope, C->callAsConstructor(argv: &lenValue, argc: 1)); |
1638 | if (scope.hasException()) |
1639 | return Encode::undefined(); |
1640 | if (!::validateTypedArray(o: newObj)) |
1641 | return scope.engine->throwTypeError(); |
1642 | TypedArray *a = newObj->as<TypedArray>(); |
1643 | Q_ASSERT(a); |
1644 | if (a->length() < static_cast<uint>(len)) |
1645 | return scope.engine->throwTypeError(); |
1646 | |
1647 | for (int k = 0; k < len; ++k) { |
1648 | newObj->put(id: PropertyKey::fromArrayIndex(idx: k), v: items[k]); |
1649 | } |
1650 | return newObj->asReturnedValue(); |
1651 | } |
1652 | |
1653 | ReturnedValue IntrinsicTypedArrayCtor::method_from(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
1654 | { |
1655 | Scope scope(f); |
1656 | ScopedObject itemsObject(scope, argv[0]); |
1657 | bool usingIterator = false; |
1658 | |
1659 | ScopedFunctionObject mapfn(scope, Value::undefinedValue()); |
1660 | Value *mapArguments = nullptr; |
1661 | if (argc > 1) { |
1662 | mapfn = ScopedFunctionObject(scope, argv[1]); |
1663 | if (!mapfn) |
1664 | return scope.engine->throwTypeError(message: QString::fromLatin1(str: "%1 is not a function" ).arg(a: argv[1].toQStringNoThrow())); |
1665 | mapArguments = scope.alloc(nValues: 2); |
1666 | } |
1667 | |
1668 | // Iterator validity check goes after map function validity has been checked. |
1669 | if (itemsObject) { |
1670 | // If the object claims to support iterators, then let's try use them. |
1671 | ScopedValue it(scope, itemsObject->get(name: scope.engine->symbol_iterator())); |
1672 | CHECK_EXCEPTION(); |
1673 | if (!it->isNullOrUndefined()) { |
1674 | ScopedFunctionObject itfunc(scope, it); |
1675 | if (!itfunc) |
1676 | return scope.engine->throwTypeError(); |
1677 | usingIterator = true; |
1678 | } |
1679 | } |
1680 | |
1681 | ScopedValue thisArg(scope); |
1682 | if (argc > 2) |
1683 | thisArg = argv[2]; |
1684 | |
1685 | const FunctionObject *C = thisObject->as<FunctionObject>(); |
1686 | |
1687 | if (usingIterator) { |
1688 | // Item iteration supported, so let's go ahead and try use that. |
1689 | CHECK_EXCEPTION(); |
1690 | |
1691 | qint64 iterableLength = 0; |
1692 | Value *nextValue = scope.alloc(nValues: 1); |
1693 | ScopedValue done(scope); |
1694 | |
1695 | ScopedObject lengthIterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true)); |
1696 | CHECK_EXCEPTION(); // symbol_iterator threw; whoops. |
1697 | if (!lengthIterator) { |
1698 | return scope.engine->throwTypeError(); // symbol_iterator wasn't an object. |
1699 | } |
1700 | |
1701 | forever { |
1702 | // Here we calculate the length of the iterable range. |
1703 | if (iterableLength > (static_cast<qint64>(1) << 53) - 1) { |
1704 | ScopedValue falsey(scope, Encode(false)); |
1705 | ScopedValue error(scope, scope.engine->throwTypeError()); |
1706 | return Runtime::IteratorClose::call(scope.engine, lengthIterator, falsey); |
1707 | } |
1708 | // Retrieve the next value. If the iteration ends, we're done here. |
1709 | done = Value::fromReturnedValue(val: Runtime::IteratorNext::call(scope.engine, lengthIterator, nextValue)); |
1710 | if (scope.engine->hasException) |
1711 | return Runtime::IteratorClose::call(scope.engine, lengthIterator, Value::fromBoolean(b: false)); |
1712 | if (done->toBoolean()) { |
1713 | break; |
1714 | } |
1715 | iterableLength++; |
1716 | } |
1717 | |
1718 | // Constructor validity check goes after we have calculated the length, because that calculation can throw |
1719 | // errors that are not type errors and at least the tests expect those rather than type errors. |
1720 | if (!C || !C->isConstructor()) |
1721 | return scope.engine->throwTypeError(); |
1722 | |
1723 | ScopedObject iterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true)); |
1724 | CHECK_EXCEPTION(); // symbol_iterator can throw. |
1725 | if (!iterator) { |
1726 | return scope.engine->throwTypeError(); // symbol_iterator wasn't an object. |
1727 | } |
1728 | |
1729 | ScopedObject a(scope, Value::undefinedValue()); |
1730 | ScopedValue ctorArgument(scope, Value::fromReturnedValue(val: QV4::Encode(int(iterableLength)))); |
1731 | a = C->callAsConstructor(argv: ctorArgument, argc: 1); |
1732 | CHECK_EXCEPTION(); |
1733 | |
1734 | // We check exceptions above, and only after doing so, check the array's validity after construction. |
1735 | if (!::validateTypedArray(o: a) || (a->getLength() < iterableLength)) |
1736 | return scope.engine->throwTypeError(); |
1737 | |
1738 | |
1739 | // The loop below traverses the iterator, and puts elements into the created array. |
1740 | ScopedValue mappedValue(scope, Value::undefinedValue()); |
1741 | for (qint64 k = 0; k < iterableLength; ++k) { |
1742 | done = Value::fromReturnedValue(val: Runtime::IteratorNext::call(scope.engine, iterator, nextValue)); |
1743 | if (scope.engine->hasException) |
1744 | return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(b: false)); |
1745 | |
1746 | if (mapfn) { |
1747 | mapArguments[0] = *nextValue; |
1748 | mapArguments[1] = Value::fromDouble(d: k); |
1749 | mappedValue = mapfn->call(thisObject: thisArg, argv: mapArguments, argc: 2); |
1750 | if (scope.engine->hasException) |
1751 | return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(b: false)); |
1752 | } else { |
1753 | mappedValue = *nextValue; |
1754 | } |
1755 | |
1756 | a->put(idx: k, v: mappedValue); |
1757 | if (scope.engine->hasException) |
1758 | return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(b: false)); |
1759 | } |
1760 | return a.asReturnedValue(); |
1761 | } else { |
1762 | // Array-like fallback. We request elements by index, and put them into the created array. |
1763 | ScopedObject arrayLike(scope, argv[0].toObject(e: scope.engine)); |
1764 | if (!arrayLike) |
1765 | return scope.engine->throwTypeError(message: QString::fromLatin1(str: "Cannot convert %1 to object" ).arg(a: argv[0].toQStringNoThrow())); |
1766 | |
1767 | int len = arrayLike->getLength(); |
1768 | CHECK_EXCEPTION(); |
1769 | |
1770 | // Getting the length may throw, and must do so before we check the constructor validity. |
1771 | if (!C || !C->isConstructor()) |
1772 | return scope.engine->throwTypeError(); |
1773 | |
1774 | ScopedObject a(scope, Value::undefinedValue()); |
1775 | ScopedValue ctorArgument(scope, Value::fromReturnedValue(val: QV4::Encode(len))); |
1776 | a = C->callAsConstructor(argv: ctorArgument, argc: 1); |
1777 | CHECK_EXCEPTION(); |
1778 | |
1779 | // We check exceptions above, and only after doing so, check the array's validity after construction. |
1780 | if (!::validateTypedArray(o: a) || (a->getLength() < len)) |
1781 | return scope.engine->throwTypeError(); |
1782 | |
1783 | ScopedValue mappedValue(scope, Value::undefinedValue()); |
1784 | ScopedValue kValue(scope); |
1785 | for (int k = 0; k < len; ++k) { |
1786 | kValue = arrayLike->get(idx: k); |
1787 | CHECK_EXCEPTION(); |
1788 | |
1789 | if (mapfn) { |
1790 | mapArguments[0] = kValue; |
1791 | mapArguments[1] = Value::fromDouble(d: k); |
1792 | mappedValue = mapfn->call(thisObject: thisArg, argv: mapArguments, argc: 2); |
1793 | CHECK_EXCEPTION(); |
1794 | } else { |
1795 | mappedValue = kValue; |
1796 | } |
1797 | |
1798 | a->put(idx: k, v: mappedValue); |
1799 | CHECK_EXCEPTION(); |
1800 | } |
1801 | return a.asReturnedValue(); |
1802 | } |
1803 | } |
1804 | |
1805 | void IntrinsicTypedArrayPrototype::init(ExecutionEngine *engine, IntrinsicTypedArrayCtor *ctor) |
1806 | { |
1807 | Scope scope(engine); |
1808 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: *this); |
1809 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 0)); |
1810 | ScopedString s(scope, engine->newString(QStringLiteral("TypedArray" ))); |
1811 | ctor->defineReadonlyConfigurableProperty(name: engine->id_name(), value: s); |
1812 | s = scope.engine->newString(QStringLiteral("of" )); |
1813 | ctor->defineDefaultProperty(name: s, code: IntrinsicTypedArrayCtor::method_of); |
1814 | s = scope.engine->newString(QStringLiteral("from" )); |
1815 | ctor->defineDefaultProperty(name: s, code: IntrinsicTypedArrayCtor::method_from, argumentCount: 1); |
1816 | ctor->addSymbolSpecies(); |
1817 | |
1818 | defineAccessorProperty(QStringLiteral("buffer" ), getter: method_get_buffer, setter: nullptr); |
1819 | defineAccessorProperty(QStringLiteral("byteLength" ), getter: method_get_byteLength, setter: nullptr); |
1820 | defineAccessorProperty(QStringLiteral("byteOffset" ), getter: method_get_byteOffset, setter: nullptr); |
1821 | defineAccessorProperty(QStringLiteral("length" ), getter: method_get_length, setter: nullptr); |
1822 | |
1823 | defineDefaultProperty(QStringLiteral("copyWithin" ), code: method_copyWithin, argumentCount: 2); |
1824 | defineDefaultProperty(QStringLiteral("entries" ), code: method_entries, argumentCount: 0); |
1825 | defineDefaultProperty(QStringLiteral("every" ), code: method_every, argumentCount: 1); |
1826 | defineDefaultProperty(QStringLiteral("fill" ), code: method_fill, argumentCount: 1); |
1827 | defineDefaultProperty(QStringLiteral("filter" ), code: method_filter, argumentCount: 1); |
1828 | defineDefaultProperty(QStringLiteral("find" ), code: method_find, argumentCount: 1); |
1829 | defineDefaultProperty(QStringLiteral("findIndex" ), code: method_findIndex, argumentCount: 1); |
1830 | defineDefaultProperty(QStringLiteral("forEach" ), code: method_forEach, argumentCount: 1); |
1831 | defineDefaultProperty(QStringLiteral("includes" ), code: method_includes, argumentCount: 1); |
1832 | defineDefaultProperty(QStringLiteral("indexOf" ), code: method_indexOf, argumentCount: 1); |
1833 | defineDefaultProperty(QStringLiteral("join" ), code: method_join, argumentCount: 1); |
1834 | defineDefaultProperty(QStringLiteral("keys" ), code: method_keys, argumentCount: 0); |
1835 | defineDefaultProperty(QStringLiteral("lastIndexOf" ), code: method_lastIndexOf, argumentCount: 1); |
1836 | defineDefaultProperty(QStringLiteral("map" ), code: method_map, argumentCount: 1); |
1837 | defineDefaultProperty(QStringLiteral("reduce" ), code: method_reduce, argumentCount: 1); |
1838 | defineDefaultProperty(QStringLiteral("reduceRight" ), code: method_reduceRight, argumentCount: 1); |
1839 | defineDefaultProperty(QStringLiteral("reverse" ), code: method_reverse, argumentCount: 0); |
1840 | defineDefaultProperty(QStringLiteral("some" ), code: method_some, argumentCount: 1); |
1841 | defineDefaultProperty(QStringLiteral("set" ), code: method_set, argumentCount: 1); |
1842 | defineDefaultProperty(QStringLiteral("slice" ), code: method_slice, argumentCount: 2); |
1843 | defineDefaultProperty(QStringLiteral("subarray" ), code: method_subarray, argumentCount: 2); |
1844 | defineDefaultProperty(name: engine->id_toLocaleString(), code: method_toLocaleString, argumentCount: 0); |
1845 | ScopedObject f(scope, engine->arrayPrototype()->get(name: engine->id_toString())); |
1846 | defineDefaultProperty(name: engine->id_toString(), value: f); |
1847 | |
1848 | ScopedString valuesString(scope, engine->newIdentifier(QStringLiteral("values" ))); |
1849 | ScopedObject values(scope, FunctionObject::createBuiltinFunction(engine, nameOrSymbol: valuesString, code: method_values, argumentCount: 0)); |
1850 | defineDefaultProperty(QStringLiteral("values" ), value: values); |
1851 | defineDefaultProperty(name: engine->symbol_iterator(), value: values); |
1852 | |
1853 | defineAccessorProperty(name: engine->symbol_toStringTag(), getter: method_get_toStringTag, setter: nullptr); |
1854 | } |
1855 | |