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