lib/VM/JSLib/Array.cpp (2,924 lines of code) (raw):
/*
* 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.
*/
//===----------------------------------------------------------------------===//
/// \file
/// ES5.1 15.4 Initialize the Array constructor.
//===----------------------------------------------------------------------===//
#include "JSLibInternal.h"
#include "hermes/VM/HandleRootOwner-inline.h"
#include "hermes/VM/JSLib/Sorting.h"
#include "hermes/VM/Operations.h"
#include "hermes/VM/StringBuilder.h"
#include "hermes/VM/StringRefUtils.h"
#include "hermes/VM/StringView.h"
#include "llvh/ADT/ScopeExit.h"
namespace hermes {
namespace vm {
//===----------------------------------------------------------------------===//
/// Array.
Handle<JSObject> createArrayConstructor(Runtime &runtime) {
auto arrayPrototype = Handle<JSArray>::vmcast(&runtime.arrayPrototype);
// Array.prototype.xxx methods.
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::toString),
nullptr,
arrayPrototypeToString,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::toLocaleString),
nullptr,
arrayPrototypeToLocaleString,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::concat),
nullptr,
arrayPrototypeConcat,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::join),
nullptr,
arrayPrototypeJoin,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::push),
nullptr,
arrayPrototypePush,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::sort),
nullptr,
arrayPrototypeSort,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::forEach),
nullptr,
arrayPrototypeForEach,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::flat),
nullptr,
arrayPrototypeFlat,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::flatMap),
nullptr,
arrayPrototypeFlatMap,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::keys),
(void *)IterationKind::Key,
arrayPrototypeIterator,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::values),
(void *)IterationKind::Value,
arrayPrototypeIterator,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::entries),
(void *)IterationKind::Entry,
arrayPrototypeIterator,
0);
auto propValue = runtime.ignoreAllocationFailure(JSObject::getNamed_RJS(
arrayPrototype, runtime, Predefined::getSymbolID(Predefined::values)));
runtime.arrayPrototypeValues = std::move(propValue);
DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags();
runtime.ignoreAllocationFailure(JSObject::defineOwnProperty(
arrayPrototype,
runtime,
Predefined::getSymbolID(Predefined::SymbolIterator),
dpf,
Handle<>(&runtime.arrayPrototypeValues)));
auto cons = defineSystemConstructor<JSArray>(
runtime,
Predefined::getSymbolID(Predefined::Array),
arrayConstructor,
arrayPrototype,
1,
CellKind::JSArrayKind);
defineMethod(
runtime,
cons,
Predefined::getSymbolID(Predefined::isArray),
nullptr,
arrayIsArray,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::slice),
nullptr,
arrayPrototypeSlice,
2);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::splice),
nullptr,
arrayPrototypeSplice,
2);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::copyWithin),
nullptr,
arrayPrototypeCopyWithin,
2);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::pop),
nullptr,
arrayPrototypePop,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::shift),
nullptr,
arrayPrototypeShift,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::unshift),
nullptr,
arrayPrototypeUnshift,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::indexOf),
nullptr,
arrayPrototypeIndexOf,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::lastIndexOf),
nullptr,
arrayPrototypeLastIndexOf,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::every),
nullptr,
arrayPrototypeEvery,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::some),
nullptr,
arrayPrototypeSome,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::map),
nullptr,
arrayPrototypeMap,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::filter),
nullptr,
arrayPrototypeFilter,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::fill),
nullptr,
arrayPrototypeFill,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::find),
nullptr,
arrayPrototypeFind,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::findIndex),
// Pass a non-null pointer here to indicate we're finding the index.
(void *)true,
arrayPrototypeFind,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::findLast),
nullptr,
arrayPrototypeFindLast,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::findLastIndex),
// Pass a non-null pointer here to indicate we're finding the index.
(void *)true,
arrayPrototypeFindLast,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::reduce),
nullptr,
arrayPrototypeReduce,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::reduceRight),
nullptr,
arrayPrototypeReduceRight,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::reverse),
nullptr,
arrayPrototypeReverse,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::includes),
nullptr,
arrayPrototypeIncludes,
1);
defineMethod(
runtime,
cons,
Predefined::getSymbolID(Predefined::of),
nullptr,
arrayOf,
0);
defineMethod(
runtime,
cons,
Predefined::getSymbolID(Predefined::from),
nullptr,
arrayFrom,
1);
return cons;
}
CallResult<HermesValue>
arrayConstructor(void *, Runtime &runtime, NativeArgs args) {
MutableHandle<JSArray> selfHandle{runtime};
// If constructor, use the allocated object, otherwise allocate a new one.
// Everything else is the same after that.
if (args.isConstructorCall())
selfHandle = vmcast<JSArray>(args.getThisArg());
else {
auto arrRes = JSArray::create(runtime, 0, 0);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
selfHandle = arrRes->get();
}
// Possibility 1: new Array(number)
if (args.getArgCount() == 1 && args.getArg(0).isNumber()) {
double number = args.getArg(0).getNumber();
uint32_t len = truncateToUInt32(number);
if (len != number) {
return runtime.raiseRangeError("invalid array length");
}
auto st = JSArray::setLengthProperty(selfHandle, runtime, len);
(void)st;
assert(
st != ExecutionStatus::EXCEPTION && *st &&
"Cannot set length of a new array");
return selfHandle.getHermesValue();
}
// Possibility 2: new Array(elements...)
uint32_t len = args.getArgCount();
// Resize the array.
auto st = JSArray::setLengthProperty(selfHandle, runtime, len);
(void)st;
assert(
st != ExecutionStatus::EXCEPTION && *st &&
"Cannot set length of a new array");
// Initialize the elements.
uint32_t index = 0;
GCScopeMarkerRAII marker(runtime);
for (Handle<> arg : args.handles()) {
JSArray::setElementAt(selfHandle, runtime, index++, arg);
marker.flush();
}
return selfHandle.getHermesValue();
}
CallResult<HermesValue>
arrayIsArray(void *, Runtime &runtime, NativeArgs args) {
CallResult<bool> res = isArray(runtime, dyn_vmcast<JSObject>(args.getArg(0)));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
return HermesValue::encodeBoolValue(*res);
}
/// ES5.1 15.4.4.5.
CallResult<HermesValue>
arrayPrototypeToString(void *, Runtime &runtime, NativeArgs args) {
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto array = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
array, runtime, Predefined::getSymbolID(Predefined::join));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto func =
Handle<Callable>::dyn_vmcast(runtime.makeHandle(std::move(*propRes)));
if (!func) {
// If not callable, set func to be Object.prototype.toString.
return directObjectPrototypeToString(runtime, array);
}
return Callable::executeCall0(func, runtime, array).toCallResultHermesValue();
}
CallResult<HermesValue>
arrayPrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope{runtime};
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto array = runtime.makeHandle<JSObject>(objRes.getValue());
auto emptyString = runtime.getPredefinedStringHandle(Predefined::emptyString);
if (runtime.insertVisitedObject(*array))
return emptyString.getHermesValue();
auto cycleScope =
llvh::make_scope_exit([&] { runtime.removeVisitedObject(*array); });
auto propRes = JSObject::getNamed_RJS(
array, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toUInt32_RJS(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint32_t len = intRes->getNumber();
// TODO: Get a list-separator String for the host environment's locale.
// Use a comma as a separator for now, as JSC does.
const char16_t separator = u',';
// Final size of the result string. Initialize to account for the separators.
SafeUInt32 size(len - 1);
if (len == 0) {
return emptyString.getHermesValue();
}
// Array to store each of the strings of the elements.
auto arrRes = JSArray::create(runtime, len, len);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto strings = *arrRes;
// Index into the array.
MutableHandle<> i{runtime, HermesValue::encodeNumberValue(0)};
auto marker = gcScope.createMarker();
while (i->getNumber() < len) {
gcScope.flushToMarker(marker);
if (LLVM_UNLIKELY(
(propRes = JSObject::getComputed_RJS(array, runtime, i)) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto E = runtime.makeHandle(std::move(*propRes));
if (E->isUndefined() || E->isNull()) {
// Empty string for undefined or null element. No need to add to size.
JSArray::setElementAt(strings, runtime, i->getNumber(), emptyString);
} else {
if (LLVM_UNLIKELY(
(objRes = toObject(runtime, E)) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto elementObj = runtime.makeHandle<JSObject>(objRes.getValue());
// Retrieve the toLocaleString function.
if (LLVM_UNLIKELY(
(propRes = JSObject::getNamed_RJS(
elementObj,
runtime,
Predefined::getSymbolID(Predefined::toLocaleString))) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (auto func = Handle<Callable>::dyn_vmcast(
runtime.makeHandle(std::move(*propRes)))) {
// If ECMA 402 is implemented, it provides a superseding
// definition of Array.prototype.toLocaleString. The only
// difference between these two definitions is that in ECMA
// 402, two arguments (locales and options), if provided, are
// passed on from this function to the element's
// "toLocaleString" method.
auto callRes =
#ifdef HERMES_ENABLE_INTL
Callable::executeCall2(
func, runtime, elementObj, args.getArg(0), args.getArg(1));
#else
Callable::executeCall0(func, runtime, elementObj);
#endif
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto strRes =
toString_RJS(runtime, runtime.makeHandle(std::move(*callRes)));
if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto elementStr = runtime.makeHandle(std::move(*strRes));
uint32_t strLength = elementStr->getStringLength();
// Throw RangeError on overflow.
size.add(strLength);
if (LLVM_UNLIKELY(size.isOverflowed())) {
return runtime.raiseRangeError(
"resulting string length exceeds limit");
}
JSArray::setElementAt(strings, runtime, i->getNumber(), elementStr);
} else {
return runtime.raiseTypeError("toLocaleString() not callable");
}
}
i = HermesValue::encodeNumberValue(i->getNumber() + 1);
}
// Create and then populate the result string.
auto builder = StringBuilder::createStringBuilder(runtime, size);
if (builder == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
MutableHandle<StringPrimitive> element{runtime};
element = strings->at(runtime, 0).getString();
builder->appendStringPrim(element);
for (uint32_t j = 1; j < len; ++j) {
// Every element after the first needs a separator before it.
builder->appendCharacter(separator);
element = strings->at(runtime, j).getString();
builder->appendStringPrim(element);
}
return HermesValue::encodeStringValue(*builder->getStringPrimitive());
}
CallResult<HermesValue>
arrayPrototypeConcat(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
// Need a signed type here to account for uint32 and -1.
int64_t argCount = args.getArgCount();
// Precompute the final size of the array so it can be preallocated.
// Note this is necessarily an estimate because an accessor on one array
// may change the length of subsequent arrays.
uint64_t finalSizeEstimate = 0;
if (JSArray *arr = dyn_vmcast<JSArray>(O.get())) {
finalSizeEstimate += JSArray::getLength(arr, runtime);
} else {
++finalSizeEstimate;
}
for (int64_t i = 0; i < argCount; ++i) {
if (JSArray *arr = dyn_vmcast<JSArray>(args.getArg(i))) {
finalSizeEstimate += JSArray::getLength(arr, runtime);
} else {
++finalSizeEstimate;
}
}
// Resultant array.
auto arrRes = JSArray::create(runtime, finalSizeEstimate, finalSizeEstimate);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto A = *arrRes;
// Index to insert into A.
uint64_t n = 0;
// Temporary handle for an object.
MutableHandle<JSObject> objHandle{runtime};
// Temporary handle for an array.
MutableHandle<JSArray> arrHandle{runtime};
// Index to read from in the array that's being concatenated.
MutableHandle<> kHandle{runtime};
// Index to put into the resultant array.
MutableHandle<> nHandle{runtime};
// Temporary handle to use when holding intermediate elements.
MutableHandle<> tmpHandle{runtime};
// Used to find the object in the prototype chain that has index as property.
MutableHandle<JSObject> propObj{runtime};
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
auto marker = gcScope.createMarker();
ComputedPropertyDescriptor desc;
// Loop first through the "this" value and then through the arguments.
// If i == -1, use the "this" value, else use the ith argument.
tmpHandle = O.getHermesValue();
for (int64_t i = -1; i < argCount; ++i, tmpHandle = args.getArg(i)) {
CallResult<bool> spreadable = isConcatSpreadable(runtime, tmpHandle);
if (LLVM_UNLIKELY(spreadable == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (*spreadable) {
// 7.d. If spreadable is true, then
objHandle = vmcast<JSObject>(*tmpHandle);
arrHandle = dyn_vmcast<JSArray>(*tmpHandle);
uint64_t len;
if (LLVM_LIKELY(arrHandle)) {
// Fast path: E is an array.
len = JSArray::getLength(*arrHandle, runtime);
} else {
CallResult<PseudoHandle<>> propRes = JSObject::getNamed_RJS(
objHandle, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
tmpHandle = std::move(*propRes);
auto lengthRes = toLength(runtime, tmpHandle);
if (LLVM_UNLIKELY(lengthRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
len = lengthRes->getNumberAs<uint64_t>();
}
// 5.c.iii. If n + len > 2^53 - 1, throw a TypeError exception
if (LLVM_UNLIKELY(n + len > ((uint64_t)1 << 53) - 1)) {
return runtime.raiseTypeError(
"Array.prototype.concat result out of space");
}
// We know we are going to set elements in the range [n, n+len),
// regardless of any changes to 'arrHandle' (see ES5.1 15.4.4.4). Ensure
// we have capacity.
if (LLVM_UNLIKELY(n + len > A->getEndIndex()) &&
LLVM_LIKELY(n + len < UINT32_MAX)) {
// Only set the endIndex if it's going to be a valid length.
if (LLVM_UNLIKELY(
A->setStorageEndIndex(A, runtime, n + len) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
// Note that we must increase n every iteration even if nothing was
// appended to the result array.
// 5.c.iv. Repeat, while k < len
for (uint64_t k = 0; k < len; ++k, ++n) {
HermesValue subElement = LLVM_LIKELY(arrHandle)
? arrHandle->at(runtime, k)
: HermesValue::encodeEmptyValue();
if (LLVM_LIKELY(!subElement.isEmpty()) &&
LLVM_LIKELY(n < A->getEndIndex())) {
// Fast path: quickly set element without making any extra calls.
// Cast is safe because A->getEndIndex must be in uint32_t range.
JSArray::unsafeSetExistingElementAt(
A.get(), runtime, static_cast<uint32_t>(n), subElement);
} else {
// Slow path fallback if there's an empty slot in arr.
// We have to use getComputedPrimitiveDescriptor because the property
// may exist anywhere in the prototype chain.
kHandle = HermesValue::encodeDoubleValue(k);
JSObject::getComputedPrimitiveDescriptor(
objHandle, runtime, kHandle, propObj, tmpPropNameStorage, desc);
CallResult<PseudoHandle<>> propRes =
JSObject::getComputedPropertyValue_RJS(
objHandle,
runtime,
propObj,
tmpPropNameStorage,
desc,
kHandle);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
tmpHandle = std::move(*propRes);
nHandle = HermesValue::encodeDoubleValue(n);
if (LLVM_UNLIKELY(
JSArray::defineOwnComputedPrimitive(
A,
runtime,
nHandle,
DefinePropertyFlags::getDefaultNewPropertyFlags(),
tmpHandle) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
gcScope.flushToMarker(marker);
}
}
gcScope.flushToMarker(marker);
} else {
// 5.d.i. NOTE: E is added as a single item rather than spread.
// 5.d.ii. If n >= 2**53 - 1, throw a TypeError exception.
if (LLVM_UNLIKELY(n >= ((uint64_t)1 << 53) - 1)) {
return runtime.raiseTypeError(
"Array.prototype.concat result out of space");
}
// Otherwise, just put the value into the next slot.
if (LLVM_LIKELY(n < UINT32_MAX)) {
JSArray::setElementAt(A, runtime, n, tmpHandle);
} else {
nHandle = HermesValue::encodeDoubleValue(n);
auto cr = valueToSymbolID(runtime, nHandle);
if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_UNLIKELY(
JSArray::defineOwnProperty(
A,
runtime,
**cr,
DefinePropertyFlags::getDefaultNewPropertyFlags(),
tmpHandle) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
gcScope.flushToMarker(marker);
++n;
}
}
// Update the array's length. We never expect this to fail since we just
// created the array.
auto res = JSArray::setLengthProperty(A, runtime, n);
assert(
res == ExecutionStatus::RETURNED &&
"Setting length of new array should never fail");
(void)res;
return A.getHermesValue();
}
/// ES5.1 15.4.4.5.
CallResult<HermesValue>
arrayPrototypeJoin(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto emptyString = runtime.getPredefinedStringHandle(Predefined::emptyString);
if (runtime.insertVisitedObject(*O))
return emptyString.getHermesValue();
auto cycleScope =
llvh::make_scope_exit([&] { runtime.removeVisitedObject(*O); });
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
// Use comma for separator if the first argument is undefined.
auto separator = args.getArg(0).isUndefined()
? runtime.makeHandle(HermesValue::encodeStringValue(
runtime.getPredefinedString(Predefined::comma)))
: args.getArgHandle(0);
auto strRes = toString_RJS(runtime, separator);
if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto sep = runtime.makeHandle(std::move(*strRes));
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.
SafeUInt32 size;
// Storage for the strings for each element.
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;
}
auto strings = *arrRes;
// Call toString on all the elements of the array.
for (MutableHandle<> i{runtime, HermesValue::encodeNumberValue(0)};
i->getNumber() < len;
i = HermesValue::encodeNumberValue(i->getNumber() + 1)) {
// Add the size of the separator, except the first time.
if (i->getNumberAs<uint32_t>())
size.add(sep->getStringLength());
GCScope gcScope2(runtime);
if (LLVM_UNLIKELY(
(propRes = JSObject::getComputed_RJS(O, runtime, i)) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto elem = runtime.makeHandle(std::move(*propRes));
if (elem->isUndefined() || elem->isNull()) {
JSArray::setElementAt(strings, runtime, i->getNumber(), emptyString);
} else {
// Otherwise, call toString_RJS() and push the result, incrementing size.
auto strRes = toString_RJS(runtime, elem);
if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto S = runtime.makeHandle(std::move(*strRes));
size.add(S->getStringLength());
JSArray::setElementAt(strings, runtime, i->getNumber(), 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");
}
}
// Allocate the complete result.
auto builder = StringBuilder::createStringBuilder(runtime, size);
if (builder == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
MutableHandle<StringPrimitive> element{runtime};
element = strings->at(runtime, 0).getString();
builder->appendStringPrim(element);
for (size_t i = 1; i < len; ++i) {
builder->appendStringPrim(sep);
element = strings->at(runtime, i).getString();
builder->appendStringPrim(element);
}
return HermesValue::encodeStringValue(*builder->getStringPrimitive());
}
/// ES9.0 22.1.3.18.
CallResult<HermesValue>
arrayPrototypePush(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
// 1. Let O be ? ToObject(this value).
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
MutableHandle<> len{runtime};
// 2. Let len be ? ToLength(? Get(O, "length")).
Handle<JSArray> arr = Handle<JSArray>::dyn_vmcast(O);
if (LLVM_LIKELY(arr)) {
// Fast path for getting the length.
len =
HermesValue::encodeNumberValue(JSArray::getLength(arr.get(), runtime));
} else {
// Slow path, used when pushing onto non-array objects.
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lenRes = toLength(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
len = lenRes.getValue();
}
// 3. Let items be a List whose elements are, in left to right order, the
// arguments that were passed to this function invocation.
// 4. Let argCount be the number of elements in items.
uint32_t argCount = args.getArgCount();
// 5. If len + argCount > 2**53-1, throw a TypeError exception.
if (len->getNumber() + (double)argCount > std::pow(2.0, 53) - 1) {
return runtime.raiseTypeError("Array length exceeded in push()");
}
auto marker = gcScope.createMarker();
// 6. Repeat, while items is not empty
for (auto arg : args.handles()) {
// a. Remove the first element from items and let E be the value of the
// element.
// b. Perform ? Set(O, ! ToString(len), E, true).
// NOTE: If the prototype has an index-like non-writable property at
// index n, we have to fail to push.
// If the prototype has an index-like accessor at index n,
// then we have to attempt to call the setter.
// Must call putComputed because the array prototype could have values for
// keys that haven't been inserted into O yet.
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O, runtime, len, arg, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
gcScope.flushToMarker(marker);
// c. Let len be len+1.
len = HermesValue::encodeDoubleValue(len->getNumber() + 1);
}
// 7. Perform ? Set(O, "length", len, true).
if (LLVM_UNLIKELY(
JSObject::putNamed_RJS(
O,
runtime,
Predefined::getSymbolID(Predefined::length),
len,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 8. Return len.
return len.get();
}
namespace {
/// General object sorting model used by custom sorting routines.
/// Provides a model by which to less and swap elements, using the [[Get]],
/// [[Put]], and [[Delete]] internal methods of a supplied Object. Should be
/// allocated on the stack, because it creates its own internal GCScope, with
/// reusable MutableHandle<>-s that are used in the less and swap methods.
/// These allow for quick accesses without allocating a great number of new
/// handles every time we want to compare different elements.
/// Usage example:
/// StandardSortModel sm{runtime, obj, compareFn};
/// quickSort(sm, 0, length);
/// Note that this is generic and does nothing different if passed a JSArray.
class StandardSortModel : public SortModel {
private:
/// Runtime to sort in.
Runtime &runtime_;
/// Scope to allocate handles in, gets destroyed with this.
GCScope gcScope_;
/// JS comparison function, return -1 for less, 0 for equal, 1 for greater.
/// If null, then use the built in < operator.
Handle<Callable> compareFn_;
/// Object to sort elements [0, length).
Handle<JSObject> obj_;
/// Temporary handles for property name.
MutableHandle<SymbolID> aTmpNameStorage_;
MutableHandle<SymbolID> bTmpNameStorage_;
/// Preallocate handles in the current GCScope so that we don't have to make
/// new handles in every method call.
/// Handles for two indices.
MutableHandle<> aHandle_;
MutableHandle<> bHandle_;
/// Handles for the values at two indices.
MutableHandle<> aValue_;
MutableHandle<> bValue_;
/// Handles for the objects the values are retrieved from.
MutableHandle<JSObject> aDescObjHandle_;
MutableHandle<JSObject> bDescObjHandle_;
/// Marker created after initializing all fields so handles allocated later
/// can be flushed.
GCScope::Marker gcMarker_;
public:
StandardSortModel(
Runtime &runtime,
Handle<JSObject> obj,
Handle<Callable> compareFn)
: runtime_(runtime),
gcScope_(runtime),
compareFn_(compareFn),
obj_(obj),
aTmpNameStorage_(runtime),
bTmpNameStorage_(runtime),
aHandle_(runtime),
bHandle_(runtime),
aValue_(runtime),
bValue_(runtime),
aDescObjHandle_(runtime),
bDescObjHandle_(runtime),
gcMarker_(gcScope_.createMarker()) {}
/// Use getComputed and putComputed to swap the values at obj[a] and obj[b].
ExecutionStatus swap(uint32_t a, uint32_t b) override {
// Ensure that we don't leave here with any new handles.
GCScopeMarkerRAII gcMarker{gcScope_, gcMarker_};
aHandle_ = HermesValue::encodeDoubleValue(a);
bHandle_ = HermesValue::encodeDoubleValue(b);
ComputedPropertyDescriptor aDesc;
JSObject::getComputedPrimitiveDescriptor(
obj_, runtime_, aHandle_, aDescObjHandle_, aTmpNameStorage_, aDesc);
ComputedPropertyDescriptor bDesc;
JSObject::getComputedPrimitiveDescriptor(
obj_, runtime_, bHandle_, bDescObjHandle_, bTmpNameStorage_, bDesc);
if (aDescObjHandle_) {
if (LLVM_LIKELY(!aDesc.flags.proxyObject)) {
auto res = JSObject::getComputedPropertyValue_RJS(
obj_,
runtime_,
aDescObjHandle_,
aTmpNameStorage_,
aDesc,
aDescObjHandle_);
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*res)->isEmpty())) {
aValue_ = std::move(*res);
}
} else {
auto keyRes = toPropertyKey(runtime_, aHandle_);
if (keyRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
aHandle_ = keyRes->get();
CallResult<bool> hasPropRes = JSProxy::getOwnProperty(
aDescObjHandle_, runtime_, aHandle_, aDesc, nullptr);
if (hasPropRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
if (*hasPropRes) {
auto res =
JSProxy::getComputed(aDescObjHandle_, runtime_, aHandle_, obj_);
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
aValue_ = std::move(*res);
} else {
aDescObjHandle_ = nullptr;
}
}
}
if (bDescObjHandle_) {
if (LLVM_LIKELY(!bDesc.flags.proxyObject)) {
auto res = JSObject::getComputedPropertyValue_RJS(
obj_,
runtime_,
bDescObjHandle_,
bTmpNameStorage_,
bDesc,
bDescObjHandle_);
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*res)->isEmpty())) {
bValue_ = std::move(*res);
}
} else {
auto keyRes = toPropertyKey(runtime_, bHandle_);
if (keyRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
bHandle_ = keyRes->get();
CallResult<bool> hasPropRes = JSProxy::getOwnProperty(
bDescObjHandle_, runtime_, bHandle_, bDesc, nullptr);
if (hasPropRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
if (*hasPropRes) {
auto res =
JSProxy::getComputed(bDescObjHandle_, runtime_, bHandle_, obj_);
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
bValue_ = std::move(*res);
} else {
bDescObjHandle_ = nullptr;
}
}
}
if (bDescObjHandle_) {
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
obj_,
runtime_,
aHandle_,
bValue_,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else {
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
obj_, runtime_, aHandle_, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
if (aDescObjHandle_) {
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
obj_,
runtime_,
bHandle_,
aValue_,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else {
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
obj_, runtime_, bHandle_, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
return ExecutionStatus::RETURNED;
}
/// If compareFn isn't null, return compareFn(obj[a], obj[b])
/// If compareFn is null, return -1 if obj[a] < obj[b], 1 if obj[a] > obj[b],
/// 0 otherwise
CallResult<int> compare(uint32_t a, uint32_t b) override {
// Ensure that we don't leave here with any new handles.
GCScopeMarkerRAII gcMarker{gcScope_, gcMarker_};
aHandle_ = HermesValue::encodeDoubleValue(a);
bHandle_ = HermesValue::encodeDoubleValue(b);
ComputedPropertyDescriptor aDesc;
JSObject::getComputedPrimitiveDescriptor(
obj_, runtime_, aHandle_, aDescObjHandle_, aTmpNameStorage_, aDesc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
obj_, runtime_, aDescObjHandle_, aTmpNameStorage_, aDesc, aHandle_);
if (propRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
if ((*propRes)->isEmpty()) {
// Spec defines empty as greater than everything.
return 1;
}
aValue_ = std::move(*propRes);
assert(!aValue_->isEmpty());
ComputedPropertyDescriptor bDesc;
JSObject::getComputedPrimitiveDescriptor(
obj_, runtime_, bHandle_, bDescObjHandle_, bTmpNameStorage_, bDesc);
if ((propRes = JSObject::getComputedPropertyValue_RJS(
obj_,
runtime_,
bDescObjHandle_,
bTmpNameStorage_,
bDesc,
bHandle_)) == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
if ((*propRes)->isEmpty()) {
// Spec defines empty as greater than everything.
return -1;
}
bValue_ = std::move(*propRes);
assert(!bValue_->isEmpty());
if (aValue_->isUndefined()) {
// Spec defines undefined as greater than everything.
return 1;
}
if (bValue_->isUndefined()) {
// Spec defines undefined as greater than everything.
return -1;
}
if (compareFn_) {
// If we have a compareFn, just use that.
auto callRes = Callable::executeCall2(
compareFn_,
runtime_,
Runtime::getUndefinedValue(),
aValue_.get(),
bValue_.get());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes =
toNumber_RJS(runtime_, runtime_.makeHandle(std::move(*callRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// Cannot return intRes's value directly because it can be NaN
auto res = intRes->getNumber();
return (res < 0) ? -1 : (res > 0 ? 1 : 0);
} else {
// Convert both arguments to strings and compare
auto aValueRes = toString_RJS(runtime_, aValue_);
if (LLVM_UNLIKELY(aValueRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
aValue_ = aValueRes->getHermesValue();
auto bValueRes = toString_RJS(runtime_, bValue_);
if (LLVM_UNLIKELY(bValueRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
bValue_ = bValueRes->getHermesValue();
return aValue_->getString()->compare(bValue_->getString());
}
}
};
/// Perform a sort of a sparse object by querying its properties first.
/// It cannot be a proxy or a host object because they are not guaranteed to
/// be able to list their properties.
CallResult<HermesValue> sortSparse(
Runtime &runtime,
Handle<JSObject> O,
Handle<Callable> compareFn,
uint64_t len) {
GCScope gcScope{runtime};
assert(
!O->isHostObject() && !O->isProxyObject() &&
"only non-exotic objects can be sparsely sorted");
// This is a "non-fast" object, meaning we need to create a symbol for every
// property name. On the assumption that it is sparse, get all properties
// first, so that we only have to read the existing properties.
auto crNames = JSObject::getOwnPropertyNames(O, runtime, false);
if (crNames == ExecutionStatus::EXCEPTION)
return ExecutionStatus::EXCEPTION;
// Get the underlying storage containing the names.
auto names = runtime.makeHandle((*crNames)->getIndexedStorage(runtime));
if (!names) {
// Indexed storage can be null if there's nothing to store.
return O.getHermesValue();
}
// Find out how many sortable numeric properties we have.
JSArray::StorageType::size_type numProps = 0;
for (JSArray::StorageType::size_type e = names->size(); numProps != e;
++numProps) {
HermesValue hv = names->at(numProps);
// Stop at the first non-number.
if (!hv.isNumber())
break;
// Stop if the property name is beyond "len".
if (hv.getNumberAs<uint64_t>() >= len)
break;
}
// If we didn't find any numeric properties, there is nothing to do.
if (numProps == 0)
return O.getHermesValue();
// Create a new array which we will actually sort.
auto crArray = JSArray::create(runtime, numProps, numProps);
if (crArray == ExecutionStatus::EXCEPTION)
return ExecutionStatus::EXCEPTION;
auto array = *crArray;
if (JSArray::setStorageEndIndex(array, runtime, numProps) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
MutableHandle<> propName{runtime};
MutableHandle<> propVal{runtime};
GCScopeMarkerRAII gcMarker{gcScope};
// Copy all sortable properties into the array and delete them from the
// source. Deleting all sortable properties makes it easy to just copy the
// sorted result back in the end.
for (decltype(numProps) i = 0; i != numProps; ++i) {
gcMarker.flush();
propName = names->at(i);
auto res = JSObject::getComputed_RJS(O, runtime, propName);
if (res == ExecutionStatus::EXCEPTION)
return ExecutionStatus::EXCEPTION;
// Skip empty values.
if (res->getHermesValue().isEmpty())
continue;
JSArray::unsafeSetExistingElementAt(
*array, runtime, i, res->getHermesValue());
if (JSObject::deleteComputed(
O, runtime, propName, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
}
gcMarker.flush();
{
StandardSortModel sm(runtime, array, compareFn);
if (LLVM_UNLIKELY(
quickSort(&sm, 0u, numProps) == ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
}
// Time to copy back the values.
for (decltype(numProps) i = 0; i != numProps; ++i) {
gcMarker.flush();
auto hv = array->at(runtime, i);
assert(
!hv.isEmpty() &&
"empty values cannot appear in the array out of nowhere");
propVal = hv;
propName = HermesValue::encodeNumberValue(i);
if (JSObject::putComputed_RJS(
O, runtime, propName, propVal, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
}
return O.getHermesValue();
}
} // anonymous namespace
/// ES5.1 15.4.4.11.
CallResult<HermesValue>
arrayPrototypeSort(void *, Runtime &runtime, NativeArgs args) {
// Null if not a callable compareFn.
auto compareFn = Handle<Callable>::dyn_vmcast(args.getArgHandle(0));
if (!args.getArg(0).isUndefined() && !compareFn) {
return runtime.raiseTypeError("Array sort argument must be callable");
}
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
// If we are not sorting a regular dense array, use a special routine which
// first copies all properties into an array.
// Proxies and host objects however are excluded because they are weird.
if (!O->isProxyObject() && !O->isHostObject() && !O->hasFastIndexProperties())
return sortSparse(runtime, O, compareFn, len);
// This is the "fast" path. We are sorting an array with indexed storage.
StandardSortModel sm(runtime, O, compareFn);
// Use our custom sort routine. We can't use std::sort because it performs
// optimizations that allow it to bypass calls to std::swap, but our swap
// function is special, since it needs to use the internal Object functions.
if (LLVM_UNLIKELY(quickSort(&sm, 0u, len) == ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
return O.getHermesValue();
}
inline CallResult<HermesValue>
arrayPrototypeForEach(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
auto callbackFn = args.dyncastArg<Callable>(0);
if (!callbackFn) {
return runtime.raiseTypeError(
"Array.prototype.forEach() requires a callable argument");
}
// Index to execute the callback on.
MutableHandle<> k{runtime, HermesValue::encodeDoubleValue(0)};
MutableHandle<JSObject> descObjHandle{runtime};
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
// Loop through and execute the callback on all existing values.
// TODO: Implement a fast path for actual arrays.
auto marker = gcScope.createMarker();
while (k->getDouble() < len) {
gcScope.flushToMarker(marker);
ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, descObjHandle, tmpPropNameStorage, desc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
O, runtime, descObjHandle, tmpPropNameStorage, desc, k);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
auto kValue = std::move(*propRes);
if (LLVM_UNLIKELY(
Callable::executeCall3(
callbackFn,
runtime,
args.getArgHandle(1),
kValue.get(),
k.get(),
O.getHermesValue()) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
k = HermesValue::encodeDoubleValue(k->getDouble() + 1);
}
return HermesValue::encodeUndefinedValue();
}
/// ES10 22.1.3.10.1 FlattenIntoArray
/// mapperFunction may be null to signify its absence.
/// If mapperFunction is null, thisArg is ignored.
static CallResult<uint64_t> flattenIntoArray(
Runtime &runtime,
Handle<JSArray> target,
Handle<JSObject> source,
uint64_t sourceLen,
uint64_t start,
double depth,
Handle<Callable> mapperFunction,
Handle<> thisArg) {
ScopedNativeDepthTracker depthTracker{runtime};
if (LLVM_UNLIKELY(depthTracker.overflowed())) {
return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack);
}
if (!mapperFunction) {
assert(
thisArg->isUndefined() &&
"thisArg must be undefined if there is no mapper");
}
GCScope gcScope{runtime};
// 1. Let targetIndex be start.
uint64_t targetIndex = start;
// 2. Let sourceIndex be 0.
uint64_t sourceIndex = 0;
// Temporary storage for sourceIndex and targetIndex.
MutableHandle<> indexHandle{runtime};
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
MutableHandle<JSObject> propObj{runtime};
MutableHandle<> element{runtime};
MutableHandle<> lenResHandle{runtime};
auto marker = gcScope.createMarker();
// 3. Repeat, while sourceIndex < sourceLen
while (sourceIndex < sourceLen) {
gcScope.flushToMarker(marker);
// a. Let P be ! ToString(sourceIndex).
// b. Let exists be ? HasProperty(source, P).
ComputedPropertyDescriptor desc{};
indexHandle = HermesValue::encodeNumberValue(sourceIndex);
if (LLVM_UNLIKELY(
JSObject::getComputedDescriptor(
source,
runtime,
indexHandle,
propObj,
tmpPropNameStorage,
desc) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// c. If exists is true, then
// i. Let element be ? Get(source, P).
CallResult<PseudoHandle<>> elementRes =
JSObject::getComputedPropertyValue_RJS(
source, runtime, propObj, tmpPropNameStorage, desc, indexHandle);
if (LLVM_UNLIKELY(elementRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*elementRes)->isEmpty())) {
element = std::move(*elementRes);
// ii. If mapperFunction is present, then
if (mapperFunction) {
// 1. Assert: thisArg is present.
assert(!thisArg->isEmpty() && "mapperFunction requires a thisArg");
// 2. Set element to ? Call(mapperFunction, thisArg , « element,
// sourceIndex, source »).
elementRes = Callable::executeCall3(
mapperFunction,
runtime,
thisArg,
element.getHermesValue(),
HermesValue::encodeNumberValue(sourceIndex),
source.getHermesValue());
if (LLVM_UNLIKELY(elementRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
element = std::move(*elementRes);
}
// iii. Let shouldFlatten be false.
bool shouldFlatten = false;
if (depth > 0) {
// iv. If depth > 0, then
// 1. Set shouldFlatten to ? IsArray(element).
// NOTE: isArray accepts nullptr for the obj argument.
CallResult<bool> shouldFlattenRes =
isArray(runtime, dyn_vmcast<JSObject>(element.get()));
if (LLVM_UNLIKELY(shouldFlattenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
shouldFlatten = *shouldFlattenRes;
}
if (shouldFlatten) {
// It is valid to cast `element` to JSObject because shouldFlatten is
// only true when `isArray(element)` is true.
// v. If shouldFlatten is true, then
// 1. Let elementLen be ? ToLength(? Get(element, "length")).
CallResult<PseudoHandle<>> lenRes = JSObject::getNamed_RJS(
Handle<JSObject>::vmcast(element),
runtime,
Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
lenResHandle = std::move(*lenRes);
CallResult<uint64_t> elementLenRes = toLengthU64(runtime, lenResHandle);
if (LLVM_UNLIKELY(elementLenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t elementLen = *elementLenRes;
// 2. Set targetIndex to ? FlattenIntoArray(target, element, elementLen,
// targetIndex, depth - 1).
CallResult<uint64_t> targetIndexRes = flattenIntoArray(
runtime,
target,
Handle<JSObject>::vmcast(element),
elementLen,
targetIndex,
depth - 1,
runtime.makeNullHandle<Callable>(),
runtime.getUndefinedValue());
if (LLVM_UNLIKELY(targetIndexRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
targetIndex = *targetIndexRes;
} else {
// vi. Else,
// 1. If targetIndex ≥ 2**53-1, throw a TypeError exception.
if (targetIndex >= ((uint64_t)1 << 53) - 1) {
return runtime.raiseTypeError("flattened array exceeds length limit");
}
// 2. Perform ? CreateDataPropertyOrThrow(
// target, !ToString(targetIndex), element).
indexHandle = HermesValue::encodeNumberValue(targetIndex);
if (LLVM_UNLIKELY(
JSObject::defineOwnComputed(
target,
runtime,
indexHandle,
DefinePropertyFlags::getDefaultNewPropertyFlags(),
element,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 3. Increase targetIndex by 1.
++targetIndex;
}
}
// d. Increase sourceIndex by 1.
++sourceIndex;
}
// 4. Return targetIndex.
return targetIndex;
}
CallResult<HermesValue>
arrayPrototypeFlat(void *ctx, Runtime &runtime, NativeArgs args) {
// 1. Let O be ? ToObject(this value).
CallResult<HermesValue> ORes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(ORes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(*ORes);
// 2. Let sourceLen be ? ToLength(? Get(O, "length")).
CallResult<PseudoHandle<>> lenRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
CallResult<uint64_t> sourceLenRes =
toLengthU64(runtime, runtime.makeHandle(std::move(*lenRes)));
if (LLVM_UNLIKELY(sourceLenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t sourceLen = *sourceLenRes;
// 3. Let depthNum be 1.
double depthNum = 1;
if (!args.getArg(0).isUndefined()) {
// 4. If depth is not undefined, then
// a. Set depthNum to ? ToIntegerOrInfinity(depth).
auto depthNumRes = toIntegerOrInfinity(runtime, args.getArgHandle(0));
if (LLVM_UNLIKELY(depthNumRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
depthNum = depthNumRes->getNumber();
}
// 5. Let A be ? ArraySpeciesCreate(O, 0).
auto ARes = JSArray::create(runtime, 0, 0);
if (LLVM_UNLIKELY(ARes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto A = *ARes;
// 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
if (LLVM_UNLIKELY(
flattenIntoArray(
runtime,
A,
O,
sourceLen,
0,
depthNum,
runtime.makeNullHandle<Callable>(),
runtime.getUndefinedValue()) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 7. Return A.
return A.getHermesValue();
}
CallResult<HermesValue>
arrayPrototypeFlatMap(void *ctx, Runtime &runtime, NativeArgs args) {
// 1. Let O be ? ToObject(this value).
CallResult<HermesValue> ORes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(ORes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(*ORes);
// 2. Let sourceLen be ? ToLength(? Get(O, "length")).
CallResult<PseudoHandle<>> lenRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
CallResult<uint64_t> sourceLenRes =
toLengthU64(runtime, runtime.makeHandle(std::move(*lenRes)));
if (LLVM_UNLIKELY(sourceLenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t sourceLen = *sourceLenRes;
// 3. If IsCallable(mapperFunction) is false, throw a TypeError exception.
Handle<Callable> mapperFunction = args.dyncastArg<Callable>(0);
if (!mapperFunction) {
return runtime.raiseTypeError("flatMap mapper must be callable");
}
// 4. If thisArg is present, let T be thisArg; else let T be undefined.
auto T = args.getArgHandle(1);
// 5. Let A be ? ArraySpeciesCreate(O, 0).
auto ARes = JSArray::create(runtime, 0, 0);
if (LLVM_UNLIKELY(ARes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto A = *ARes;
// 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, T).
if (LLVM_UNLIKELY(
flattenIntoArray(runtime, A, O, sourceLen, 0, 1, mapperFunction, T) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 7. Return A.
return A.getHermesValue();
}
CallResult<HermesValue>
arrayPrototypeIterator(void *ctx, Runtime &runtime, NativeArgs args) {
IterationKind kind = *reinterpret_cast<IterationKind *>(&ctx);
assert(
kind < IterationKind::NumKinds &&
"arrayPrototypeIterator with wrong kind");
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto obj = runtime.makeHandle<JSObject>(*objRes);
return JSArrayIterator::create(runtime, obj, kind).getHermesValue();
}
CallResult<HermesValue>
arrayPrototypeSlice(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lenRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *lenRes;
auto intRes = toIntegerOrInfinity(runtime, args.getArgHandle(0));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// Start index. If negative, then offset from the right side of the array.
double relativeStart = intRes->getNumber();
// Index that we're currently copying from.
// Starts at the actual start value, computed from relativeStart.
MutableHandle<> k{
runtime,
HermesValue::encodeDoubleValue(
relativeStart < 0 ? std::max(len + relativeStart, 0.0)
: std::min(relativeStart, len))};
// End index. If negative, then offset from the right side of the array.
double relativeEnd;
if (args.getArg(1).isUndefined()) {
relativeEnd = len;
} else {
if (LLVM_UNLIKELY(
(intRes = toIntegerOrInfinity(runtime, args.getArgHandle(1))) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
relativeEnd = intRes->getNumber();
}
// Actual end index.
double fin = relativeEnd < 0 ? std::max(len + relativeEnd, 0.0)
: std::min(relativeEnd, len);
// Create the result array.
double count = std::max(fin - k->getNumber(), 0.0);
if (LLVM_UNLIKELY(count > JSArray::StorageType::maxElements())) {
return runtime.raiseRangeError("Out of memory for array elements.");
}
auto arrRes = JSArray::create(runtime, count, count);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto A = *arrRes;
// Next index in A to write to.
uint32_t n = 0;
MutableHandle<JSObject> descObjHandle{runtime};
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
MutableHandle<> kValue{runtime};
auto marker = gcScope.createMarker();
// Copy the elements between the actual start and end indices into A.
// TODO: Implement a fast path for actual arrays.
while (k->getNumber() < fin) {
ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, descObjHandle, tmpPropNameStorage, desc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
O, runtime, descObjHandle, tmpPropNameStorage, desc, k);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
kValue = std::move(*propRes);
JSArray::setElementAt(A, runtime, n, kValue);
}
k = HermesValue::encodeDoubleValue(k->getNumber() + 1);
++n;
gcScope.flushToMarker(marker);
}
if (LLVM_UNLIKELY(
JSArray::setLengthProperty(A, runtime, n) ==
ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
return A.getHermesValue();
}
CallResult<HermesValue>
arrayPrototypeSplice(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lenRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *lenRes;
auto intRes = toIntegerOrInfinity(runtime, args.getArgHandle(0));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double relativeStart = intRes->getNumber();
// Index to start the deletion/insertion at.
double actualStart = relativeStart < 0 ? std::max(len + relativeStart, 0.0)
: std::min(relativeStart, len);
// Implement the newer calculation of actualDeleteCount (ES6.0),
// since 5.1 doesn't define behavior for less than 2 arguments.
uint32_t argCount = args.getArgCount();
uint64_t actualDeleteCount;
uint64_t insertCount;
switch (argCount) {
case 0:
insertCount = 0;
actualDeleteCount = 0;
break;
case 1:
// If just one argument specified, delete everything until the end.
insertCount = 0;
actualDeleteCount = len - actualStart;
break;
default:
// Otherwise, use the specified delete count.
if (LLVM_UNLIKELY(
(intRes = toIntegerOrInfinity(runtime, args.getArgHandle(1))) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
insertCount = argCount - 2;
actualDeleteCount =
std::min(std::max(intRes->getNumber(), 0.0), len - actualStart);
}
// If len+insertCount−actualDeleteCount > 2^53-1, throw a TypeError exception.
// Checks for overflow as well.
auto lenAfterInsert = len + insertCount;
if (LLVM_UNLIKELY(
lenAfterInsert < len ||
lenAfterInsert - actualDeleteCount > (1LLU << 53) - 1)) {
return runtime.raiseTypeError("Array.prototype.splice result out of space");
}
// Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
if (LLVM_UNLIKELY(actualDeleteCount > JSArray::StorageType::maxElements())) {
return runtime.raiseRangeError("Out of memory for array elements.");
}
auto arrRes = JSArray::create(runtime, actualDeleteCount, actualDeleteCount);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto A = *arrRes;
// Indices used for various copies in loops below.
MutableHandle<> from{runtime};
MutableHandle<> to{runtime};
// Value storage used for copying values.
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
MutableHandle<JSObject> fromDescObjHandle{runtime};
MutableHandle<> fromValue{runtime};
MutableHandle<> i{runtime};
MutableHandle<> k{runtime};
auto gcMarker = gcScope.createMarker();
{
// Copy actualDeleteCount elements to A, starting at actualStart.
// TODO: Add a fast path for actual arrays.
for (uint32_t j = 0; j < actualDeleteCount; ++j) {
from = HermesValue::encodeDoubleValue(actualStart + j);
ComputedPropertyDescriptor fromDesc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, from, fromDescObjHandle, tmpPropNameStorage, fromDesc);
CallResult<PseudoHandle<>> propRes =
JSObject::getComputedPropertyValue_RJS(
O,
runtime,
fromDescObjHandle,
tmpPropNameStorage,
fromDesc,
from);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
fromValue = std::move(*propRes);
JSArray::setElementAt(A, runtime, j, fromValue);
}
gcScope.flushToMarker(gcMarker);
}
if (LLVM_UNLIKELY(
JSArray::setLengthProperty(A, runtime, actualDeleteCount) ==
ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
}
// Perform ? Set(A, "length", actualDeleteCount, true).
if (LLVM_UNLIKELY(
JSObject::putNamed_RJS(
A,
runtime,
Predefined::getSymbolID(Predefined::length),
runtime.makeHandle(
HermesValue::encodeNumberValue(actualDeleteCount)),
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// Number of new items to add to the array.
uint32_t itemCount = args.getArgCount() > 2 ? args.getArgCount() - 2 : 0;
if (itemCount < actualDeleteCount) {
// Inserting less items than deleting.
// Copy items from (k + actualDeleteCount) to (k + itemCount).
// This leaves itemCount spaces to copy the arguments into.
// TODO: Add a fast path for actual arrays.
for (double j = actualStart; j < len - actualDeleteCount; ++j) {
from = HermesValue::encodeDoubleValue(j + actualDeleteCount);
to = HermesValue::encodeDoubleValue(j + itemCount);
ComputedPropertyDescriptor fromDesc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, from, fromDescObjHandle, tmpPropNameStorage, fromDesc);
CallResult<PseudoHandle<>> propRes =
JSObject::getComputedPropertyValue_RJS(
O,
runtime,
fromDescObjHandle,
tmpPropNameStorage,
fromDesc,
from);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
fromValue = std::move(*propRes);
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
to,
fromValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else {
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, to, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
gcScope.flushToMarker(gcMarker);
}
// Use i here to refer to (k-1) in the spec, and reindex the loop.
i = HermesValue::encodeDoubleValue(len - 1);
// Delete the remaining elements from the right that we didn't copy into.
// TODO: Add a fast path for actual arrays.
while (i->getNumber() > len - actualDeleteCount + itemCount - 1) {
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, i, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
i = HermesValue::encodeDoubleValue(i->getDouble() - 1);
gcScope.flushToMarker(gcMarker);
}
} else if (itemCount > actualDeleteCount) {
// Inserting more items than deleting.
// Start from the right, and copy elements to the right.
// This makes space to insert the elements from the arguments.
// TODO: Add a fast path for actual arrays.
for (double j = len - actualDeleteCount; j > actualStart; --j) {
from = HermesValue::encodeDoubleValue(j + actualDeleteCount - 1);
to = HermesValue::encodeDoubleValue(j + itemCount - 1);
ComputedPropertyDescriptor fromDesc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, from, fromDescObjHandle, tmpPropNameStorage, fromDesc);
CallResult<PseudoHandle<>> propRes =
JSObject::getComputedPropertyValue_RJS(
O,
runtime,
fromDescObjHandle,
tmpPropNameStorage,
fromDesc,
from);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
fromValue = std::move(*propRes);
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
to,
fromValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else {
// fromPresent is false
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, to, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
gcScope.flushToMarker(gcMarker);
}
}
{
// Finally, just copy the elements from the args into the array.
// TODO: Add a fast path for actual arrays.
k = HermesValue::encodeDoubleValue(actualStart);
for (size_t j = 2; j < argCount; ++j) {
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
k,
args.getArgHandle(j),
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
k = HermesValue::encodeDoubleValue(k->getDouble() + 1);
gcScope.flushToMarker(gcMarker);
}
}
if (LLVM_UNLIKELY(
JSObject::putNamed_RJS(
O,
runtime,
Predefined::getSymbolID(Predefined::length),
runtime.makeHandle(HermesValue::encodeDoubleValue(
len - actualDeleteCount + itemCount)),
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
return A.getHermesValue();
}
CallResult<HermesValue>
arrayPrototypeCopyWithin(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope{runtime};
// 1. Let O be ToObject(this value).
// 2. ReturnIfAbrupt(O).
auto oRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(oRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(*oRes);
// 3. Let len be ToLength(Get(O, "length")).
// 4. ReturnIfAbrupt(len).
// Use doubles for all lengths and indices to allow for proper Infinity
// handling, because ToInteger may return Infinity and we must do double
// arithmetic.
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lenRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *lenRes;
// 5. Let relativeTarget be ToIntegerOrInfinity(target).
// 6. ReturnIfAbrupt(relativeTarget).
auto relativeTargetRes = toIntegerOrInfinity(runtime, args.getArgHandle(0));
if (LLVM_UNLIKELY(relativeTargetRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double relativeTarget = relativeTargetRes->getNumber();
// 7. If relativeTarget < 0, let to be max((len + relativeTarget),0); else let
// to be min(relativeTarget, len).
double to = relativeTarget < 0 ? std::max((len + relativeTarget), (double)0)
: std::min(relativeTarget, len);
// 8. Let relativeStart be ToIntegerOrInfinity(start).
// 9. ReturnIfAbrupt(relativeStart).
auto relativeStartRes = toIntegerOrInfinity(runtime, args.getArgHandle(1));
if (LLVM_UNLIKELY(relativeStartRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double relativeStart = relativeStartRes->getNumber();
// 10. If relativeStart < 0, let from be max((len + relativeStart),0); else
// let from be min(relativeStart, len).
double from = relativeStart < 0 ? std::max((len + relativeStart), (double)0)
: std::min(relativeStart, len);
// 11. If end is undefined, let relativeEnd be len; else let relativeEnd be
// ToIntegerOrInfinity(end).
// 12. ReturnIfAbrupt(relativeEnd).
double relativeEnd;
if (args.getArg(2).isUndefined()) {
relativeEnd = len;
} else {
auto relativeEndRes = toIntegerOrInfinity(runtime, args.getArgHandle(2));
if (LLVM_UNLIKELY(relativeEndRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
relativeEnd = relativeEndRes->getNumber();
}
// 13. If relativeEnd < 0, let final be max((len + relativeEnd),0); else let
// final be min(relativeEnd, len).
double fin = relativeEnd < 0 ? std::max((len + relativeEnd), (double)0)
: std::min(relativeEnd, len);
// 14. Let count be min(final-from, len-to).
double count = std::min(fin - from, len - to);
int direction;
if (from < to && to < from + count) {
// 15. If from<to and to<from+count
// a. Let direction be -1.
direction = -1;
// b. Let from be from + count -1.
from = from + count - 1;
// c. Let to be to + count -1.
to = to + count - 1;
} else {
// 16. Else,
// a. Let direction = 1.
direction = 1;
}
MutableHandle<> fromHandle{runtime, HermesValue::encodeNumberValue(from)};
MutableHandle<> toHandle{runtime, HermesValue::encodeNumberValue(to)};
MutableHandle<SymbolID> fromNameTmpStorage{runtime};
MutableHandle<JSObject> fromObj{runtime};
MutableHandle<> fromVal{runtime};
GCScopeMarkerRAII marker{gcScope};
for (; count > 0; marker.flush()) {
// 17. Repeat, while count > 0
// a. Let fromKey be ToString(from).
// b. Let toKey be ToString(to).
// c. Let fromPresent be HasProperty(O, fromKey).
// d. ReturnIfAbrupt(fromPresent).
ComputedPropertyDescriptor fromDesc;
if (LLVM_UNLIKELY(
JSObject::getComputedDescriptor(
O,
runtime,
fromHandle,
fromObj,
fromNameTmpStorage,
fromDesc) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
CallResult<PseudoHandle<>> fromValRes =
JSObject::getComputedPropertyValue_RJS(
O, runtime, fromObj, fromNameTmpStorage, fromDesc, fromHandle);
if (LLVM_UNLIKELY(fromValRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// e. If fromPresent is true, then
if (LLVM_LIKELY(!(*fromValRes)->isEmpty())) {
// i. Let fromVal be Get(O, fromKey).
// ii. ReturnIfAbrupt(fromVal).
fromVal = std::move(*fromValRes);
// iii. Let setStatus be Set(O, toKey, fromVal, true).
// iv. ReturnIfAbrupt(setStatus).
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
toHandle,
fromVal,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else {
// f. Else fromPresent is false,
// i. Let deleteStatus be DeletePropertyOrThrow(O, toKey).
// ii. ReturnIfAbrupt(deleteStatus).
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, toHandle, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
// g. Let from be from + direction.
fromHandle =
HermesValue::encodeNumberValue(fromHandle->getNumber() + direction);
// h. Let to be to + direction.
toHandle =
HermesValue::encodeNumberValue(toHandle->getNumber() + direction);
// i. Let count be count − 1.
--count;
}
// 18. Return O.
return O.getHermesValue();
}
CallResult<HermesValue>
arrayPrototypePop(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto res = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(res.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
if (len == 0) {
if (LLVM_UNLIKELY(
JSObject::putNamed_RJS(
O,
runtime,
Predefined::getSymbolID(Predefined::length),
runtime.makeHandle(HermesValue::encodeDoubleValue(0)),
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
return HermesValue::encodeUndefinedValue();
}
auto idxVal = runtime.makeHandle(HermesValue::encodeDoubleValue(len - 1));
if (LLVM_UNLIKELY(
(propRes = JSObject::getComputed_RJS(O, runtime, idxVal)) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto element = runtime.makeHandle(std::move(*propRes));
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, idxVal, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_UNLIKELY(
JSObject::putNamed_RJS(
O,
runtime,
Predefined::getSymbolID(Predefined::length),
runtime.makeHandle(HermesValue::encodeDoubleValue(len - 1)),
PropOpFlags().plusThrowOnError()) == ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
return element.get();
}
CallResult<HermesValue>
arrayPrototypeShift(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
if (len == 0) {
// Need to set length to 0 per spec.
if (JSObject::putNamed_RJS(
O,
runtime,
Predefined::getSymbolID(Predefined::length),
runtime.makeHandle(HermesValue::encodeDoubleValue(0)),
PropOpFlags().plusThrowOnError()) == ExecutionStatus::EXCEPTION)
return ExecutionStatus::EXCEPTION;
return HermesValue::encodeUndefinedValue();
}
auto idxVal = runtime.makeHandle(HermesValue::encodeDoubleValue(0));
if (LLVM_UNLIKELY(
(propRes = JSObject::getComputed_RJS(O, runtime, idxVal)) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto first = runtime.makeHandle(std::move(*propRes));
MutableHandle<> from{runtime, HermesValue::encodeDoubleValue(1)};
MutableHandle<> to{runtime};
MutableHandle<SymbolID> fromNameTmpStorage{runtime};
MutableHandle<JSObject> fromDescObjHandle{runtime};
MutableHandle<> fromVal{runtime};
// Move every element to the left one slot.
// TODO: Add a fast path for actual arrays.
while (from->getDouble() < len) {
GCScopeMarkerRAII marker{gcScope};
// Moving an element from "from" to "from - 1".
to = HermesValue::encodeDoubleValue(from->getDouble() - 1);
ComputedPropertyDescriptor fromDesc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, from, fromDescObjHandle, fromNameTmpStorage, fromDesc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
O, runtime, fromDescObjHandle, fromNameTmpStorage, fromDesc, from);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
// fromPresent is true, so read fromVal and set the "to" index.
fromVal = std::move(*propRes);
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O, runtime, to, fromVal, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else {
// fromVal is not present so move the empty slot to the left.
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, to, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
from = HermesValue::encodeDoubleValue(from->getDouble() + 1);
}
// Delete last element of the array.
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O,
runtime,
runtime.makeHandle(HermesValue::encodeDoubleValue(len - 1)),
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// Decrement length.
if (LLVM_UNLIKELY(
JSObject::putNamed_RJS(
O,
runtime,
Predefined::getSymbolID(Predefined::length),
runtime.makeHandle(HermesValue::encodeDoubleValue(len - 1)),
PropOpFlags().plusThrowOnError()) == ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
return first.get();
}
/// Used to help with indexOf and lastIndexOf.
/// \p reverse true if searching in reverse (lastIndexOf), false otherwise.
static inline CallResult<HermesValue>
indexOfHelper(Runtime &runtime, NativeArgs args, const bool reverse) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lenRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *lenRes;
// Early return before running into any coercions on args.
// 2. Let len be ? LengthOfArrayLike(O).
// 3. If len is 0, return -1.
if (len == 0) {
return HermesValue::encodeDoubleValue(-1);
}
// Relative index to start the search at.
auto intRes = toIntegerOrInfinity(runtime, args.getArgHandle(1));
double n;
if (args.getArgCount() > 1) {
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
n = intRes->getNumber();
if (LLVM_UNLIKELY(n == 0)) {
// To handle the special case when n is -0, we need to make sure it's 0.
n = 0;
}
} else {
n = !reverse ? 0 : len - 1;
}
// Actual index to start the search at.
MutableHandle<> k{runtime};
if (!reverse) {
if (n >= 0) {
k = HermesValue::encodeDoubleValue(n);
} else {
// If len - abs(n) < 0, set k=0. Otherwise set k = len - abs(n).
k = HermesValue::encodeDoubleValue(std::max(len - std::abs(n), 0.0));
}
} else {
if (n >= 0) {
k = HermesValue::encodeDoubleValue(std::min(n, len - 1));
} else {
k = HermesValue::encodeDoubleValue(len - std::abs(n));
}
}
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
MutableHandle<JSObject> descObjHandle{runtime};
// Search for the element.
auto searchElement = args.getArgHandle(0);
auto marker = gcScope.createMarker();
while (true) {
gcScope.flushToMarker(marker);
// Check that we're not done yet.
if (!reverse) {
if (k->getDouble() >= len) {
break;
}
} else {
if (k->getDouble() < 0) {
break;
}
}
ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, descObjHandle, tmpPropNameStorage, desc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
O, runtime, descObjHandle, tmpPropNameStorage, desc, k);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (!(*propRes)->isEmpty() &&
strictEqualityTest(searchElement.get(), propRes->get())) {
return k.get();
}
// Update the index based on the direction of the search.
k = HermesValue::encodeDoubleValue(k->getDouble() + (reverse ? -1 : 1));
}
// Not found, return -1.
return HermesValue::encodeDoubleValue(-1);
}
CallResult<HermesValue>
arrayPrototypeUnshift(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
size_t argCount = args.getArgCount();
// 4. If argCount > 0, then
if (argCount > 0) {
// If len+ argCount > (2 ^ 53) -1, throw a TypeError exception.
if (LLVM_UNLIKELY(len + argCount >= ((uint64_t)1 << 53) - 1)) {
return runtime.raiseTypeError(
"Array.prototype.unshift result out of space");
}
// Loop indices.
MutableHandle<> k{runtime, HermesValue::encodeDoubleValue(len)};
MutableHandle<> j{runtime, HermesValue::encodeDoubleValue(0)};
// Indices to copy from/to when shifting.
MutableHandle<> from{runtime};
MutableHandle<> to{runtime};
// Value that is being copied.
MutableHandle<SymbolID> fromNameTmpStorage{runtime};
MutableHandle<JSObject> fromDescObjHandle{runtime};
MutableHandle<> fromValue{runtime};
// Move elements to the right by argCount to account for the new elements.
// TODO: Add a fast path for actual arrays.
auto marker = gcScope.createMarker();
while (k->getDouble() > 0) {
gcScope.flushToMarker(marker);
from = HermesValue::encodeDoubleValue(k->getDouble() - 1);
to = HermesValue::encodeDoubleValue(k->getDouble() + argCount - 1);
ComputedPropertyDescriptor fromDesc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, from, fromDescObjHandle, fromNameTmpStorage, fromDesc);
CallResult<PseudoHandle<>> propRes =
JSObject::getComputedPropertyValue_RJS(
O,
runtime,
fromDescObjHandle,
fromNameTmpStorage,
fromDesc,
from);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
fromValue = std::move(*propRes);
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
to,
fromValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else {
// Shift the empty slot by deleting at the destination.
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, to, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
k = HermesValue::encodeDoubleValue(k->getDouble() - 1);
}
// Put the arguments into the beginning of the array.
for (auto arg : args.handles()) {
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O, runtime, j, arg, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
gcScope.flushToMarker(marker);
j = HermesValue::encodeDoubleValue(j->getDouble() + 1);
}
}
// Increment length by argCount.
auto newLen = HermesValue::encodeDoubleValue(len + argCount);
if (LLVM_UNLIKELY(
JSObject::putNamed_RJS(
O,
runtime,
Predefined::getSymbolID(Predefined::length),
runtime.makeHandle(newLen),
PropOpFlags().plusThrowOnError()) == ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
return newLen;
}
CallResult<HermesValue>
arrayPrototypeIndexOf(void *, Runtime &runtime, NativeArgs args) {
return indexOfHelper(runtime, args, false);
}
CallResult<HermesValue>
arrayPrototypeLastIndexOf(void *, Runtime &runtime, NativeArgs args) {
return indexOfHelper(runtime, args, true);
}
/// Helper function for every/some.
/// \param every true if calling every(), false if calling some().
static inline CallResult<HermesValue>
everySomeHelper(Runtime &runtime, NativeArgs args, const bool every) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
auto callbackFn = args.dyncastArg<Callable>(0);
if (!callbackFn) {
return runtime.raiseTypeError(
"Array.prototype.every() requires a callable argument");
}
// Index to check the callback on.
MutableHandle<> k{runtime, HermesValue::encodeDoubleValue(0)};
// Value at index k;
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
MutableHandle<JSObject> descObjHandle{runtime};
MutableHandle<> kValue{runtime};
// Loop through and run the callback.
auto marker = gcScope.createMarker();
while (k->getDouble() < len) {
gcScope.flushToMarker(marker);
ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, descObjHandle, tmpPropNameStorage, desc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
O, runtime, descObjHandle, tmpPropNameStorage, desc, k);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
// kPresent is true, call the callback on the kth element.
kValue = std::move(*propRes);
auto callRes = Callable::executeCall3(
callbackFn,
runtime,
args.getArgHandle(1),
kValue.get(),
k.get(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto testResult = std::move(*callRes);
if (every) {
// Done if one is false.
if (!toBoolean(testResult.get())) {
return HermesValue::encodeBoolValue(false);
}
} else {
// Done if one is true.
if (toBoolean(testResult.get())) {
return HermesValue::encodeBoolValue(true);
}
}
}
k = HermesValue::encodeDoubleValue(k->getDouble() + 1);
}
// If we're looking for every, then we finished without returning true.
// If we're looking for some, then we finished without returning false.
return HermesValue::encodeBoolValue(every);
}
CallResult<HermesValue>
arrayPrototypeEvery(void *, Runtime &runtime, NativeArgs args) {
return everySomeHelper(runtime, args, true);
}
CallResult<HermesValue>
arrayPrototypeSome(void *, Runtime &runtime, NativeArgs args) {
return everySomeHelper(runtime, args, false);
}
CallResult<HermesValue>
arrayPrototypeMap(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
auto callbackFn = args.dyncastArg<Callable>(0);
if (!callbackFn) {
return runtime.raiseTypeError(
"Array.prototype.map() requires a callable argument");
}
// Resultant array.
if (LLVM_UNLIKELY(len > JSArray::StorageType::maxElements())) {
return runtime.raiseRangeError("Out of memory for array elements.");
}
auto arrRes = JSArray::create(runtime, len, len);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto A = *arrRes;
// Current index to execute callback on.
MutableHandle<> k{runtime, HermesValue::encodeDoubleValue(0)};
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
MutableHandle<JSObject> descObjHandle{runtime};
// Main loop to execute callback and store the results in A.
// TODO: Implement a fast path for actual arrays.
auto marker = gcScope.createMarker();
while (k->getDouble() < len) {
gcScope.flushToMarker(marker);
ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, descObjHandle, tmpPropNameStorage, desc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
O, runtime, descObjHandle, tmpPropNameStorage, desc, k);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
// kPresent is true, execute callback and store result in A[k].
auto kValue = std::move(*propRes);
auto callRes = Callable::executeCall3(
callbackFn,
runtime,
args.getArgHandle(1),
kValue.get(),
k.get(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
JSArray::setElementAt(
A, runtime, k->getDouble(), runtime.makeHandle(std::move(*callRes)));
}
k = HermesValue::encodeDoubleValue(k->getDouble() + 1);
}
return A.getHermesValue();
}
CallResult<HermesValue>
arrayPrototypeFilter(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;
auto callbackFn = args.dyncastArg<Callable>(0);
if (!callbackFn) {
return runtime.raiseTypeError(
"Array.prototype.filter() requires a callable argument");
}
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;
}
auto A = *arrRes;
// Index in the original array.
MutableHandle<> k{runtime, HermesValue::encodeDoubleValue(0)};
// Index to copy to in the new array.
uint32_t to = 0;
// Value at index k.
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
MutableHandle<JSObject> descObjHandle{runtime};
MutableHandle<> kValue{runtime};
auto marker = gcScope.createMarker();
while (k->getDouble() < len) {
gcScope.flushToMarker(marker);
ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, descObjHandle, tmpPropNameStorage, desc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
O, runtime, descObjHandle, tmpPropNameStorage, desc, k);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
kValue = std::move(*propRes);
// Call the callback.
auto callRes = Callable::executeCall3(
callbackFn,
runtime,
args.getArgHandle(1),
kValue.get(),
k.get(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (toBoolean(callRes->get())) {
// Add the element to the array if it passes the callback.
JSArray::setElementAt(A, runtime, to, kValue);
++to;
}
}
k = HermesValue::encodeDoubleValue(k->getDouble() + 1);
}
if (LLVM_UNLIKELY(
JSArray::setLengthProperty(A, runtime, to) ==
ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
return A.getHermesValue();
}
CallResult<HermesValue>
arrayPrototypeFill(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
// Get the length.
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lenRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *lenRes;
// Get the value to be filled.
MutableHandle<> value(runtime, args.getArg(0));
// Get the relative start and end.
auto intRes = toIntegerOrInfinity(runtime, args.getArgHandle(1));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double relativeStart = intRes->getNumber();
// Index to start the deletion/insertion at.
double actualStart = relativeStart < 0 ? std::max(len + relativeStart, 0.0)
: std::min(relativeStart, len);
double relativeEnd;
if (args.getArg(2).isUndefined()) {
relativeEnd = len;
} else {
if (LLVM_UNLIKELY(
(intRes = toIntegerOrInfinity(runtime, args.getArgHandle(2))) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
relativeEnd = intRes->getNumber();
}
// Actual end index.
double actualEnd = relativeEnd < 0 ? std::max(len + relativeEnd, 0.0)
: std::min(relativeEnd, len);
MutableHandle<> k(runtime, HermesValue::encodeDoubleValue(actualStart));
auto marker = gcScope.createMarker();
while (k->getDouble() < actualEnd) {
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O, runtime, k, value, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
k.set(HermesValue::encodeDoubleValue(k->getDouble() + 1));
gcScope.flushToMarker(marker);
}
return O.getHermesValue();
}
static CallResult<HermesValue>
findHelper(void *ctx, bool reverse, Runtime &runtime, NativeArgs args) {
GCScope gcScope{runtime};
bool findIndex = ctx != nullptr;
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
// Get the length.
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *intRes;
auto predicate = args.dyncastArg<Callable>(0);
if (!predicate) {
return runtime.raiseTypeError("Find argument must be a function");
}
// "this" argument to the callback function.
auto T = args.getArgHandle(1);
MutableHandle<> kHandle{runtime};
MutableHandle<> kValue{runtime};
auto marker = gcScope.createMarker();
for (size_t i = 0; i < len; ++i) {
kHandle = HermesValue::encodeNumberValue(reverse ? (len - i - 1) : i);
gcScope.flushToMarker(marker);
if (LLVM_UNLIKELY(
(propRes = JSObject::getComputed_RJS(O, runtime, kHandle)) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
kValue = std::move(*propRes);
auto callRes = Callable::executeCall3(
predicate,
runtime,
T,
kValue.getHermesValue(),
kHandle.getHermesValue(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
bool testResult = toBoolean(callRes->get());
if (testResult) {
// If this is index find variant, then return the index k.
// Else, return the value at the index k.
return findIndex ? kHandle.getHermesValue() : kValue.getHermesValue();
}
}
// Failure case for Array.prototype.findIndex is -1.
// Failure case for Array.prototype.find is undefined.
// The last variants share the same failure case values.
return findIndex ? HermesValue::encodeNumberValue(-1)
: HermesValue::encodeUndefinedValue();
}
CallResult<HermesValue>
arrayPrototypeFind(void *ctx, Runtime &runtime, NativeArgs args) {
return findHelper(ctx, false, runtime, args);
}
CallResult<HermesValue>
arrayPrototypeFindLast(void *ctx, Runtime &runtime, NativeArgs args) {
return findHelper(ctx, true, runtime, args);
}
/// Helper for reduce and reduceRight.
/// \param reverse set to true to reduceRight, false to reduce from the left.
static inline CallResult<HermesValue>
reduceHelper(Runtime &runtime, NativeArgs args, const bool reverse) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *intRes;
size_t argCount = args.getArgCount();
auto callbackFn = args.dyncastArg<Callable>(0);
if (!callbackFn) {
return runtime.raiseTypeError(
"Array.prototype.reduce() requires a callable argument");
}
// Can't reduce an empty array without an initial value.
if (len == 0 && argCount < 2) {
return runtime.raiseTypeError(
"Array.prototype.reduce() requires an initial value with empty array");
}
// Current index in the reduction iteration.
MutableHandle<> k{
runtime, HermesValue::encodeDoubleValue(reverse ? len - 1 : 0)};
MutableHandle<SymbolID> kNameTmpStorage{runtime};
MutableHandle<JSObject> kDescObjHandle{runtime};
MutableHandle<> accumulator{runtime};
auto marker = gcScope.createMarker();
// How much to increment k by each iteration of a loop.
double increment = reverse ? -1 : 1;
// Initialize the accumulator to either the intialValue arg or the first value
// of the array.
if (argCount >= 2) {
accumulator = args.getArg(1);
} else {
bool kPresent = false;
while (!kPresent) {
gcScope.flushToMarker(marker);
if (!reverse) {
if (k->getDouble() >= len) {
break;
}
} else {
if (k->getDouble() < 0) {
break;
}
}
ComputedPropertyDescriptor kDesc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, kDescObjHandle, kNameTmpStorage, kDesc);
CallResult<PseudoHandle<>> propRes =
JSObject::getComputedPropertyValue_RJS(
O, runtime, kDescObjHandle, kNameTmpStorage, kDesc, k);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
kPresent = true;
accumulator = std::move(*propRes);
}
k = HermesValue::encodeDoubleValue(k->getDouble() + increment);
}
if (!kPresent) {
return runtime.raiseTypeError(
"Array.prototype.reduce() requires an intial value with empty array");
}
}
// Perform the reduce.
while (true) {
gcScope.flushToMarker(marker);
if (!reverse) {
if (k->getDouble() >= len) {
break;
}
} else {
if (k->getDouble() < 0) {
break;
}
}
ComputedPropertyDescriptor kDesc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, kDescObjHandle, kNameTmpStorage, kDesc);
CallResult<PseudoHandle<>> propRes = JSObject::getComputedPropertyValue_RJS(
O, runtime, kDescObjHandle, kNameTmpStorage, kDesc, k);
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_LIKELY(!(*propRes)->isEmpty())) {
// kPresent is true, run the accumulation step.
auto kValue = std::move(*propRes);
auto callRes = Callable::executeCall4(
callbackFn,
runtime,
Runtime::getUndefinedValue(),
accumulator.get(),
kValue.get(),
k.get(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
accumulator = std::move(*callRes);
}
k = HermesValue::encodeDoubleValue(k->getDouble() + increment);
}
return accumulator.get();
}
CallResult<HermesValue>
arrayPrototypeReduce(void *, Runtime &runtime, NativeArgs args) {
return reduceHelper(runtime, args, false);
}
CallResult<HermesValue>
arrayPrototypeReduceRight(void *, Runtime &runtime, NativeArgs args) {
return reduceHelper(runtime, args, true);
}
/// ES10.0 22.1.3.23.
CallResult<HermesValue>
arrayPrototypeReverse(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
MutableHandle<> lower{runtime, HermesValue::encodeDoubleValue(0)};
MutableHandle<> upper{runtime};
// The values at the lower and upper indices.
MutableHandle<> lowerValue{runtime};
MutableHandle<> upperValue{runtime};
auto marker = gcScope.createMarker();
auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lenRes = toLengthU64(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *lenRes;
// Indices used in the reversal process.
uint64_t middle = len / 2;
while (lower->getDouble() != middle) {
gcScope.flushToMarker(marker);
upper = HermesValue::encodeDoubleValue(len - lower->getNumber() - 1);
CallResult<bool> lowerExistsRes = JSObject::hasComputed(O, runtime, lower);
if (LLVM_UNLIKELY(lowerExistsRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (*lowerExistsRes) {
CallResult<PseudoHandle<>> lowerValueRes =
JSObject::getComputed_RJS(O, runtime, lower);
if (LLVM_UNLIKELY(lowerValueRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
lowerValue = std::move(*lowerValueRes);
gcScope.flushToMarker(marker);
}
CallResult<bool> upperExistsRes = JSObject::hasComputed(O, runtime, upper);
if (LLVM_UNLIKELY(upperExistsRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (*upperExistsRes) {
CallResult<PseudoHandle<>> upperValueRes =
JSObject::getComputed_RJS(O, runtime, upper);
if (LLVM_UNLIKELY(upperValueRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
upperValue = std::move(*upperValueRes);
gcScope.flushToMarker(marker);
}
// Handle cases in which lower/upper do/don't exist.
if (*lowerExistsRes && *upperExistsRes) {
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
lower,
upperValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
upper,
lowerValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else if (*upperExistsRes) {
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
lower,
upperValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, upper, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
} else if (*lowerExistsRes) {
if (LLVM_UNLIKELY(
JSObject::deleteComputed(
O, runtime, lower, PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
if (LLVM_UNLIKELY(
JSObject::putComputed_RJS(
O,
runtime,
upper,
lowerValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
}
lower = HermesValue::encodeDoubleValue(lower->getDouble() + 1);
}
return O.getHermesValue();
}
CallResult<HermesValue>
arrayPrototypeIncludes(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope{runtime};
// 1. Let O be ? ToObject(this value).
auto oRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(oRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime.makeHandle<JSObject>(*oRes);
// 2. Let len be ? ToLength(? Get(O, "length")).
auto lenPropRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(lenPropRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lenRes =
toLengthU64(runtime, runtime.makeHandle(std::move(*lenPropRes)));
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
double len = *lenRes;
// 3. If len is 0, return false.
if (len == 0) {
return HermesValue::encodeBoolValue(false);
}
// 4. Let n be ? ToIntegerOrInfinity(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
auto nRes = toIntegerOrInfinity(runtime, args.getArgHandle(1));
if (LLVM_UNLIKELY(nRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// Use double here, because ToInteger may return Infinity.
double n = nRes->getNumber();
double k;
if (n >= 0) {
// 5. If n ≥ 0, then
// 5a. Let k be n.
k = n;
} else {
// 6. Else n < 0,
// 6a. Let k be len + n.
k = len + n;
// 6b. If k < 0, let k be 0.
if (k < 0) {
k = 0;
}
}
MutableHandle<> kHandle{runtime};
// 7. Repeat, while k < len
auto marker = gcScope.createMarker();
while (k < len) {
gcScope.flushToMarker(marker);
// 7a. Let elementK be the result of ? Get(O, ! ToString(k)).
kHandle = HermesValue::encodeNumberValue(k);
auto elementKRes = JSObject::getComputed_RJS(O, runtime, kHandle);
if (LLVM_UNLIKELY(elementKRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 7b. If SameValueZero(searchElement, elementK) is true, return true.
if (isSameValueZero(args.getArg(0), elementKRes->get())) {
return HermesValue::encodeBoolValue(true);
}
// 7c. Increase k by 1.
++k;
}
// 8. Return false.
return HermesValue::encodeBoolValue(false);
}
CallResult<HermesValue> arrayOf(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope{runtime};
// 1. Let len be the actual number of arguments passed to this function.
uint32_t len = args.getArgCount();
// 2. Let items be the List of arguments passed to this function.
// 3. Let C be the this value.
auto C = args.getThisHandle();
MutableHandle<JSObject> A{runtime};
CallResult<bool> isConstructorRes = isConstructor(runtime, *C);
if (LLVM_UNLIKELY(isConstructorRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 4. If IsConstructor(C) is true, then
if (*isConstructorRes) {
// a. Let A be Construct(C, «len»).
auto aRes = Callable::executeConstruct1(
Handle<Callable>::vmcast(C),
runtime,
runtime.makeHandle(HermesValue::encodeNumberValue(len)));
if (LLVM_UNLIKELY(aRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
A = PseudoHandle<JSObject>::vmcast(std::move(*aRes));
} else {
// 5. Else,
// a. Let A be ArrayCreate(len).
auto aRes = JSArray::create(runtime, len, len);
if (LLVM_UNLIKELY(aRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
A = vmcast<JSObject>(aRes->getHermesValue());
}
// 7. Let k be 0.
MutableHandle<> k{runtime, HermesValue::encodeNumberValue(0)};
MutableHandle<> kValue{runtime};
GCScopeMarkerRAII marker{gcScope};
// 8. Repeat, while k < len
for (; k->getNumberAs<uint32_t>() < len; marker.flush()) {
// a. Let kValue be items[k].
kValue = args.getArg(k->getNumber());
// c. Let defineStatus be CreateDataPropertyOrThrow(A,Pk, kValue).
if (LLVM_UNLIKELY(
JSObject::defineOwnComputedPrimitive(
A,
runtime,
k,
DefinePropertyFlags::getDefaultNewPropertyFlags(),
kValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// e. Increase k by 1.
k = HermesValue::encodeNumberValue(k->getNumber() + 1);
}
// 9. Let setStatus be Set(A, "length", len, true).
// 10. ReturnIfAbrupt(setStatus).
auto setStatus = JSObject::putNamed_RJS(
A,
runtime,
Predefined::getSymbolID(Predefined::length),
runtime.makeHandle(HermesValue::encodeNumberValue(len)),
PropOpFlags().plusThrowOnError());
if (LLVM_UNLIKELY(setStatus == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 11. Return A.
return A.getHermesValue();
}
/// ES6.0 22.1.2.1 Array.from ( items [ , mapfn [ , thisArg ] ] )
CallResult<HermesValue> arrayFrom(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope{runtime};
auto itemsHandle = args.getArgHandle(0);
// 1. Let C be the this value.
auto C = args.getThisHandle();
// 2. If mapfn is undefined, let mapping be false.
// 3. else
MutableHandle<Callable> mapfn{runtime};
MutableHandle<> T{runtime, HermesValue::encodeUndefinedValue()};
if (!args.getArg(1).isUndefined()) {
mapfn = dyn_vmcast<Callable>(args.getArg(1));
// a. If IsCallable(mapfn) is false, throw a TypeError exception.
if (LLVM_UNLIKELY(!mapfn)) {
return runtime.raiseTypeError("Mapping function is not callable.");
}
// b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (args.getArgCount() >= 3) {
T = args.getArg(2);
}
// c. Let mapping be true
}
// 4. Let usingIterator be GetMethod(items, @@iterator).
// 5. ReturnIfAbrupt(usingIterator).
auto methodRes = getMethod(
runtime,
itemsHandle,
runtime.makeHandle(Predefined::getSymbolID(Predefined::SymbolIterator)));
if (LLVM_UNLIKELY(methodRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto usingIterator = runtime.makeHandle(methodRes->getHermesValue());
MutableHandle<JSObject> A{runtime};
// 6. If usingIterator is not undefined, then
if (!usingIterator->isUndefined()) {
CallResult<bool> isConstructorRes = isConstructor(runtime, *C);
if (LLVM_UNLIKELY(isConstructorRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// a. If IsConstructor(C) is true, then
if (*isConstructorRes) {
GCScopeMarkerRAII markerConstruct{gcScope};
// i. Let A be Construct(C).
auto callRes =
Callable::executeConstruct0(Handle<Callable>::vmcast(C), runtime);
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
A = PseudoHandle<JSObject>::vmcast(std::move(*callRes));
} else {
// b. Else,
// i. Let A be ArrayCreate(0).
auto arrRes = JSArray::create(runtime, 0, 0);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
A = arrRes->get();
}
// c. ReturnIfAbrupt(A).
// d. Let iterator be GetIterator(items, usingIterator).
// Assert we can cast usingIterator to a Callable otherwise getMethod would
// have thrown.
// e. ReturnIfAbrupt(iterator).
auto iterRes = getIterator(
runtime, args.getArgHandle(0), Handle<Callable>::vmcast(usingIterator));
if (LLVM_UNLIKELY(iterRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto iteratorRecord = *iterRes;
// f. Let k be 0.
MutableHandle<> k{runtime, HermesValue::encodeNumberValue(0)};
// g. Repeat
MutableHandle<> mappedValue{runtime};
MutableHandle<> nextValue{runtime};
while (true) {
GCScopeMarkerRAII marker1{runtime};
// ii. Let next be IteratorStep(iteratorRecord).
// iii. ReturnIfAbrupt(next).
auto next = iteratorStep(runtime, iteratorRecord);
if (LLVM_UNLIKELY(next == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// iv. If next is false, then
if (!next.getValue()) {
// 1. Let setStatus be Set(A, "length", k, true).
// 2. ReturnIfAbrupt(setStatus).
// 3. Return A.
auto setStatus = JSObject::putNamed_RJS(
A,
runtime,
Predefined::getSymbolID(Predefined::length),
k,
PropOpFlags().plusThrowOnError());
if (LLVM_UNLIKELY(setStatus == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
return A.getHermesValue();
}
// v. Let nextValue be IteratorValue(next).
// vi. ReturnIfAbrupt(nextValue).
auto propRes = JSObject::getNamed_RJS(
*next, runtime, Predefined::getSymbolID(Predefined::value));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
nextValue = std::move(*propRes);
// vii. If mapping is true, then
if (mapfn) {
// 1. Let mappedValue be Call(mapfn, T, «nextValue, k»).
auto callRes = Callable::executeCall2(
mapfn, runtime, T, nextValue.getHermesValue(), k.getHermesValue());
// 2. If mappedValue is an abrupt completion, return
// IteratorClose(iterator, mappedValue).
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return iteratorCloseAndRethrow(runtime, iteratorRecord.iterator);
}
// 3. Let mappedValue be mappedValue.[[value]].
mappedValue = std::move(*callRes);
} else {
// viii. Else, let mappedValue be nextValue.
mappedValue = nextValue.getHermesValue();
}
// ix. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue).
// x. If defineStatus is an abrupt completion, return
// IteratorClose(iterator, defineStatus).
if (LLVM_UNLIKELY(
JSObject::defineOwnComputedPrimitive(
A,
runtime,
k,
DefinePropertyFlags::getDefaultNewPropertyFlags(),
mappedValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return iteratorCloseAndRethrow(runtime, iteratorRecord.iterator);
}
// xi. Increase k by 1.
k = HermesValue::encodeNumberValue(k->getNumber() + 1);
}
}
// 7. Assert: items is not an Iterable so assume it is an array-like object.
// 8. Let arrayLike be ToObject(items).
auto objRes = toObject(runtime, itemsHandle);
// 9. ReturnIfAbrupt(arrayLike).
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto arrayLike = runtime.makeHandle<JSObject>(objRes.getValue());
// 10. Let len be ToLength(Get(arrayLike, "length")).
// 11. ReturnIfAbrupt(len).
auto propRes = JSObject::getNamed_RJS(
arrayLike, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto lengthRes = toLength(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(lengthRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = lengthRes->getNumberAs<uint64_t>();
CallResult<bool> isConstructorRes = isConstructor(runtime, *C);
if (LLVM_UNLIKELY(isConstructorRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 12. If IsConstructor(C) is true, then
if (*isConstructorRes) {
// a. Let A be Construct(C, «len»).
auto callRes = Callable::executeConstruct1(
Handle<Callable>::vmcast(C),
runtime,
runtime.makeHandle(lengthRes.getValue()));
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
A = PseudoHandle<JSObject>::vmcast(std::move(*callRes));
} else {
// 13. Else,
// a. Let A be ArrayCreate(len).
if (LLVM_UNLIKELY(len > JSArray::StorageType::maxElements())) {
return runtime.raiseRangeError("Out of memory for array elements.");
}
auto arrRes = JSArray::create(runtime, len, len);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
A = arrRes->get();
}
// 14. ReturnIfAbrupt(A).
// 15. Let k be 0.
MutableHandle<> k{runtime, HermesValue::encodeNumberValue(0)};
// 16. Repeat, while k < len
MutableHandle<> mappedValue{runtime};
while (k->getNumberAs<uint32_t>() < len) {
GCScopeMarkerRAII marker2{runtime};
// b. Let kValue be Get(arrayLike, Pk).
propRes = JSObject::getComputed_RJS(arrayLike, runtime, k);
// c. ReturnIfAbrupt(kValue).
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// d. If mapping is true, then
if (mapfn) {
// i. Let mappedValue be Call(mapfn, T, «kValue, k»).
// ii. ReturnIfAbrupt(mappedValue).
auto callRes = Callable::executeCall2(
mapfn, runtime, T, propRes->get(), k.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
mappedValue = std::move(*callRes);
} else {
// e. Else, let mappedValue be kValue.
mappedValue = std::move(*propRes);
}
// f. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue).
// g. ReturnIfAbrupt(defineStatus).
if (LLVM_UNLIKELY(
JSObject::defineOwnComputedPrimitive(
A,
runtime,
k,
DefinePropertyFlags::getDefaultNewPropertyFlags(),
mappedValue,
PropOpFlags().plusThrowOnError()) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// h. Increase k by 1.
k = HermesValue::encodeNumberValue(k->getNumber() + 1);
}
// 17. Let setStatus be Set(A, "length", len, true).
auto setStatus = JSObject::putNamed_RJS(
A,
runtime,
Predefined::getSymbolID(Predefined::length),
k,
PropOpFlags().plusThrowOnError());
// 18. ReturnIfAbrupt(setStatus).
if (LLVM_UNLIKELY(setStatus == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// 19. Return A.
return A.getHermesValue();
}
} // namespace vm
} // namespace hermes