diff --git a/lib/VM/JSLib/Array.cpp b/lib/VM/JSLib/Array.cpp index 3383f7bd2cd..24ebd2ebfac 100644 --- a/lib/VM/JSLib/Array.cpp +++ b/lib/VM/JSLib/Array.cpp @@ -1014,13 +1014,11 @@ arrayPrototypeJoin(void *, Runtime &runtime, NativeArgs args) { struct : Locals { PinnedValue O; PinnedValue<> lenProp; - PinnedValue<> separator; PinnedValue sep; PinnedValue strings; - PinnedValue<> i; PinnedValue<> elem; - PinnedValue S; PinnedValue elementStr; + PinnedValue inputStorage; } lv; LocalsRAII lraii{runtime, &lv}; @@ -1031,13 +1029,14 @@ arrayPrototypeJoin(void *, Runtime &runtime, NativeArgs args) { } lv.O = vmcast(*objRes); - auto emptyString = runtime.getPredefinedStringHandle(Predefined::emptyString); - - if (runtime.insertVisitedObject(*lv.O)) - return emptyString.getHermesValue(); + if (runtime.insertVisitedObject(*lv.O)) { + return HermesValue::encodeStringValue( + runtime.getPredefinedString(Predefined::emptyString)); + } auto cycleScope = llvh::make_scope_exit( [&runtime, &lv] { runtime.removeVisitedObject(*lv.O); }); + // Obtain input length in len. auto propRes = JSObject::getNamed_RJS( lv.O, runtime, Predefined::getSymbolID(Predefined::length)); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { @@ -1050,71 +1049,145 @@ arrayPrototypeJoin(void *, Runtime &runtime, NativeArgs args) { } uint64_t len = *intRes; - // Use comma for separator if the first argument is undefined. - lv.separator = args.getArg(0).isUndefined() - ? HermesValue::encodeStringValue( - runtime.getPredefinedString(Predefined::comma)) - : args.getArg(0); - auto strRes = toString_RJS(runtime, lv.separator); - if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { - return ExecutionStatus::EXCEPTION; + // Determine the separator. Use comma if the first argument is undefined. + if (args.getArg(0).isUndefined()) { + lv.sep = runtime.getPredefinedString(Predefined::comma); + } else { + // Otherwise convert it to string. + auto strRes = toString_RJS(runtime, args.getArgHandle(0)); + if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) + return ExecutionStatus::EXCEPTION; + lv.sep = std::move(*strRes); } - lv.sep = std::move(*strRes); + // Exit early if the input is empty. if (len == 0) { return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::emptyString)); } - // Track the size of the resultant string. Use a 64-bit value to detect - // overflow. + // Track the size of the resultant string. SafeUInt32 size; - // Storage for the strings for each element. + // Storage for the strings for each element must fit in a JSArray. if (LLVM_UNLIKELY(len > JSArray::StorageType::maxElements())) { return runtime.raiseRangeError("Out of memory for array elements."); } - auto arrRes = JSArray::create(runtime, len, 0); - if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { - return ExecutionStatus::EXCEPTION; + + // The first element not processed by the fast path. + uint32_t fastPathEnd = 0; + + // 1. Fast Path: Process as many elements as possible quickly. + if (JSArray *arr = dyn_vmcast(lv.O.get()); + arr && arrayFastPathCheck(runtime, arr, nullptr, (uint32_t)len)) { + // Accumulate the size of the strings in the array, stopping at the first + // element that is not a string, null, or undefined. + auto *storage = arr->getIndexedStorage(runtime); + // Save it for later. + lv.inputStorage = storage; + + uint32_t i; + for (i = 0; i < len; ++i) { + SmallHermesValue elem = storage->at(runtime, i); // Direct access + uint32_t elemLen; + if (elem.isString()) + elemLen = elem.getString(runtime)->getStringLength(); + else if (elem.isNull() || elem.isUndefined() || elem.isEmpty()) + elemLen = 0; + else + break; + + if (i > 0) + size.add(lv.sep->getStringLength()); + size.add(elemLen); + } + fastPathEnd = i; + // Check for string overflow to create the illusion that we are appending + // to the string. Also, prevent uint32_t overflow. + if (size.isOverflowed() || + size.get() > StringPrimitive::MAX_STRING_LENGTH) { + return runtime.raiseRangeError("String is too long"); + } } - lv.strings = std::move(*arrRes); - // Call toString on all the elements of the array. - // i can't be larger than UINT32_MAX as checked above. - for (uint32_t i = 0; i < len; ++i) { - // Add the size of the separator, except the first time. - if (i) - size.add(lv.sep->getStringLength()); + // If there are remaining elements that weren't strings, or the input wasn't + // a dense array at all. + if (fastPathEnd < len) { + // Create temporary storage for the remaining input strings. Element i of + // lv.strings will correspond to element i + fastPathEnd of the input array. + + auto arrRes = JSArray::create(runtime, 0, 0); + if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) + return ExecutionStatus::EXCEPTION; + lv.strings = std::move(*arrRes); - GCScope gcScope2(runtime); - lv.i = HermesValue::encodeTrustedNumberValue(i); + // Resize the array. + if (LLVM_UNLIKELY( + JSArray::setStorageEndIndex( + lv.strings, runtime, len - fastPathEnd) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } if (LLVM_UNLIKELY( - (propRes = JSObject::getComputed_RJS(lv.O, runtime, lv.i)) == + JSArray::setLengthProperty( + lv.strings, runtime, len - fastPathEnd) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } - lv.elem = std::move(*propRes); + auto marker = gcScope.createMarker(); + // Call toString on the remaining elements of the array. + // i can't be larger than UINT32_MAX as checked above. + for (uint32_t i = fastPathEnd; i < len; + gcScope.flushToMarker(marker), ++i) { + // Add the size of the separator, except the first time. + if (i) + size.add(lv.sep->getStringLength()); + + // Fetch the element into lv.elem. + if (lv.inputStorage.get()) { + // Fast-path: input array is dense. + lv.elem = lv.inputStorage->at(runtime, i).unboxToHV(runtime); + } else { + PinnedValue<> key = HermesValue::encodeTrustedNumberValue(i); + if (LLVM_UNLIKELY( + (propRes = JSObject::getComputed_RJS(lv.O, runtime, key)) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + lv.elem = std::move(*propRes); + } - if (lv.elem->isUndefined() || lv.elem->isNull()) { - JSArray::setElementAt(lv.strings, runtime, i, emptyString); - } else { - // Otherwise, call toString_RJS() and push the result, incrementing - // size. - auto strRes = toString_RJS(runtime, lv.elem); - if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { - return ExecutionStatus::EXCEPTION; + // null and undefined are empty strings. The empty could come from + // reading the input storage directly; it acts as undefined. + if (lv.elem->isUndefined() || lv.elem->isNull() || lv.elem->isEmpty()) { + auto emptyString = SmallHermesValue::encodeStringValue( + runtime.getPredefinedString(Predefined::emptyString), runtime); + JSArray::unsafeSetExistingElementAt( + lv.strings.get(), runtime, i - fastPathEnd, emptyString); + } else { + // Otherwise, call toString_RJS() and save the result, incrementing + // size. + auto strRes = toString_RJS(runtime, lv.elem); + if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) + return ExecutionStatus::EXCEPTION; + + StringPrimitive *s = strRes.getValue().get(); + size.add(s->getStringLength()); + + JSArray::unsafeSetExistingElementAt( + lv.strings.get(), + runtime, + i - fastPathEnd, + SmallHermesValue::encodeStringValue(s, runtime)); } - lv.S = std::move(*strRes); - size.add(lv.S->getStringLength()); - JSArray::setElementAt(lv.strings, runtime, i, lv.S); - } - // Check for string overflow on every iteration to create the illusion - // that we are appending to the string. Also, prevent uint32_t overflow. - if (size.isOverflowed()) { - return runtime.raiseRangeError("String is too long"); + // Check for string overflow on every iteration to create the illusion + // that we are appending to the string. Also, prevent uint32_t overflow. + if (size.isOverflowed() || + size.get() > StringPrimitive::MAX_STRING_LENGTH) { + return runtime.raiseRangeError("String is too long"); + } } } @@ -1123,13 +1196,26 @@ arrayPrototypeJoin(void *, Runtime &runtime, NativeArgs args) { if (builder == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } - lv.elementStr = lv.strings->at(runtime, 0).getString(runtime); - builder->appendStringPrim(lv.elementStr); - for (size_t i = 1; i < len; ++i) { - builder->appendStringPrim(lv.sep); - lv.elementStr = lv.strings->at(runtime, i).getString(runtime); + + for (uint32_t i = 0; i < fastPathEnd; ++i) { + if (i > 0) + builder->appendStringPrim(lv.sep); + SmallHermesValue elem = lv.inputStorage->at(runtime, i); + if (elem.isString()) { + lv.elementStr = lv.inputStorage->at(runtime, i).getString(runtime); + builder->appendStringPrim(lv.elementStr); + } else { + assert(elem.isNull() || elem.isUndefined() || elem.isEmpty()); + } + } + + for (uint32_t i = fastPathEnd; i < len; ++i) { + if (i > 0) + builder->appendStringPrim(lv.sep); + lv.elementStr = lv.strings->at(runtime, i - fastPathEnd).getString(runtime); builder->appendStringPrim(lv.elementStr); } + return HermesValue::encodeStringValue(*builder->getStringPrimitive()); } @@ -4265,13 +4351,9 @@ arrayPrototypeIncludes(void *, Runtime &runtime, NativeArgs args) { /// ES14.0 23.1.3.33 CallResult arrayPrototypeToReversed(void *, Runtime &runtime, NativeArgs args) { - GCScope gcScope{runtime}; struct : Locals { PinnedValue O; PinnedValue A; - PinnedValue<> pk; - PinnedValue<> from; - PinnedValue<> fromValue; } lv; LocalsRAII lraii{runtime, &lv}; @@ -4296,57 +4378,73 @@ arrayPrototypeToReversed(void *, Runtime &runtime, NativeArgs args) { if (LLVM_UNLIKELY(len32 != len)) { return runtime.raiseRangeError("invalid array length"); } - auto ARes = JSArray::create(runtime, len32, len32); + auto ARes = JSArray::create(runtime, 0, 0); if (LLVM_UNLIKELY(ARes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } lv.A = std::move(*ARes); + if (LLVM_UNLIKELY( + JSArray::setStorageEndIndex(lv.A, runtime, len32) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + if (LLVM_UNLIKELY( + JSArray::setLengthProperty(lv.A, runtime, len32) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } - // 4. Let k be 0. - double k = 0; - auto marker = gcScope.createMarker(); - // 5. Repeat, while k < len, - while (k < len) { - gcScope.flushToMarker(marker); - - double from = len - k - 1; - // 5a. Let from be ! ToString(𝔽(len - k - 1)). - lv.from = HermesValue::encodeUntrustedNumberValue(from); - - // 5b. Let Pk be ! ToString(𝔽(k)). - lv.pk = HermesValue::encodeTrustedNumberValue(k); + // Fast Path: Input is a JSArray and arrayFastPathCheck passes. + if (jsArr && arrayFastPathCheck(runtime, jsArr.get(), nullptr, len32)) { + auto *srcStorage = jsArr->getIndexedStorage(runtime); + auto *destStorage = lv.A->getIndexedStorage(runtime); + for (uint32_t to = 0, from = len32 - 1; to < len32; ++to, --from) { + SmallHermesValue fromValue = srcStorage->at(runtime, from); + destStorage->set( + runtime, + to, + LLVM_LIKELY(!fromValue.isEmpty()) + ? fromValue + : SmallHermesValue::encodeUndefinedValue()); + } + return lv.A.getHermesValue(); + } + // Read a single element. This is a lambda to get around SmallHermesValue not + // having an assignment operator. + auto readElem = + [&runtime, &jsArr, &lv](uint32_t from) -> CallResult { // 5c. Let fromValue be ? Get(O, from). if (LLVM_LIKELY(jsArr)) { - const SmallHermesValue elm = jsArr->at(runtime, from); + auto elm = jsArr->at(runtime, from); // If the element is not empty, we can return it directly here. // Otherwise, we must proceed to the slow path. - if (!elm.isEmpty()) { - lv.fromValue = elm.unboxToHV(runtime); - } + if (!elm.isEmpty()) + return elm; } + // Slow path - else { - CallResult> propRes = - JSObject::getComputed_RJS(lv.O, runtime, lv.from); - if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { - return ExecutionStatus::EXCEPTION; - } - lv.fromValue = std::move(*propRes); + GCScopeMarkerRAII marker{runtime}; + PinnedValue fromV = HermesValue::encodeTrustedNumberValue(from); + CallResult> propRes = + JSObject::getComputed_RJS(lv.O, runtime, fromV); + if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; } + return SmallHermesValue::encodeHermesValue( + propRes->getHermesValue(), runtime); + }; - // 5d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue). - if (LLVM_UNLIKELY( - JSObject::defineOwnComputedPrimitive( - lv.A, - runtime, - lv.pk, - DefinePropertyFlags::getDefaultNewPropertyFlags(), - lv.fromValue, - PropOpFlags().plusThrowOnError()) == - ExecutionStatus::EXCEPTION)) { + // 4. Let k be 0. + uint32_t k = 0; + // 5. Repeat, while k < len, + while (k < len) { + uint32_t from = len - k - 1; + auto elm = readElem(from); + if (LLVM_UNLIKELY(elm == ExecutionStatus::EXCEPTION)) return ExecutionStatus::EXCEPTION; - } + + JSArray::unsafeSetExistingElementAt(lv.A.get(), runtime, k, *elm); // 5e. Set k to k + 1. ++k; diff --git a/test/hermes/array-functions.js b/test/hermes/array-functions.js index d794163e149..135486b1385 100644 --- a/test/hermes/array-functions.js +++ b/test/hermes/array-functions.js @@ -10,6 +10,8 @@ // RUN: %shermes -exec %s | %FileCheck --match-full-lines %s "use strict"; +// TODO: adopt some of the helpers from array-reverse.js + // Performs a nested array comparison between the two arrays. function arrayEquals(a, b) { if (a.length !== b.length) { @@ -386,161 +388,6 @@ var a = {length: 2**53 - 1}; try { a.push(1); } catch (e) { print('caught', e.name) } // CHECK-NEXT: caught TypeError -print('reverse'); -// CHECK-LABEL: reverse - -function testReverse(arr) { - function toWeirdArray(a) { - a = Array.from(a); - a.__proto__ = toWeirdArray.prototype; - return a; - } - toWeirdArray.prototype.__proto__ = Array.prototype; - - function toBadArray(a) { - a = Array.from(a); - if (a.length < 2) - return a; - let elem = a[1]; - delete a[1]; - toBadArray.prototype[1] = elem; - a.__proto__ = toBadArray.prototype; - return a; - } - toBadArray.prototype.__proto__ = Array.prototype; - - function toObj(a) { - return {...a, length: a.length}; - } - - print("input:", arr, "end"); - print("array:", Array.from(arr).reverse(), "end"); - print("obj:", JSON.stringify(Array.prototype.reverse.call(toObj(arr)))); - print("weird array:", toWeirdArray(arr).reverse(), "end"); - print("bad array:", toBadArray(arr).reverse(), "end"); -} - -testReverse([1,2,3]); -// CHECK-NEXT: input: 1,2,3 end -// CHECK-NEXT: array: 3,2,1 end -// CHECK-NEXT: obj: {"0":3,"1":2,"2":1,"length":3} -// CHECK-NEXT: weird array: 3,2,1 end -// CHECK-NEXT: bad array: 3,2,1 end - -testReverse([1,2]); -// CHECK-NEXT: input: 1,2 end -// CHECK-NEXT: array: 2,1 end -// CHECK-NEXT: obj: {"0":2,"1":1,"length":2} -// CHECK-NEXT: weird array: 2,1 end -// CHECK-NEXT: bad array: 2,1 end - -testReverse([]); -// CHECK-NEXT: input: end -// CHECK-NEXT: array: end -// CHECK-NEXT: obj: {"length":0} -// CHECK-NEXT: weird array: end -// CHECK-NEXT: bad array: end - -testReverse([12,13,14,15,16,17,18]); -// CHECK-NEXT: input: 12,13,14,15,16,17,18 end -// CHECK-NEXT: array: 18,17,16,15,14,13,12 end -// CHECK-NEXT: obj: {"0":18,"1":17,"2":16,"3":15,"4":14,"5":13,"6":12,"length":7} -// CHECK-NEXT: weird array: 18,17,16,15,14,13,12 end -// CHECK-NEXT: bad array: 18,17,16,15,14,13,12 end - -testReverse([,,,1]); -// CHECK-NEXT: input: ,,,1 end -// CHECK-NEXT: array: 1,,, end -// CHECK-NEXT: obj: {"0":1,"length":4} -// CHECK-NEXT: weird array: 1,,, end -// CHECK-NEXT: bad array: 1,,, end - -testReverse([,,1,,5,7,,]); -// CHECK-NEXT: input: ,,1,,5,7, end -// CHECK-NEXT: array: ,7,5,,1,, end -// CHECK-NEXT: obj: {"1":7,"2":5,"4":1,"length":7} -// CHECK-NEXT: weird array: ,7,5,,1,, end -// CHECK-NEXT: bad array: ,7,5,,1,, end - -var a = {}; -a[0] = 'a'; -a[1] = 'b'; -a[5] = 'c'; -a.length = 6; -Array.prototype.reverse.call(a); -print(a[0], a[4], a[5]); -// CHECK-NEXT: c b a -var a = [0, 1, 2, 3]; -Object.defineProperty(a, 3, { - get: function() { - delete a[1]; - return -1; - }, - set: function() { print('setter'); } -}); -a.reverse(); -print(a); -// CHECK-NEXT: setter -// CHECK-NEXT: -1,2,,-1 -var a = [0, 1]; -Object.defineProperty(a, 0, { - get: function() { - a.pop(); - return -1; - } -}); -a.reverse(); -print(a); -// CHECK-NEXT: ,-1 -var a = []; -Object.defineProperties(a, { - '0': { - get: function() { - print('getter 0'); - return a.val_0; - }, - set: function(v) { a.val_0 = v; } - }, - '1': { - get: function() { - print('getter 1'); - return a.val_1; - }, - set: function(v) { a.val_1 = v; } - }, -}); -a[0] = 0; -a[1] = 1; -a.reverse(); -print(a); -// CHECK-NEXT: getter 0 -// CHECK-NEXT: getter 1 -// CHECK-NEXT: getter 0 -// CHECK-NEXT: getter 1 -// CHECK-NEXT: 1,0 -var a = [0, 1, 2, 3, 4, 5, 6]; -Object.defineProperties(a, { - '0': { - get: function() { - a.pop(); - return -1; - } - }, - '1': { - set: function() { a.push('a'); } - }, - '2': { - get: function() { - a.push('b'); - return -2; - }, - set: function() { a.pop(); } - } -}); -a.reverse(); -print(a); -// CHECK-NEXT: ,,-2,3,-2,,-1,a - print('shift'); // CHECK-LABEL: shift var a = [1,2,3]; @@ -1201,21 +1048,6 @@ print(Array.prototype.at.call({length: 3, 0: 'a', 1: 'b', 2: 'c'}, -1)); print(Array.prototype.at.call({length: 30}, 5)); // CHECK-NEXT: undefined -print('toReversed'); -// CHECK-LABEL: toReversed -print(Array.prototype.toReversed.length); -// CHECK-NEXT: 0 -var a = [1,2,3,4]; -print(a.toReversed().toString()) -// CHECK-NEXT: 4,3,2,1 -print(a.toString()) -// CHECK-NEXT: 1,2,3,4 -print(arrayEquals([ 1, 2, 3 ].toReversed(), [ 3, 2, 1 ])); -// CHECK-NEXT: true -print(Array.prototype.toReversed.call({length : 3, 0 : 'a', 1 : 'b', 2 : 'c'}) - .toString()) -// CHECK-NEXT: c,b,a - print('toSpliced'); // CHECK-LABEL: toSpliced print(Array.prototype.toSpliced.length); diff --git a/test/hermes/array-reverse.js b/test/hermes/array-reverse.js new file mode 100644 index 00000000000..75ab31f593d --- /dev/null +++ b/test/hermes/array-reverse.js @@ -0,0 +1,273 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermes -target=HBC -O %s | %FileCheck --match-full-lines %s +// RUN: %hermes -target=HBC -O -emit-binary -out %t.hbc %s && %hermes %t.hbc | %FileCheck --match-full-lines %s +// RUN: %shermes -exec %s | %FileCheck --match-full-lines %s +"use strict"; + +// Performs a nested array comparison between the two arrays. +function arrayEquals(a, b) { + if (a.length !== b.length) { + return false; + } + for (var i = 0; i < a.length; ++i) { + if (Array.isArray(a[i]) && Array.isArray(b[i])) { + if (!arrayEquals(a[i], b[i])) { + return false; + } + } else { + if (a[i] !== b[i]) { + return false; + } + } + } + return true; +} + +function pr(msg, arr, end) { + function fmtArray(arr) { + var res = "["; + for(var i = 0; i < arr.length; ++i) { + if (i) + res += ", "; + if (i in arr) + res += arr[i]; + else + res += ""; + } + return res + "]"; + } + print(msg, fmtArray(arr), end); +} +function arrayClone(a) { + var res = []; + res.length = a.length; + for(var i = 0; i < a.length; ++i) + if (a.hasOwnProperty(i)) + res[i] = a[i]; + return res; +} +function toWeirdArray(a) { + a = arrayClone(a); + a.__proto__ = {__proto__: Array.prototype}; + return a; +} +function toBadArray(a) { + a = arrayClone(a); + + let prototype = {__proto__: Array.prototype}; + + let key = 0; + for(let i = 1; i < a.length; ++i) { + if (a.hasOwnProperty(i)) { + key = i; + break; + } + } + if (!key) + return a; + + let elem = a[key]; + delete a[key]; + prototype[key] = elem; + a.__proto__ = prototype; + return a; +} +function toObj(a) { + return {...a, length: a.length}; +} + +print('reverse'); +// CHECK: reverse + +function testReverse(arr) { + pr("input:", arr, "end"); + pr("array:", arrayClone(arr).reverse(), "end"); + pr("obj:", Array.prototype.reverse.call(toObj(arr))); + pr("weird array:", toWeirdArray(arr).reverse(), "end"); + pr("bad array:", toBadArray(arr).reverse(), "end"); +} + +testReverse([1,2,3]); +// CHECK-NEXT: input: [1, 2, 3] end +// CHECK-NEXT: array: [3, 2, 1] end +// CHECK-NEXT: obj: [3, 2, 1] undefined +// CHECK-NEXT: weird array: [3, 2, 1] end +// CHECK-NEXT: bad array: [3, 2, 1] end + +testReverse([1,2]); +// CHECK-NEXT: input: [1, 2] end +// CHECK-NEXT: array: [2, 1] end +// CHECK-NEXT: obj: [2, 1] undefined +// CHECK-NEXT: weird array: [2, 1] end +// CHECK-NEXT: bad array: [2, 1] end + +testReverse([]); +// CHECK-NEXT: input: [] end +// CHECK-NEXT: array: [] end +// CHECK-NEXT: obj: [] undefined +// CHECK-NEXT: weird array: [] end +// CHECK-NEXT: bad array: [] end + +testReverse([12,13,14,15,16,17,18]); +// CHECK-NEXT: input: [12, 13, 14, 15, 16, 17, 18] end +// CHECK-NEXT: array: [18, 17, 16, 15, 14, 13, 12] end +// CHECK-NEXT: obj: [18, 17, 16, 15, 14, 13, 12] undefined +// CHECK-NEXT: weird array: [18, 17, 16, 15, 14, 13, 12] end +// CHECK-NEXT: bad array: [18, 17, 16, 15, 14, 13, 12] end + +testReverse([,,,1]); +// CHECK-NEXT: input: [, , , 1] end +// CHECK-NEXT: array: [1, , , ] end +// CHECK-NEXT: obj: [1, , , ] undefined +// CHECK-NEXT: weird array: [1, , , ] end +// CHECK-NEXT: bad array: [1, , , 1] end + +testReverse([,,1,,5,7,,]); +// CHECK-NEXT: input: [, , 1, , 5, 7, ] end +// CHECK-NEXT: array: [, 7, 5, , 1, , ] end +// CHECK-NEXT: obj: [, 7, 5, , 1, , ] undefined +// CHECK-NEXT: weird array: [, 7, 5, , 1, , ] end +// CHECK-NEXT: bad array: [, 7, 5, , 1, , ] end + +var a = {}; +a[0] = 'a'; +a[1] = 'b'; +a[5] = 'c'; +a.length = 6; +Array.prototype.reverse.call(a); +print(a[0], a[4], a[5]); +// CHECK-NEXT: c b a +var a = [0, 1, 2, 3]; +Object.defineProperty(a, 3, { + get: function() { + delete a[1]; + return -1; + }, + set: function() { print('setter'); } +}); +a.reverse(); +print(a); +// CHECK-NEXT: setter +// CHECK-NEXT: -1,2,,-1 +var a = [0, 1]; +Object.defineProperty(a, 0, { + get: function() { + a.pop(); + return -1; + } +}); +a.reverse(); +print(a); +// CHECK-NEXT: ,-1 +var a = []; +Object.defineProperties(a, { + '0': { + get: function() { + print('getter 0'); + return a.val_0; + }, + set: function(v) { a.val_0 = v; } + }, + '1': { + get: function() { + print('getter 1'); + return a.val_1; + }, + set: function(v) { a.val_1 = v; } + }, +}); +a[0] = 0; +a[1] = 1; +a.reverse(); +print(a); +// CHECK-NEXT: getter 0 +// CHECK-NEXT: getter 1 +// CHECK-NEXT: getter 0 +// CHECK-NEXT: getter 1 +// CHECK-NEXT: 1,0 +var a = [0, 1, 2, 3, 4, 5, 6]; +Object.defineProperties(a, { + '0': { + get: function() { + a.pop(); + return -1; + } + }, + '1': { + set: function() { a.push('a'); } + }, + '2': { + get: function() { + a.push('b'); + return -2; + }, + set: function() { a.pop(); } + } +}); +a.reverse(); +print(a); +// CHECK-NEXT: ,,-2,3,-2,,-1,a + +print('shift'); +// CHECK-LABEL: shift +var a = [1,2,3]; +print(a.shift(), a, a.length); +// CHECK-NEXT: 1 2,3 2 +print(a.shift(), a, a.length); +// CHECK-NEXT: 2 3 1 +print(a.shift(), a, a.length); +// CHECK-NEXT: 3 0 +print(a.shift(), a, a.length); +// CHECK-NEXT: undefined 0 +print(a.shift(), a, a.length); +// CHECK-NEXT: undefined 0 + +print('toReversed'); +// CHECK-LABEL: toReversed + +print(Array.prototype.toReversed.length); +// CHECK-NEXT: 0 +var a = [1,2,3,4]; +print(a.toReversed().toString()) +// CHECK-NEXT: 4,3,2,1 +print(a.toString()) +// CHECK-NEXT: 1,2,3,4 +print(arrayEquals([ 1, 2, 3 ].toReversed(), [ 3, 2, 1 ])); +// CHECK-NEXT: true +print(Array.prototype.toReversed.call({length : 3, 0 : 'a', 1 : 'b', 2 : 'c'}) + .toString()) +// CHECK-NEXT: c,b,a + +print('toReversed'); +// CHECK-LABEL: toReversed + +function testToReversed(arr) { + pr("input:", arr, "end"); + pr("array:", arr.toReversed(), "end"); + pr("obj:", Array.prototype.toReversed.call(toObj(arr))); + pr("weird array:", toWeirdArray(arr).toReversed(), "end"); + pr("bad array:", toBadArray(arr).toReversed(), "end"); +} + +print(Array.prototype.toReversed.length); +// CHECK-NEXT: 0 + +testToReversed(["a", "b", "c", "d"]); +// CHECK-NEXT: input: [a, b, c, d] end +// CHECK-NEXT: array: [d, c, b, a] end +// CHECK-NEXT: obj: [d, c, b, a] undefined +// CHECK-NEXT: weird array: [d, c, b, a] end +// CHECK-NEXT: bad array: [d, c, b, a] end + +testToReversed(["a", "b",,,]); +// CHECK-NEXT: input: [a, b, , ] end +// CHECK-NEXT: array: [undefined, undefined, b, a] end +// CHECK-NEXT: obj: [undefined, undefined, b, a] undefined +// CHECK-NEXT: weird array: [undefined, undefined, b, a] end +// CHECK-NEXT: bad array: [undefined, undefined, b, a] end