1 | // Copyright (C) 2018 Crimson AS <info@crimson.no> |
2 | // Copyright (C) 2016 The Qt Company Ltd. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include "qv4arrayobject_p.h" |
6 | #include "qv4arrayiterator_p.h" |
7 | #include "qv4objectproto_p.h" |
8 | #include "qv4argumentsobject_p.h" |
9 | #include "qv4runtime_p.h" |
10 | #include "qv4symbol_p.h" |
11 | #include <QtCore/qscopedvaluerollback.h> |
12 | |
13 | using namespace QV4; |
14 | |
15 | DEFINE_OBJECT_VTABLE(ArrayCtor); |
16 | |
17 | void Heap::ArrayCtor::init(QV4::ExecutionContext *scope) |
18 | { |
19 | Heap::FunctionObject::init(scope, QStringLiteral("Array" )); |
20 | } |
21 | |
22 | ReturnedValue ArrayCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
23 | { |
24 | ExecutionEngine *v4 = static_cast<const ArrayCtor *>(f)->engine(); |
25 | Scope scope(v4); |
26 | ScopedArrayObject a(scope, v4->newArrayObject()); |
27 | if (newTarget) |
28 | a->setProtoFromNewTarget(newTarget); |
29 | uint len; |
30 | if (argc == 1 && argv[0].isNumber()) { |
31 | bool ok; |
32 | len = argv[0].asArrayLength(ok: &ok); |
33 | |
34 | if (!ok) |
35 | return v4->throwRangeError(value: argv[0]); |
36 | |
37 | if (len < 0x1000) |
38 | a->arrayReserve(n: len); |
39 | } else { |
40 | len = argc; |
41 | a->arrayReserve(n: len); |
42 | a->arrayPut(index: 0, values: argv, n: len); |
43 | } |
44 | a->setArrayLengthUnchecked(len); |
45 | |
46 | return a.asReturnedValue(); |
47 | } |
48 | |
49 | ReturnedValue ArrayCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc) |
50 | { |
51 | return virtualCallAsConstructor(f, argv, argc, newTarget: f); |
52 | } |
53 | |
54 | void ArrayPrototype::init(ExecutionEngine *engine, Object *ctor) |
55 | { |
56 | Scope scope(engine); |
57 | ScopedObject o(scope); |
58 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 1)); |
59 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this)); |
60 | ctor->defineDefaultProperty(QStringLiteral("isArray" ), code: method_isArray, argumentCount: 1); |
61 | ctor->defineDefaultProperty(QStringLiteral("of" ), code: method_of, argumentCount: 0); |
62 | ctor->defineDefaultProperty(QStringLiteral("from" ), code: method_from, argumentCount: 1); |
63 | ctor->addSymbolSpecies(); |
64 | |
65 | Scoped<InternalClass> ic(scope, engine->classes[EngineBase::Class_Empty] |
66 | ->changeVTable(vt: QV4::Object::staticVTable())); |
67 | ScopedObject unscopables(scope, engine->newObject(internalClass: ic->d())); |
68 | ScopedString name(scope); |
69 | defineDefaultProperty(QStringLiteral("constructor" ), value: (o = ctor)); |
70 | defineDefaultProperty(name: engine->id_toString(), code: method_toString, argumentCount: 0); |
71 | defineDefaultProperty(name: engine->id_toLocaleString(), code: method_toLocaleString, argumentCount: 0); |
72 | defineDefaultProperty(QStringLiteral("concat" ), code: method_concat, argumentCount: 1); |
73 | name = engine->newIdentifier(QStringLiteral("copyWithin" )); |
74 | unscopables->put(name, v: Value::fromBoolean(b: true)); |
75 | defineDefaultProperty(name, code: method_copyWithin, argumentCount: 2); |
76 | name = engine->newIdentifier(QStringLiteral("entries" )); |
77 | unscopables->put(name, v: Value::fromBoolean(b: true)); |
78 | defineDefaultProperty(name, code: method_entries, argumentCount: 0); |
79 | name = engine->newIdentifier(QStringLiteral("fill" )); |
80 | unscopables->put(name, v: Value::fromBoolean(b: true)); |
81 | defineDefaultProperty(name, code: method_fill, argumentCount: 1); |
82 | name = engine->newIdentifier(QStringLiteral("find" )); |
83 | unscopables->put(name, v: Value::fromBoolean(b: true)); |
84 | defineDefaultProperty(name, code: method_find, argumentCount: 1); |
85 | name = engine->newIdentifier(QStringLiteral("findIndex" )); |
86 | unscopables->put(name, v: Value::fromBoolean(b: true)); |
87 | defineDefaultProperty(name, code: method_findIndex, argumentCount: 1); |
88 | name = engine->newIdentifier(QStringLiteral("includes" )); |
89 | unscopables->put(name, v: Value::fromBoolean(b: true)); |
90 | defineDefaultProperty(name, code: method_includes, argumentCount: 1); |
91 | defineDefaultProperty(QStringLiteral("join" ), code: method_join, argumentCount: 1); |
92 | name = engine->newIdentifier(QStringLiteral("keys" )); |
93 | unscopables->put(name, v: Value::fromBoolean(b: true)); |
94 | defineDefaultProperty(name, code: method_keys, argumentCount: 0); |
95 | defineDefaultProperty(QStringLiteral("pop" ), code: method_pop, argumentCount: 0); |
96 | defineDefaultProperty(QStringLiteral("push" ), code: method_push, argumentCount: 1); |
97 | defineDefaultProperty(QStringLiteral("reverse" ), code: method_reverse, argumentCount: 0); |
98 | defineDefaultProperty(QStringLiteral("shift" ), code: method_shift, argumentCount: 0); |
99 | defineDefaultProperty(QStringLiteral("slice" ), code: method_slice, argumentCount: 2); |
100 | defineDefaultProperty(QStringLiteral("sort" ), code: method_sort, argumentCount: 1); |
101 | defineDefaultProperty(QStringLiteral("splice" ), code: method_splice, argumentCount: 2); |
102 | defineDefaultProperty(QStringLiteral("unshift" ), code: method_unshift, argumentCount: 1); |
103 | defineDefaultProperty(QStringLiteral("indexOf" ), code: method_indexOf, argumentCount: 1); |
104 | defineDefaultProperty(QStringLiteral("lastIndexOf" ), code: method_lastIndexOf, argumentCount: 1); |
105 | defineDefaultProperty(QStringLiteral("every" ), code: method_every, argumentCount: 1); |
106 | defineDefaultProperty(QStringLiteral("some" ), code: method_some, argumentCount: 1); |
107 | defineDefaultProperty(QStringLiteral("forEach" ), code: method_forEach, argumentCount: 1); |
108 | defineDefaultProperty(QStringLiteral("map" ), code: method_map, argumentCount: 1); |
109 | defineDefaultProperty(QStringLiteral("filter" ), code: method_filter, argumentCount: 1); |
110 | defineDefaultProperty(QStringLiteral("reduce" ), code: method_reduce, argumentCount: 1); |
111 | defineDefaultProperty(QStringLiteral("reduceRight" ), code: method_reduceRight, argumentCount: 1); |
112 | ScopedString valuesString(scope, engine->newIdentifier(QStringLiteral("values" ))); |
113 | ScopedObject values(scope, FunctionObject::createBuiltinFunction(engine, nameOrSymbol: valuesString, code: method_values, argumentCount: 0)); |
114 | engine->jsObjects[ExecutionEngine::ArrayProtoValues] = values; |
115 | unscopables->put(name: valuesString, v: Value::fromBoolean(b: true)); |
116 | defineDefaultProperty(name: valuesString, value: values); |
117 | defineDefaultProperty(name: engine->symbol_iterator(), value: values); |
118 | |
119 | defineReadonlyConfigurableProperty(name: engine->symbol_unscopables(), value: unscopables); |
120 | } |
121 | |
122 | ReturnedValue ArrayPrototype::method_isArray(const FunctionObject *, const Value *, const Value *argv, int argc) |
123 | { |
124 | if (!argc || !argv->objectValue()) |
125 | return Encode(false); |
126 | return Encode(argv->objectValue()->isArray()); |
127 | } |
128 | |
129 | static ScopedObject createObjectFromCtorOrArray(Scope &scope, ScopedFunctionObject ctor, bool useLen, int len) |
130 | { |
131 | ScopedObject a(scope, Value::undefinedValue()); |
132 | |
133 | if (ctor && ctor->isConstructor()) { |
134 | // this isn't completely kosher. for instance: |
135 | // Array.from.call(Object, []).constructor == Object |
136 | // is expected by the tests, but naturally, we get Number. |
137 | ScopedValue argument(scope, useLen ? Value::fromReturnedValue(val: QV4::Encode(len)) |
138 | : Value::undefinedValue()); |
139 | a = ctor->callAsConstructor(argv: argument, argc: useLen ? 1 : 0); |
140 | } else { |
141 | a = scope.engine->newArrayObject(count: len); |
142 | } |
143 | |
144 | return a; |
145 | } |
146 | |
147 | ReturnedValue ArrayPrototype::method_from(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) |
148 | { |
149 | Scope scope(builtin); |
150 | ScopedFunctionObject thatCtor(scope, thisObject); |
151 | ScopedObject itemsObject(scope, argv[0]); |
152 | bool usingIterator = false; |
153 | |
154 | if (itemsObject) { |
155 | // If the object claims to support iterators, then let's try use them. |
156 | ScopedValue it(scope, itemsObject->get(name: scope.engine->symbol_iterator())); |
157 | if (!it->isNullOrUndefined()) { |
158 | ScopedFunctionObject itfunc(scope, it); |
159 | if (!itfunc) |
160 | return scope.engine->throwTypeError(); |
161 | usingIterator = true; |
162 | } |
163 | } |
164 | |
165 | ScopedFunctionObject mapfn(scope, Value::undefinedValue()); |
166 | Value *mapArguments = nullptr; |
167 | if (argc > 1) { |
168 | mapfn = ScopedFunctionObject(scope, argv[1]); |
169 | if (!mapfn) |
170 | return scope.engine->throwTypeError(message: QString::fromLatin1(ba: "%1 is not a function" ).arg(a: argv[1].toQStringNoThrow())); |
171 | mapArguments = scope.alloc(nValues: 2); |
172 | } |
173 | |
174 | ScopedValue thisArg(scope); |
175 | if (argc > 2) |
176 | thisArg = argv[2]; |
177 | |
178 | if (usingIterator) { |
179 | // Item iteration supported, so let's go ahead and try use that. |
180 | ScopedObject a(createObjectFromCtorOrArray(scope, ctor: thatCtor, useLen: false, len: 0)); |
181 | CHECK_EXCEPTION(); |
182 | ScopedObject iterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true)); |
183 | CHECK_EXCEPTION(); // symbol_iterator threw; whoops. |
184 | if (!iterator) { |
185 | return scope.engine->throwTypeError(); // symbol_iterator wasn't an object. |
186 | } |
187 | |
188 | qint64 k = 0; |
189 | ScopedValue mappedValue(scope); |
190 | Value *nextValue = scope.alloc(nValues: 1); |
191 | ScopedValue done(scope); |
192 | |
193 | // The loop below pulls out all the properties using the iterator, and |
194 | // sets them into the created array. |
195 | forever { |
196 | if (k > (static_cast<qint64>(1) << 53) - 1) { |
197 | ScopedValue falsey(scope, Encode(false)); |
198 | ScopedValue error(scope, scope.engine->throwTypeError()); |
199 | return Runtime::IteratorClose::call(scope.engine, iterator, falsey); |
200 | } |
201 | |
202 | // Retrieve the next value. If the iteration ends, we're done here. |
203 | done = Value::fromReturnedValue(val: Runtime::IteratorNext::call(scope.engine, iterator, nextValue)); |
204 | CHECK_EXCEPTION(); |
205 | if (done->toBoolean()) { |
206 | if (ArrayObject *ao = a->as<ArrayObject>()) { |
207 | ao->setArrayLengthUnchecked(k); |
208 | } else { |
209 | a->set(name: scope.engine->id_length(), v: Value::fromDouble(d: k), shouldThrow: QV4::Object::DoThrowOnRejection); |
210 | CHECK_EXCEPTION(); |
211 | } |
212 | return a.asReturnedValue(); |
213 | } |
214 | |
215 | if (mapfn) { |
216 | Q_ASSERT(mapArguments); // if mapfn is set, we always setup mapArguments with scope.alloc |
217 | mapArguments[0] = *nextValue; |
218 | mapArguments[1] = Value::fromDouble(d: k); |
219 | mappedValue = mapfn->call(thisObject: thisArg, argv: mapArguments, argc: 2); |
220 | if (scope.hasException()) |
221 | return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(b: false)); |
222 | } else { |
223 | mappedValue = *nextValue; |
224 | } |
225 | |
226 | if (a->getOwnProperty(id: PropertyKey::fromArrayIndex(idx: k)) == Attr_Invalid) { |
227 | a->arraySet(index: k, value: mappedValue); |
228 | } else { |
229 | // Don't return: we need to close the iterator. |
230 | scope.engine->throwTypeError(message: QString::fromLatin1(ba: "Cannot redefine property: %1" ).arg(a: k)); |
231 | } |
232 | |
233 | if (scope.hasException()) { |
234 | ScopedValue falsey(scope, Encode(false)); |
235 | return Runtime::IteratorClose::call(scope.engine, iterator, falsey); |
236 | } |
237 | |
238 | k++; |
239 | } |
240 | |
241 | // the return is hidden up in the loop above, when iteration finishes. |
242 | } else { |
243 | // Array-like fallback. We request properties by index, and set them on |
244 | // the return object. |
245 | ScopedObject arrayLike(scope, argv[0].toObject(e: scope.engine)); |
246 | if (!arrayLike) |
247 | return scope.engine->throwTypeError(message: QString::fromLatin1(ba: "Cannot convert %1 to object" ).arg(a: argv[0].toQStringNoThrow())); |
248 | qint64 len = arrayLike->getLength(); |
249 | ScopedObject a(createObjectFromCtorOrArray(scope, ctor: thatCtor, useLen: true, len)); |
250 | CHECK_EXCEPTION(); |
251 | |
252 | qint64 k = 0; |
253 | ScopedValue mappedValue(scope, Value::undefinedValue()); |
254 | ScopedValue kValue(scope); |
255 | while (k < len) { |
256 | kValue = arrayLike->get(idx: k); |
257 | CHECK_EXCEPTION(); |
258 | |
259 | if (mapfn) { |
260 | Q_ASSERT(mapArguments); // if mapfn is set, we always setup mapArguments with scope.alloc |
261 | mapArguments[0] = kValue; |
262 | mapArguments[1] = Value::fromDouble(d: k); |
263 | mappedValue = mapfn->call(thisObject: thisArg, argv: mapArguments, argc: 2); |
264 | CHECK_EXCEPTION(); |
265 | } else { |
266 | mappedValue = kValue; |
267 | } |
268 | |
269 | if (a->getOwnProperty(id: PropertyKey::fromArrayIndex(idx: k)) != Attr_Invalid) |
270 | return scope.engine->throwTypeError(message: QString::fromLatin1(ba: "Cannot redefine property: %1" ).arg(a: k)); |
271 | |
272 | a->arraySet(index: k, value: mappedValue); |
273 | CHECK_EXCEPTION(); |
274 | |
275 | k++; |
276 | } |
277 | |
278 | if (ArrayObject *ao = a->as<ArrayObject>()) { |
279 | ao->setArrayLengthUnchecked(k); |
280 | } else { |
281 | a->set(name: scope.engine->id_length(), v: Value::fromDouble(d: k), shouldThrow: QV4::Object::DoThrowOnRejection); |
282 | CHECK_EXCEPTION(); |
283 | } |
284 | return a.asReturnedValue(); |
285 | } |
286 | |
287 | } |
288 | |
289 | ReturnedValue ArrayPrototype::method_of(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) |
290 | { |
291 | Scope scope(builtin); |
292 | ScopedFunctionObject that(scope, thisObject); |
293 | ScopedObject a(createObjectFromCtorOrArray(scope, ctor: that, useLen: true, len: argc)); |
294 | CHECK_EXCEPTION(); |
295 | |
296 | int k = 0; |
297 | while (k < argc) { |
298 | if (a->getOwnProperty(id: PropertyKey::fromArrayIndex(idx: k)) != Attr_Invalid) { |
299 | return scope.engine->throwTypeError(message: QString::fromLatin1(ba: "Cannot redefine property: %1" ).arg(a: k)); |
300 | } |
301 | a->arraySet(index: k, value: argv[k]); |
302 | CHECK_EXCEPTION(); |
303 | |
304 | k++; |
305 | } |
306 | |
307 | // ArrayObject updates its own length, and will throw if we try touch it. |
308 | if (!a->as<ArrayObject>()) { |
309 | a->set(name: scope.engine->id_length(), v: Value::fromDouble(d: argc), shouldThrow: QV4::Object::DoThrowOnRejection); |
310 | CHECK_EXCEPTION(); |
311 | } |
312 | |
313 | return a.asReturnedValue(); |
314 | } |
315 | |
316 | ReturnedValue ArrayPrototype::method_toString(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc) |
317 | { |
318 | Scope scope(builtin); |
319 | ScopedObject that(scope, thisObject->toObject(e: scope.engine)); |
320 | if (scope.hasException()) |
321 | return QV4::Encode::undefined(); |
322 | |
323 | ScopedString string(scope, scope.engine->newString(QStringLiteral("join" ))); |
324 | ScopedFunctionObject f(scope, that->get(name: string)); |
325 | if (f) |
326 | return checkedResult(v4: scope.engine, result: f->call(thisObject: that, argv, argc)); |
327 | return ObjectPrototype::method_toString(builtin, thisObject: that, argv, argc); |
328 | } |
329 | |
330 | ReturnedValue ArrayPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int) |
331 | { |
332 | Scope scope(b); |
333 | ScopedObject instance(scope, thisObject); |
334 | if (!instance) |
335 | return scope.engine->throwTypeError(); |
336 | |
337 | uint len = instance->getLength(); |
338 | const QString separator = QStringLiteral("," ); |
339 | |
340 | QString R; |
341 | |
342 | ScopedValue v(scope); |
343 | ScopedString s(scope); |
344 | |
345 | ScopedPropertyKey tolocaleString(scope, scope.engine->id_toLocaleString()->toPropertyKey()); |
346 | Q_ASSERT(!scope.engine->hasException); |
347 | |
348 | for (uint k = 0; k < len; ++k) { |
349 | if (k) |
350 | R += separator; |
351 | |
352 | v = instance->get(idx: k); |
353 | if (v->isNullOrUndefined()) |
354 | continue; |
355 | |
356 | ScopedObject valueAsObject(scope, v->toObject(e: scope.engine)); |
357 | Q_ASSERT(valueAsObject); // null and undefined handled above |
358 | |
359 | ScopedFunctionObject function(scope, valueAsObject->get(id: tolocaleString)); |
360 | if (!function) |
361 | return scope.engine->throwTypeError(); |
362 | |
363 | v = function->call(thisObject: valueAsObject, argv: nullptr, argc: 0); |
364 | if (scope.hasException()) |
365 | return Encode::undefined(); |
366 | |
367 | s = v->toString(e: scope.engine); |
368 | if (scope.hasException()) |
369 | return Encode::undefined(); |
370 | |
371 | R += s->toQString(); |
372 | } |
373 | return scope.engine->newString(s: R)->asReturnedValue(); |
374 | } |
375 | |
376 | ReturnedValue ArrayPrototype::method_concat(const FunctionObject *b, const Value *that, const Value *argv, int argc) |
377 | { |
378 | Scope scope(b); |
379 | ScopedObject thisObject(scope, that->toObject(e: scope.engine)); |
380 | if (!thisObject) |
381 | RETURN_UNDEFINED(); |
382 | |
383 | ScopedArrayObject result(scope, scope.engine->newArrayObject()); |
384 | |
385 | ScopedArrayObject elt(scope); |
386 | ScopedObject eltAsObj(scope); |
387 | ScopedValue entry(scope); |
388 | for (int i = -1; i < argc; ++i) { |
389 | const Value *v = i == -1 ? thisObject.getPointer() : argv + i; |
390 | eltAsObj = *v; |
391 | elt = *v; |
392 | if (elt) { |
393 | uint n = elt->getLength(); |
394 | uint newLen = ArrayData::append(obj: result, otherObj: elt, n); |
395 | result->setArrayLengthUnchecked(newLen); |
396 | } else if (eltAsObj && eltAsObj->isConcatSpreadable()) { |
397 | const uint startIndex = result->getLength(); |
398 | const uint len = eltAsObj->getLength(); |
399 | if (scope.hasException()) |
400 | return Encode::undefined(); |
401 | |
402 | for (uint i = 0; i < len; ++i) { |
403 | bool hasProperty = false; |
404 | entry = eltAsObj->get(idx: i, hasProperty: &hasProperty); |
405 | if (hasProperty) { |
406 | if (!result->put(idx: startIndex + i, v: entry)) |
407 | return scope.engine->throwTypeError(); |
408 | } |
409 | } |
410 | } else if (eltAsObj && eltAsObj->isArrayLike()) { |
411 | const uint startIndex = result->getLength(); |
412 | for (int i = 0, len = eltAsObj->getLength(); i < len; ++i) { |
413 | entry = eltAsObj->get(idx: i); |
414 | // spec says not to throw if this fails |
415 | result->put(idx: startIndex + i, v: entry); |
416 | } |
417 | } else { |
418 | result->arraySet(index: result->getLength(), value: *v); |
419 | } |
420 | } |
421 | |
422 | return result.asReturnedValue(); |
423 | } |
424 | |
425 | ReturnedValue ArrayPrototype::method_copyWithin(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
426 | { |
427 | Scope scope(b); |
428 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
429 | if (!instance) |
430 | RETURN_UNDEFINED(); |
431 | |
432 | double len = instance->getLength(); |
433 | double target = argv[0].toInteger(); |
434 | double start = argc > 1 ? argv[1].toInteger() : 0; |
435 | double end = len; |
436 | |
437 | if (argc > 2 && !argv[2].isUndefined()) { |
438 | end = argv[2].toInteger(); |
439 | } |
440 | |
441 | double relativeTarget = target; |
442 | double relativeStart = start; |
443 | double relativeEnd = end; |
444 | double from = 0; |
445 | double to = 0; |
446 | |
447 | if (relativeTarget < 0) { |
448 | to = std::max(a: len+relativeTarget, b: 0.0); |
449 | } else { |
450 | to = std::min(a: relativeTarget, b: len); |
451 | } |
452 | if (relativeStart < 0) { |
453 | from = std::max(a: len+relativeStart, b: 0.0); |
454 | } else { |
455 | from = std::min(a: relativeStart, b: len); |
456 | } |
457 | |
458 | double fin = 0; |
459 | if (relativeEnd < 0) { |
460 | fin = std::max(a: len+relativeEnd, b: 0.0); |
461 | } else { |
462 | fin = std::min(a: relativeEnd, b: len); |
463 | } |
464 | double count = std::min(a: fin-from, b: len-to); |
465 | double direction = 1; |
466 | if (from < to && to < from+count) { |
467 | direction = -1; |
468 | from = from + count - 1; |
469 | to = to + count - 1; |
470 | } |
471 | |
472 | while (count > 0) { |
473 | bool fromPresent = false; |
474 | ScopedValue fromVal(scope, instance->get(idx: from, hasProperty: &fromPresent)); |
475 | |
476 | if (fromPresent) { |
477 | instance->setIndexed(idx: to, v: fromVal, shouldThrow: QV4::Object::DoThrowOnRejection); |
478 | CHECK_EXCEPTION(); |
479 | } else { |
480 | bool didDelete = instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: to)); |
481 | CHECK_EXCEPTION(); |
482 | if (!didDelete) { |
483 | return scope.engine->throwTypeError(); |
484 | } |
485 | } |
486 | |
487 | from = from + direction; |
488 | to = to + direction; |
489 | count = count - 1; |
490 | } |
491 | |
492 | return instance.asReturnedValue(); |
493 | } |
494 | |
495 | ReturnedValue ArrayPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) |
496 | { |
497 | Scope scope(b); |
498 | ScopedObject O(scope, thisObject->toObject(e: scope.engine)); |
499 | if (!O) |
500 | RETURN_UNDEFINED(); |
501 | |
502 | Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(o: O)); |
503 | ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; |
504 | return ao->asReturnedValue(); |
505 | } |
506 | |
507 | ReturnedValue ArrayPrototype::method_find(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
508 | { |
509 | Scope scope(b); |
510 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
511 | if (!instance) |
512 | RETURN_UNDEFINED(); |
513 | |
514 | uint len = instance->getLength(); |
515 | |
516 | if (!argc || !argv[0].isFunctionObject()) |
517 | THROW_TYPE_ERROR(); |
518 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
519 | |
520 | ScopedValue result(scope); |
521 | Value *arguments = scope.alloc(nValues: 3); |
522 | |
523 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
524 | |
525 | for (uint k = 0; k < len; ++k) { |
526 | arguments[0] = instance->get(idx: k); |
527 | CHECK_EXCEPTION(); |
528 | |
529 | arguments[1] = Value::fromDouble(d: k); |
530 | arguments[2] = instance; |
531 | result = callback->call(thisObject: that, argv: arguments, argc: 3); |
532 | |
533 | CHECK_EXCEPTION(); |
534 | if (result->toBoolean()) |
535 | return arguments[0].asReturnedValue(); |
536 | } |
537 | |
538 | RETURN_UNDEFINED(); |
539 | } |
540 | |
541 | ReturnedValue ArrayPrototype::method_findIndex(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
542 | { |
543 | Scope scope(b); |
544 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
545 | if (!instance) |
546 | RETURN_UNDEFINED(); |
547 | |
548 | uint len = instance->getLength(); |
549 | |
550 | if (!argc || !argv[0].isFunctionObject()) |
551 | THROW_TYPE_ERROR(); |
552 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
553 | |
554 | ScopedValue result(scope); |
555 | Value *arguments = scope.alloc(nValues: 3); |
556 | |
557 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
558 | |
559 | for (uint k = 0; k < len; ++k) { |
560 | arguments[0] = instance->get(idx: k); |
561 | CHECK_EXCEPTION(); |
562 | |
563 | arguments[1] = Value::fromDouble(d: k); |
564 | arguments[2] = instance; |
565 | result = callback->call(thisObject: that, argv: arguments, argc: 3); |
566 | |
567 | CHECK_EXCEPTION(); |
568 | if (result->toBoolean()) |
569 | return Encode(k); |
570 | } |
571 | |
572 | return Encode(-1); |
573 | } |
574 | |
575 | ReturnedValue ArrayPrototype::method_join(const FunctionObject *functionObject, |
576 | const Value *thisObject, const Value *argv, int argc) |
577 | { |
578 | Scope scope(functionObject); |
579 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
580 | |
581 | if (!instance) |
582 | return Encode(scope.engine->newString()); |
583 | |
584 | // We cannot optimize the resolution of the argument away in case of length == 0 |
585 | // It may have side effects. |
586 | ScopedValue argument(scope, argc ? argv[0] : Value::undefinedValue()); |
587 | const QString separator = argument->isUndefined() |
588 | ? QStringLiteral("," ) |
589 | : argument->toQString(); |
590 | |
591 | ScopedValue scopedLength(scope, instance->get(name: scope.engine->id_length())); |
592 | const quint32 genericLength = scopedLength->isUndefined() ? 0 : scopedLength->toUInt32(); |
593 | if (!genericLength) |
594 | return Encode(scope.engine->newString()); |
595 | |
596 | QString result; |
597 | if (auto *arrayObject = instance->as<ArrayObject>()) { |
598 | ScopedValue entry(scope); |
599 | const qint64 arrayLength = arrayObject->getLength(); |
600 | Q_ASSERT(arrayLength >= 0); |
601 | Q_ASSERT(arrayLength <= std::numeric_limits<quint32>::max()); |
602 | for (quint32 i = 0; i < quint32(arrayLength); ++i) { |
603 | if (i) |
604 | result += separator; |
605 | |
606 | entry = arrayObject->get(idx: i); |
607 | CHECK_EXCEPTION(); |
608 | if (!entry->isNullOrUndefined()) |
609 | result += entry->toQString(); |
610 | } |
611 | } else { |
612 | ScopedString name(scope, scope.engine->newString(QStringLiteral("0" ))); |
613 | ScopedValue value(scope, instance->get(name)); |
614 | CHECK_EXCEPTION(); |
615 | |
616 | if (!value->isNullOrUndefined()) |
617 | result = value->toQString(); |
618 | |
619 | for (quint32 i = 1; i < genericLength; ++i) { |
620 | result += separator; |
621 | |
622 | name = Value::fromDouble(d: i).toString(e: scope.engine); |
623 | value = instance->get(name); |
624 | CHECK_EXCEPTION(); |
625 | |
626 | if (!value->isNullOrUndefined()) |
627 | result += value->toQString(); |
628 | } |
629 | } |
630 | |
631 | return Encode(scope.engine->newString(s: result)); |
632 | } |
633 | |
634 | ReturnedValue ArrayPrototype::method_pop(const FunctionObject *b, const Value *thisObject, const Value *, int) |
635 | { |
636 | Scope scope(b); |
637 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
638 | if (!instance) |
639 | RETURN_UNDEFINED(); |
640 | |
641 | uint len = instance->getLength(); |
642 | |
643 | if (!len) { |
644 | if (!instance->isArrayObject()) |
645 | instance->put(name: scope.engine->id_length(), v: ScopedValue(scope, Value::fromInt32(i: 0))); |
646 | RETURN_UNDEFINED(); |
647 | } |
648 | |
649 | ScopedValue result(scope, instance->get(idx: len - 1)); |
650 | CHECK_EXCEPTION(); |
651 | |
652 | if (!instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: len - 1))) |
653 | return scope.engine->throwTypeError(); |
654 | |
655 | if (instance->isArrayObject()) |
656 | instance->setArrayLength(len - 1); |
657 | else { |
658 | if (!instance->put(name: scope.engine->id_length(), v: ScopedValue(scope, Value::fromDouble(d: len - 1)))) |
659 | return scope.engine->throwTypeError(); |
660 | } |
661 | return result->asReturnedValue(); |
662 | } |
663 | |
664 | ReturnedValue ArrayPrototype::method_push(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
665 | { |
666 | Scope scope(b); |
667 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
668 | if (!instance) |
669 | RETURN_UNDEFINED(); |
670 | |
671 | instance->arrayCreate(); |
672 | Q_ASSERT(instance->arrayData()); |
673 | |
674 | qint64 len = instance->getLength(); |
675 | |
676 | if (len + quint64(argc) >= UINT_MAX) { |
677 | // ughh... this goes beyond UINT_MAX |
678 | double l = len; |
679 | ScopedString s(scope); |
680 | for (int i = 0, ei = argc; i < ei; ++i) { |
681 | s = Value::fromDouble(d: l + i).toString(e: scope.engine); |
682 | if (!instance->put(name: s, v: argv[i])) |
683 | return scope.engine->throwTypeError(); |
684 | } |
685 | double newLen = l + argc; |
686 | if (!instance->isArrayObject()) { |
687 | if (!instance->put(name: scope.engine->id_length(), v: ScopedValue(scope, Value::fromDouble(d: newLen)))) |
688 | return scope.engine->throwTypeError(); |
689 | } else { |
690 | ScopedString str(scope, scope.engine->newString(QStringLiteral("Array.prototype.push: Overflow" ))); |
691 | return scope.engine->throwRangeError(value: str); |
692 | } |
693 | return Encode(newLen); |
694 | } |
695 | |
696 | if (!argc) |
697 | ; |
698 | else if (!instance->protoHasArray() && instance->arrayData()->length() <= len && instance->arrayData()->type == Heap::ArrayData::Simple) { |
699 | instance->arrayData()->vtable()->putArray(instance, len, argv, argc); |
700 | len = instance->arrayData()->length(); |
701 | } else { |
702 | for (int i = 0, ei = argc; i < ei; ++i) { |
703 | if (!instance->put(idx: len + i, v: argv[i])) |
704 | return scope.engine->throwTypeError(); |
705 | } |
706 | len += argc; |
707 | } |
708 | if (instance->isArrayObject()) |
709 | instance->setArrayLengthUnchecked(len); |
710 | else { |
711 | if (!instance->put(name: scope.engine->id_length(), v: ScopedValue(scope, Value::fromDouble(d: len)))) |
712 | return scope.engine->throwTypeError(); |
713 | } |
714 | |
715 | return Encode(uint(len)); |
716 | } |
717 | |
718 | ReturnedValue ArrayPrototype::method_reverse(const FunctionObject *b, const Value *thisObject, const Value *, int) |
719 | { |
720 | Scope scope(b); |
721 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
722 | if (!instance) |
723 | RETURN_UNDEFINED(); |
724 | |
725 | qint64 length = instance->getLength(); |
726 | // ### FIXME |
727 | if (length >= UINT_MAX) |
728 | return scope.engine->throwRangeError(message: QLatin1String("Array.prototype.reverse: Length out of range." )); |
729 | |
730 | int lo = 0, hi = length - 1; |
731 | |
732 | ScopedValue lval(scope); |
733 | ScopedValue hval(scope); |
734 | for (; lo < hi; ++lo, --hi) { |
735 | bool loExists, hiExists; |
736 | lval = instance->get(idx: lo, hasProperty: &loExists); |
737 | hval = instance->get(idx: hi, hasProperty: &hiExists); |
738 | CHECK_EXCEPTION(); |
739 | bool ok; |
740 | if (hiExists) |
741 | ok = instance->put(idx: lo, v: hval); |
742 | else |
743 | ok = instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: lo)); |
744 | if (ok) { |
745 | if (loExists) |
746 | ok = instance->put(idx: hi, v: lval); |
747 | else |
748 | ok = instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: hi)); |
749 | } |
750 | if (!ok) |
751 | return scope.engine->throwTypeError(); |
752 | } |
753 | return instance->asReturnedValue(); |
754 | } |
755 | |
756 | ReturnedValue ArrayPrototype::method_shift(const FunctionObject *b, const Value *thisObject, const Value *, int) |
757 | { |
758 | Scope scope(b); |
759 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
760 | if (!instance) |
761 | RETURN_UNDEFINED(); |
762 | |
763 | instance->arrayCreate(); |
764 | Q_ASSERT(instance->arrayData()); |
765 | |
766 | uint len = instance->getLength(); |
767 | |
768 | if (!len) { |
769 | if (!instance->isArrayObject()) |
770 | if (!instance->put(name: scope.engine->id_length(), v: ScopedValue(scope, Value::fromInt32(i: 0)))) |
771 | return scope.engine->throwTypeError(); |
772 | RETURN_UNDEFINED(); |
773 | } |
774 | |
775 | ScopedValue result(scope); |
776 | if (!instance->protoHasArray() && !instance->arrayData()->attrs && instance->arrayData()->length() <= len && instance->arrayData()->type != Heap::ArrayData::Custom) { |
777 | result = instance->arrayData()->vtable()->pop_front(instance); |
778 | } else { |
779 | result = instance->get(idx: uint(0)); |
780 | CHECK_EXCEPTION(); |
781 | ScopedValue v(scope); |
782 | // do it the slow way |
783 | for (uint k = 1; k < len; ++k) { |
784 | bool exists; |
785 | v = instance->get(idx: k, hasProperty: &exists); |
786 | CHECK_EXCEPTION(); |
787 | bool ok; |
788 | if (exists) |
789 | ok = instance->put(idx: k - 1, v); |
790 | else |
791 | ok = instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: k - 1)); |
792 | if (!ok) |
793 | return scope.engine->throwTypeError(); |
794 | } |
795 | bool ok = instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: len - 1)); |
796 | if (!ok) |
797 | return scope.engine->throwTypeError(); |
798 | } |
799 | |
800 | if (instance->isArrayObject()) |
801 | instance->setArrayLengthUnchecked(len - 1); |
802 | else { |
803 | bool ok = instance->put(name: scope.engine->id_length(), v: ScopedValue(scope, Value::fromDouble(d: len - 1))); |
804 | if (!ok) |
805 | return scope.engine->throwTypeError(); |
806 | } |
807 | |
808 | return result->asReturnedValue(); |
809 | } |
810 | |
811 | ReturnedValue ArrayPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
812 | { |
813 | Scope scope(b); |
814 | ScopedObject o(scope, thisObject->toObject(e: scope.engine)); |
815 | if (!o) |
816 | RETURN_UNDEFINED(); |
817 | |
818 | ScopedArrayObject result(scope, scope.engine->newArrayObject()); |
819 | uint len = o->getLength(); |
820 | double s = (argc ? argv[0] : Value::undefinedValue()).toInteger(); |
821 | uint start; |
822 | if (s < 0) |
823 | start = (uint)qMax(a: len + s, b: 0.); |
824 | else if (s > len) |
825 | start = len; |
826 | else |
827 | start = (uint) s; |
828 | uint end = len; |
829 | if (argc > 1 && !argv[1].isUndefined()) { |
830 | double e = argv[1].toInteger(); |
831 | if (e < 0) |
832 | end = (uint)qMax(a: len + e, b: 0.); |
833 | else if (e > len) |
834 | end = len; |
835 | else |
836 | end = (uint) e; |
837 | } |
838 | |
839 | ScopedValue v(scope); |
840 | uint n = 0; |
841 | for (uint i = start; i < end; ++i) { |
842 | bool exists; |
843 | v = o->get(idx: i, hasProperty: &exists); |
844 | CHECK_EXCEPTION(); |
845 | if (exists) |
846 | result->arraySet(index: n, value: v); |
847 | ++n; |
848 | } |
849 | return result->asReturnedValue(); |
850 | } |
851 | |
852 | ReturnedValue ArrayPrototype::method_sort(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
853 | { |
854 | Scope scope(b); |
855 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
856 | if (!instance) |
857 | RETURN_UNDEFINED(); |
858 | |
859 | uint len = instance->getLength(); |
860 | |
861 | ScopedValue comparefn(scope, argc ? argv[0] : Value::undefinedValue()); |
862 | ArrayData::sort(engine: scope.engine, thisObject: instance, comparefn, dataLen: len); |
863 | return thisObject->asReturnedValue(); |
864 | } |
865 | |
866 | ReturnedValue ArrayPrototype::method_splice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
867 | { |
868 | Scope scope(b); |
869 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
870 | if (!instance) |
871 | RETURN_UNDEFINED(); |
872 | |
873 | qint64 len = instance->getLength(); |
874 | |
875 | double rs = (argc ? argv[0] : Value::undefinedValue()).toInteger(); |
876 | qint64 start; |
877 | if (rs < 0) |
878 | start = static_cast<qint64>(qMax(a: 0., b: len + rs)); |
879 | else |
880 | start = static_cast<qint64>(qMin(a: rs, b: static_cast<double>(len))); |
881 | |
882 | qint64 deleteCount = 0; |
883 | qint64 itemCount = 0; |
884 | if (argc == 1) { |
885 | deleteCount = len - start; |
886 | } else if (argc > 1){ |
887 | itemCount = argc - 2; |
888 | double dc = argv[1].toInteger(); |
889 | deleteCount = static_cast<qint64>(qMin(a: qMax(a: dc, b: 0.), b: double(len - start))); |
890 | } |
891 | |
892 | if (len + itemCount - deleteCount > /*(static_cast<qint64>(1) << 53) - 1*/ UINT_MAX - 1) |
893 | return scope.engine->throwTypeError(); |
894 | if (deleteCount > /*(static_cast<qint64>(1) << 53) - 1*/ UINT_MAX - 1) |
895 | return scope.engine->throwRangeError(message: QString::fromLatin1(ba: "Array length out of range." )); |
896 | |
897 | ScopedArrayObject newArray(scope, scope.engine->newArrayObject()); |
898 | newArray->arrayReserve(n: deleteCount); |
899 | ScopedValue v(scope); |
900 | for (uint i = 0; i < deleteCount; ++i) { |
901 | bool exists; |
902 | v = instance->get(idx: start + i, hasProperty: &exists); |
903 | CHECK_EXCEPTION(); |
904 | if (exists) |
905 | newArray->arrayPut(index: i, value: v); |
906 | } |
907 | newArray->setArrayLengthUnchecked(deleteCount); |
908 | |
909 | |
910 | if (itemCount < deleteCount) { |
911 | for (uint k = start; k < len - deleteCount; ++k) { |
912 | bool exists; |
913 | v = instance->get(idx: k + deleteCount, hasProperty: &exists); |
914 | CHECK_EXCEPTION(); |
915 | bool ok; |
916 | if (exists) |
917 | ok = instance->put(idx: k + itemCount, v); |
918 | else |
919 | ok = instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: k + itemCount)); |
920 | if (!ok) |
921 | return scope.engine->throwTypeError(); |
922 | } |
923 | for (uint k = len; k > len - deleteCount + itemCount; --k) { |
924 | if (!instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: k - 1))) |
925 | return scope.engine->throwTypeError(); |
926 | } |
927 | } else if (itemCount > deleteCount) { |
928 | uint k = len - deleteCount; |
929 | while (k > start) { |
930 | bool exists; |
931 | v = instance->get(idx: k + deleteCount - 1, hasProperty: &exists); |
932 | CHECK_EXCEPTION(); |
933 | bool ok; |
934 | if (exists) |
935 | ok = instance->put(idx: k + itemCount - 1, v); |
936 | else |
937 | ok = instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: k + itemCount - 1)); |
938 | if (!ok) |
939 | return scope.engine->throwTypeError(); |
940 | --k; |
941 | } |
942 | } |
943 | |
944 | for (uint i = 0; i < itemCount; ++i) |
945 | instance->put(idx: start + i, v: argv[i + 2]); |
946 | |
947 | if (!instance->put(name: scope.engine->id_length(), v: ScopedValue(scope, Value::fromDouble(d: len - deleteCount + itemCount)))) |
948 | return scope.engine->throwTypeError(); |
949 | |
950 | return newArray->asReturnedValue(); |
951 | } |
952 | |
953 | ReturnedValue ArrayPrototype::method_unshift(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
954 | { |
955 | Scope scope(b); |
956 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
957 | if (!instance) |
958 | RETURN_UNDEFINED(); |
959 | |
960 | instance->arrayCreate(); |
961 | Q_ASSERT(instance->arrayData()); |
962 | |
963 | uint len = instance->getLength(); |
964 | |
965 | if (!instance->protoHasArray() && !instance->arrayData()->attrs && instance->arrayData()->length() <= len && |
966 | instance->arrayData()->type != Heap::ArrayData::Custom) { |
967 | instance->arrayData()->vtable()->push_front(instance, argv, argc); |
968 | } else { |
969 | ScopedValue v(scope); |
970 | for (uint k = len; k > 0; --k) { |
971 | bool exists; |
972 | v = instance->get(idx: k - 1, hasProperty: &exists); |
973 | bool ok; |
974 | if (exists) |
975 | ok = instance->put(idx: k + argc - 1, v); |
976 | else |
977 | ok = instance->deleteProperty(id: PropertyKey::fromArrayIndex(idx: k + argc - 1)); |
978 | if (!ok) |
979 | return scope.engine->throwTypeError(); |
980 | } |
981 | for (int i = 0, ei = argc; i < ei; ++i) { |
982 | bool ok = instance->put(idx: i, v: argv[i]); |
983 | if (!ok) |
984 | return scope.engine->throwTypeError(); |
985 | } |
986 | } |
987 | |
988 | uint newLen = len + argc; |
989 | if (instance->isArrayObject()) |
990 | instance->setArrayLengthUnchecked(newLen); |
991 | else { |
992 | if (!instance->put(name: scope.engine->id_length(), v: ScopedValue(scope, Value::fromDouble(d: newLen)))) |
993 | return scope.engine->throwTypeError(); |
994 | } |
995 | |
996 | return Encode(newLen); |
997 | } |
998 | |
999 | ReturnedValue ArrayPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1000 | { |
1001 | Scope scope(b); |
1002 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1003 | if (!instance) |
1004 | RETURN_UNDEFINED(); |
1005 | |
1006 | qint64 len = instance->getLength(); |
1007 | if (len == 0) { |
1008 | return Encode(false); |
1009 | } |
1010 | |
1011 | double n = 0; |
1012 | if (argc > 1 && !argv[1].isUndefined()) { |
1013 | n = argv[1].toInteger(); |
1014 | } |
1015 | |
1016 | double k = 0; |
1017 | if (n >= 0) { |
1018 | k = n; |
1019 | } else { |
1020 | k = len + n; |
1021 | if (k < 0) { |
1022 | k = 0; |
1023 | } |
1024 | } |
1025 | |
1026 | ScopedValue val(scope); |
1027 | while (k < len) { |
1028 | val = instance->get(idx: k); |
1029 | if (val->sameValueZero(other: argv[0])) { |
1030 | return Encode(true); |
1031 | } |
1032 | k++; |
1033 | } |
1034 | |
1035 | return Encode(false); |
1036 | } |
1037 | |
1038 | ReturnedValue ArrayPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1039 | { |
1040 | Scope scope(b); |
1041 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1042 | if (!instance) |
1043 | RETURN_UNDEFINED(); |
1044 | |
1045 | uint len = instance->getLength(); |
1046 | if (!len) |
1047 | return Encode(-1); |
1048 | |
1049 | ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue()); |
1050 | uint fromIndex = 0; |
1051 | |
1052 | if (argc >= 2) { |
1053 | double f = argv[1].toInteger(); |
1054 | CHECK_EXCEPTION(); |
1055 | if (f >= len) |
1056 | return Encode(-1); |
1057 | if (f < 0) |
1058 | f = qMax(a: len + f, b: 0.); |
1059 | fromIndex = (uint) f; |
1060 | } |
1061 | |
1062 | if (instance->isStringObject()) { |
1063 | ScopedValue v(scope); |
1064 | for (uint k = fromIndex; k < len; ++k) { |
1065 | bool exists; |
1066 | v = instance->get(idx: k, hasProperty: &exists); |
1067 | if (exists && RuntimeHelpers::strictEqual(x: v, y: searchValue)) |
1068 | return Encode(k); |
1069 | } |
1070 | return Encode(-1); |
1071 | } |
1072 | |
1073 | ScopedValue value(scope); |
1074 | |
1075 | if (ArgumentsObject::isNonStrictArgumentsObject(m: instance) || |
1076 | (instance->arrayType() >= Heap::ArrayData::Sparse) || instance->protoHasArray()) { |
1077 | // lets be safe and slow |
1078 | for (uint i = fromIndex; i < len; ++i) { |
1079 | bool exists; |
1080 | value = instance->get(idx: i, hasProperty: &exists); |
1081 | CHECK_EXCEPTION(); |
1082 | if (exists && RuntimeHelpers::strictEqual(x: value, y: searchValue)) |
1083 | return Encode(i); |
1084 | } |
1085 | } else if (!instance->arrayData()) { |
1086 | return Encode(-1); |
1087 | } else { |
1088 | Q_ASSERT(instance->arrayType() == Heap::ArrayData::Simple); |
1089 | Heap::SimpleArrayData *sa = instance->d()->arrayData.cast<Heap::SimpleArrayData>(); |
1090 | if (len > sa->values.size) |
1091 | len = sa->values.size; |
1092 | uint idx = fromIndex; |
1093 | while (idx < len) { |
1094 | value = sa->data(index: idx); |
1095 | CHECK_EXCEPTION(); |
1096 | if (RuntimeHelpers::strictEqual(x: value, y: searchValue)) |
1097 | return Encode(idx); |
1098 | ++idx; |
1099 | } |
1100 | } |
1101 | return Encode(-1); |
1102 | } |
1103 | |
1104 | ReturnedValue ArrayPrototype::method_keys(const FunctionObject *f, const Value *thisObject, const Value *, int) |
1105 | { |
1106 | Scope scope(f); |
1107 | ScopedObject O(scope, thisObject->toObject(e: scope.engine)); |
1108 | if (!O) |
1109 | RETURN_UNDEFINED(); |
1110 | |
1111 | Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(o: O)); |
1112 | ao->d()->iterationKind = IteratorKind::KeyIteratorKind; |
1113 | return ao->asReturnedValue(); |
1114 | } |
1115 | |
1116 | ReturnedValue ArrayPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1117 | { |
1118 | Scope scope(b); |
1119 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1120 | if (!instance) |
1121 | RETURN_UNDEFINED(); |
1122 | |
1123 | uint len = instance->getLength(); |
1124 | if (!len) |
1125 | return Encode(-1); |
1126 | |
1127 | ScopedValue searchValue(scope); |
1128 | uint fromIndex = len; |
1129 | |
1130 | if (argc >= 1) |
1131 | searchValue = argv[0]; |
1132 | else |
1133 | searchValue = Value::undefinedValue(); |
1134 | |
1135 | if (argc >= 2) { |
1136 | double f = argv[1].toInteger(); |
1137 | CHECK_EXCEPTION(); |
1138 | if (f > 0) |
1139 | f = qMin(a: f, b: (double)(len - 1)); |
1140 | else if (f < 0) { |
1141 | f = len + f; |
1142 | if (f < 0) |
1143 | return Encode(-1); |
1144 | } |
1145 | fromIndex = (uint) f + 1; |
1146 | } |
1147 | |
1148 | ScopedValue v(scope); |
1149 | for (uint k = fromIndex; k > 0;) { |
1150 | --k; |
1151 | bool exists; |
1152 | v = instance->get(idx: k, hasProperty: &exists); |
1153 | CHECK_EXCEPTION(); |
1154 | if (exists && RuntimeHelpers::strictEqual(x: v, y: searchValue)) |
1155 | return Encode(k); |
1156 | } |
1157 | return Encode(-1); |
1158 | } |
1159 | |
1160 | ReturnedValue ArrayPrototype::method_every(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1161 | { |
1162 | Scope scope(b); |
1163 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1164 | if (!instance) |
1165 | RETURN_UNDEFINED(); |
1166 | |
1167 | uint len = instance->getLength(); |
1168 | |
1169 | if (!argc || !argv->isFunctionObject()) |
1170 | THROW_TYPE_ERROR(); |
1171 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1172 | |
1173 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
1174 | ScopedValue r(scope); |
1175 | Value *arguments = scope.alloc(nValues: 3); |
1176 | |
1177 | bool ok = true; |
1178 | for (uint k = 0; ok && k < len; ++k) { |
1179 | bool exists; |
1180 | arguments[0] = instance->get(idx: k, hasProperty: &exists); |
1181 | if (!exists) |
1182 | continue; |
1183 | |
1184 | arguments[1] = Value::fromDouble(d: k); |
1185 | arguments[2] = instance; |
1186 | r = callback->call(thisObject: that, argv: arguments, argc: 3); |
1187 | CHECK_EXCEPTION(); |
1188 | ok = r->toBoolean(); |
1189 | } |
1190 | return Encode(ok); |
1191 | } |
1192 | |
1193 | ReturnedValue ArrayPrototype::method_fill(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1194 | { |
1195 | Scope scope(b); |
1196 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1197 | if (!instance) |
1198 | RETURN_UNDEFINED(); |
1199 | |
1200 | const qsizetype len = instance->getLength(); |
1201 | Q_ASSERT(len >= 0); |
1202 | |
1203 | const qsizetype relativeStart = argc > 1 ? argv[1].toInteger() : 0; |
1204 | qsizetype relativeEnd = len; |
1205 | if (argc > 2 && !argv[2].isUndefined()) |
1206 | relativeEnd = argv[2].toInteger(); |
1207 | |
1208 | qsizetype k = 0; |
1209 | qsizetype fin = 0; |
1210 | |
1211 | if (relativeStart < 0) { |
1212 | if (relativeStart > -len) |
1213 | k = std::max(a: len + relativeStart, b: qsizetype(0)); |
1214 | } else { |
1215 | k = std::min(a: relativeStart, b: len); |
1216 | } |
1217 | Q_ASSERT(k >= 0); |
1218 | |
1219 | if (relativeEnd < 0) { |
1220 | if (relativeEnd > -len) |
1221 | fin = std::max(a: len + relativeEnd, b: qsizetype(0)); |
1222 | } else { |
1223 | fin = std::min(a: relativeEnd, b: len); |
1224 | } |
1225 | Q_ASSERT(fin >= 0); |
1226 | |
1227 | if (sizeof(qsizetype) > sizeof(uint) && fin > qsizetype(std::numeric_limits<uint>::max())) |
1228 | return scope.engine->throwRangeError(message: QString::fromLatin1(ba: "Array length out of range." )); |
1229 | |
1230 | for (; k < fin; ++k) |
1231 | instance->setIndexed(idx: uint(k), v: argv[0], shouldThrow: QV4::Object::DoThrowOnRejection); |
1232 | |
1233 | return instance.asReturnedValue(); |
1234 | } |
1235 | |
1236 | ReturnedValue ArrayPrototype::method_some(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1237 | { |
1238 | Scope scope(b); |
1239 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1240 | if (!instance) |
1241 | RETURN_UNDEFINED(); |
1242 | |
1243 | uint len = instance->getLength(); |
1244 | |
1245 | if (!argc || !argv->isFunctionObject()) |
1246 | THROW_TYPE_ERROR(); |
1247 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1248 | |
1249 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
1250 | ScopedValue result(scope); |
1251 | Value *arguments = scope.alloc(nValues: 3); |
1252 | |
1253 | for (uint k = 0; k < len; ++k) { |
1254 | bool exists; |
1255 | arguments[0] = instance->get(idx: k, hasProperty: &exists); |
1256 | if (!exists) |
1257 | continue; |
1258 | |
1259 | arguments[1] = Value::fromDouble(d: k); |
1260 | arguments[2] = instance; |
1261 | result = callback->call(thisObject: that, argv: arguments, argc: 3); |
1262 | CHECK_EXCEPTION(); |
1263 | if (result->toBoolean()) |
1264 | return Encode(true); |
1265 | } |
1266 | return Encode(false); |
1267 | } |
1268 | |
1269 | ReturnedValue ArrayPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1270 | { |
1271 | Scope scope(b); |
1272 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1273 | if (!instance) |
1274 | RETURN_UNDEFINED(); |
1275 | |
1276 | uint len = instance->getLength(); |
1277 | |
1278 | if (!argc || !argv->isFunctionObject()) |
1279 | THROW_TYPE_ERROR(); |
1280 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1281 | |
1282 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
1283 | Value *arguments = scope.alloc(nValues: 3); |
1284 | |
1285 | for (uint k = 0; k < len; ++k) { |
1286 | bool exists; |
1287 | arguments[0] = instance->get(idx: k, hasProperty: &exists); |
1288 | if (!exists) |
1289 | continue; |
1290 | |
1291 | arguments[1] = Value::fromDouble(d: k); |
1292 | arguments[2] = instance; |
1293 | callback->call(thisObject: that, argv: arguments, argc: 3); |
1294 | } |
1295 | RETURN_UNDEFINED(); |
1296 | } |
1297 | |
1298 | ReturnedValue ArrayPrototype::method_map(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1299 | { |
1300 | Scope scope(b); |
1301 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1302 | if (!instance) |
1303 | RETURN_UNDEFINED(); |
1304 | |
1305 | qint64 len = instance->getLength(); |
1306 | |
1307 | if (!argc || !argv->isFunctionObject()) |
1308 | THROW_TYPE_ERROR(); |
1309 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1310 | |
1311 | if (len > UINT_MAX - 1) |
1312 | return scope.engine->throwRangeError(message: QString::fromLatin1(ba: "Array length out of range." )); |
1313 | |
1314 | ScopedArrayObject a(scope, scope.engine->newArrayObject()); |
1315 | a->arrayReserve(n: len); |
1316 | a->setArrayLengthUnchecked(len); |
1317 | |
1318 | ScopedValue v(scope); |
1319 | ScopedValue mapped(scope); |
1320 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
1321 | Value *arguments = scope.alloc(nValues: 3); |
1322 | |
1323 | for (uint k = 0; k < len; ++k) { |
1324 | bool exists; |
1325 | arguments[0] = instance->get(idx: k, hasProperty: &exists); |
1326 | if (!exists) |
1327 | continue; |
1328 | |
1329 | arguments[1] = Value::fromDouble(d: k); |
1330 | arguments[2] = instance; |
1331 | mapped = callback->call(thisObject: that, argv: arguments, argc: 3); |
1332 | CHECK_EXCEPTION(); |
1333 | a->arraySet(index: k, value: mapped); |
1334 | } |
1335 | return a.asReturnedValue(); |
1336 | } |
1337 | |
1338 | ReturnedValue ArrayPrototype::method_filter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1339 | { |
1340 | Scope scope(b); |
1341 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1342 | if (!instance) |
1343 | RETURN_UNDEFINED(); |
1344 | |
1345 | uint len = instance->getLength(); |
1346 | |
1347 | if (!argc || !argv->isFunctionObject()) |
1348 | THROW_TYPE_ERROR(); |
1349 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1350 | |
1351 | ScopedArrayObject a(scope, scope.engine->newArrayObject()); |
1352 | a->arrayReserve(n: len); |
1353 | |
1354 | ScopedValue selected(scope); |
1355 | ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
1356 | Value *arguments = scope.alloc(nValues: 3); |
1357 | |
1358 | uint to = 0; |
1359 | for (uint k = 0; k < len; ++k) { |
1360 | bool exists; |
1361 | arguments[0] = instance->get(idx: k, hasProperty: &exists); |
1362 | if (!exists) |
1363 | continue; |
1364 | |
1365 | arguments[1] = Value::fromDouble(d: k); |
1366 | arguments[2] = instance; |
1367 | selected = callback->call(thisObject: that, argv: arguments, argc: 3); |
1368 | CHECK_EXCEPTION(); |
1369 | if (selected->toBoolean()) { |
1370 | a->arraySet(index: to, value: arguments[0]); |
1371 | ++to; |
1372 | } |
1373 | } |
1374 | return a.asReturnedValue(); |
1375 | } |
1376 | |
1377 | ReturnedValue ArrayPrototype::method_reduce(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1378 | { |
1379 | Scope scope(b); |
1380 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1381 | if (!instance) |
1382 | RETURN_UNDEFINED(); |
1383 | |
1384 | uint len = instance->getLength(); |
1385 | |
1386 | if (!argc || !argv->isFunctionObject()) |
1387 | THROW_TYPE_ERROR(); |
1388 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1389 | |
1390 | uint k = 0; |
1391 | ScopedValue acc(scope); |
1392 | ScopedValue v(scope); |
1393 | |
1394 | if (argc > 1) { |
1395 | acc = argv[1]; |
1396 | } else { |
1397 | bool kPresent = false; |
1398 | while (k < len && !kPresent) { |
1399 | v = instance->get(idx: k, hasProperty: &kPresent); |
1400 | if (kPresent) |
1401 | acc = v; |
1402 | ++k; |
1403 | } |
1404 | if (!kPresent) |
1405 | THROW_TYPE_ERROR(); |
1406 | } |
1407 | |
1408 | Value *arguments = scope.alloc(nValues: 4); |
1409 | |
1410 | while (k < len) { |
1411 | bool kPresent; |
1412 | v = instance->get(idx: k, hasProperty: &kPresent); |
1413 | if (kPresent) { |
1414 | arguments[0] = acc; |
1415 | arguments[1] = v; |
1416 | arguments[2] = Value::fromDouble(d: k); |
1417 | arguments[3] = instance; |
1418 | acc = callback->call(thisObject: nullptr, argv: arguments, argc: 4); |
1419 | CHECK_EXCEPTION(); |
1420 | } |
1421 | ++k; |
1422 | } |
1423 | return acc->asReturnedValue(); |
1424 | } |
1425 | |
1426 | ReturnedValue ArrayPrototype::method_reduceRight(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1427 | { |
1428 | Scope scope(b); |
1429 | ScopedObject instance(scope, thisObject->toObject(e: scope.engine)); |
1430 | if (!instance) |
1431 | RETURN_UNDEFINED(); |
1432 | |
1433 | uint len = instance->getLength(); |
1434 | |
1435 | if (!argc || !argv->isFunctionObject()) |
1436 | THROW_TYPE_ERROR(); |
1437 | const FunctionObject *callback = static_cast<const FunctionObject *>(argv); |
1438 | |
1439 | if (len == 0) { |
1440 | if (argc == 1) |
1441 | THROW_TYPE_ERROR(); |
1442 | return argv[1].asReturnedValue(); |
1443 | } |
1444 | |
1445 | uint k = len; |
1446 | ScopedValue acc(scope); |
1447 | ScopedValue v(scope); |
1448 | if (argc > 1) { |
1449 | acc = argv[1]; |
1450 | } else { |
1451 | bool kPresent = false; |
1452 | while (k > 0 && !kPresent) { |
1453 | v = instance->get(idx: k - 1, hasProperty: &kPresent); |
1454 | if (kPresent) |
1455 | acc = v; |
1456 | --k; |
1457 | } |
1458 | if (!kPresent) |
1459 | THROW_TYPE_ERROR(); |
1460 | } |
1461 | |
1462 | Value *arguments = scope.alloc(nValues: 4); |
1463 | |
1464 | while (k > 0) { |
1465 | bool kPresent; |
1466 | v = instance->get(idx: k - 1, hasProperty: &kPresent); |
1467 | if (kPresent) { |
1468 | arguments[0] = acc; |
1469 | arguments[1] = v; |
1470 | arguments[2] = Value::fromDouble(d: k - 1); |
1471 | arguments[3] = instance; |
1472 | acc = callback->call(thisObject: nullptr, argv: arguments, argc: 4); |
1473 | CHECK_EXCEPTION(); |
1474 | } |
1475 | --k; |
1476 | } |
1477 | return acc->asReturnedValue(); |
1478 | } |
1479 | |
1480 | ReturnedValue ArrayPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1481 | { |
1482 | Scope scope(b); |
1483 | ScopedObject O(scope, thisObject->toObject(e: scope.engine)); |
1484 | if (!O) |
1485 | RETURN_UNDEFINED(); |
1486 | |
1487 | Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(o: O)); |
1488 | ao->d()->iterationKind = IteratorKind::ValueIteratorKind; |
1489 | return ao->asReturnedValue(); |
1490 | } |
1491 | |
1492 | ReturnedValue ArrayPrototype::method_get_species(const FunctionObject *, const Value *thisObject, const Value *, int) |
1493 | { |
1494 | return thisObject->asReturnedValue(); |
1495 | } |
1496 | |
1497 | |