/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "hermes/VM/JSArray.h"
#include "hermes/VM/BuildMetadata.h"
#include "hermes/VM/Callable.h"
#include "hermes/VM/JSTypedArray.h"
#include "hermes/VM/Operations.h"
#include "hermes/VM/StringView.h"
#include "llvh/Support/Debug.h"
#define DEBUG_TYPE "serialize"
namespace hermes {
namespace vm {
//===----------------------------------------------------------------------===//
// class ArrayImpl
void ArrayImplBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots());
ObjectBuildMeta(cell, mb);
const auto *self = static_cast(cell);
// This edge has to be called "elements" in order for Chrome to attribute
// the size of the indexed storage as part of total usage of "JS Arrays".
mb.addField("elements", &self->indexedStorage_);
}
void ArrayImpl::_snapshotAddEdgesImpl(
GCCell *cell,
GC *gc,
HeapSnapshot &snap) {
auto *const self = vmcast(cell);
// Add the super type's edges too.
JSObject::_snapshotAddEdgesImpl(self, gc, snap);
if (!self->indexedStorage_) {
return;
}
auto *const indexedStorage =
self->indexedStorage_.getNonNull(gc->getPointerBase());
const auto len = self->endIndex_ - self->beginIndex_;
for (uint32_t i = 0; i < len; i++) {
const auto &elem = indexedStorage->at(i);
const llvh::Optional<:nodeid> elemID = gc->getSnapshotID(elem);
if (!elemID) {
continue;
}
snap.addIndexedEdge(HeapSnapshot::EdgeType::Element, i, elemID.getValue());
}
}
#ifdef HERMESVM_SERIALIZE
ArrayImpl::ArrayImpl(Deserializer &d, const VTable *vt) : JSObject(d, vt) {
beginIndex_ = d.readInt();
endIndex_ = d.readInt();
d.readRelocation(&indexedStorage_, RelocationKind::GCPointer);
}
void serializeArrayImpl(
Serializer &s,
const GCCell *cell,
unsigned overlapSlots) {
auto *self = vmcast(cell);
JSObject::serializeObjectImpl(s, cell, overlapSlots);
s.writeInt(self->beginIndex_);
s.writeInt(self->endIndex_);
s.writeRelocation(self->indexedStorage_.get(s.getRuntime()));
}
#endif
bool ArrayImpl::_haveOwnIndexedImpl(
JSObject *selfObj,
Runtime *runtime,
uint32_t index) {
auto *self = vmcast(selfObj);
// Check whether the index is within the storage.
if (index >= self->beginIndex_ && index < self->endIndex_)
return !self->indexedStorage_.getNonNull(runtime)
->at(index - self->beginIndex_)
.isEmpty();
return false;
}
OptValue ArrayImpl::_getOwnIndexedPropertyFlagsImpl(
JSObject *selfObj,
Runtime *runtime,
uint32_t index) {
auto *self = vmcast(selfObj);
// Check whether the index is within the storage.
if (index >= self->beginIndex_ && index < self->endIndex_ &&
!self->indexedStorage_.getNonNull(runtime)
->at(index - self->beginIndex_)
.isEmpty()) {
PropertyFlags indexedElementFlags{};
indexedElementFlags.enumerable = 1;
indexedElementFlags.writable = 1;
indexedElementFlags.configurable = 1;
if (LLVM_UNLIKELY(self->flags_.sealed)) {
indexedElementFlags.configurable = 0;
if (LLVM_UNLIKELY(self->flags_.frozen))
indexedElementFlags.writable = 0;
}
return indexedElementFlags;
}
return llvh::None;
}
std::pair ArrayImpl::_getOwnIndexedRangeImpl(
JSObject *selfObj,
Runtime *runtime) {
auto *self = vmcast(selfObj);
return {self->beginIndex_, self->endIndex_};
}
HermesValue ArrayImpl::_getOwnIndexedImpl(
JSObject *selfObj,
Runtime *runtime,
uint32_t index) {
return vmcast(selfObj)->at(runtime, index);
}
ExecutionStatus ArrayImpl::setStorageEndIndex(
Handle selfHandle,
Runtime *runtime,
uint32_t newLength) {
auto *self = selfHandle.get();
if (LLVM_UNLIKELY(
newLength > self->beginIndex_ &&
newLength - self->beginIndex_ > StorageType::maxElements())) {
return runtime->raiseRangeError("Out of memory for array elements");
}
// If indexedStorage hasn't even been allocated.
if (LLVM_UNLIKELY(!self->indexedStorage_)) {
if (newLength == 0) {
return ExecutionStatus::RETURNED;
}
auto arrRes = StorageType::create(runtime, newLength, newLength);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto newStorage = runtime->makeHandle(std::move(*arrRes));
selfHandle->indexedStorage_.set(
runtime, newStorage.get(), &runtime->getHeap());
selfHandle->beginIndex_ = 0;
selfHandle->endIndex_ = newLength;
return ExecutionStatus::RETURNED;
}
auto beginIndex = self->beginIndex_;
{
NoAllocScope scope{runtime};
auto *const indexedStorage = self->indexedStorage_.getNonNull(runtime);
if (newLength <= beginIndex) {
// the new length is prior to beginIndex, clearing the storage.
selfHandle->endIndex_ = beginIndex;
// Remove the storage. If this array grows again it can be re-allocated.
self->indexedStorage_.setNull(&runtime->getHeap());
return ExecutionStatus::RETURNED;
} else if (newLength - beginIndex <= indexedStorage->capacity()) {
selfHandle->endIndex_ = newLength;
StorageType::resizeWithinCapacity(
indexedStorage, runtime, newLength - beginIndex);
return ExecutionStatus::RETURNED;
}
}
auto indexedStorage = runtime->makeMutableHandle(selfHandle->indexedStorage_);
if (StorageType::resize(indexedStorage, runtime, newLength - beginIndex) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
selfHandle->endIndex_ = newLength;
selfHandle->indexedStorage_.set(
runtime, indexedStorage.get(), &runtime->getHeap());
return ExecutionStatus::RETURNED;
}
CallResult ArrayImpl::_setOwnIndexedImpl(
Handle selfHandle,
Runtime *runtime,
uint32_t index,
Handle<> value) {
auto *self = vmcast(selfHandle.get());
auto beginIndex = self->beginIndex_;
auto endIndex = self->endIndex_;
if (LLVM_UNLIKELY(self->flags_.frozen))
return false;
// Check whether the index is within the storage.
if (LLVM_LIKELY(index >= beginIndex && index < endIndex)) {
self->indexedStorage_.getNonNull(runtime)
->at(index - beginIndex)
.set(value.get(), &runtime->getHeap());
return true;
}
// If indexedStorage hasn't even been allocated.
if (LLVM_UNLIKELY(!self->indexedStorage_)) {
// Allocate storage with capacity for 4 elements and length 1.
auto arrRes = StorageType::create(runtime, 4, 1);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto newStorage = runtime->makeHandle(std::move(*arrRes));
self = vmcast(selfHandle.get());
self->indexedStorage_.set(runtime, newStorage.get(), &runtime->getHeap());
self->beginIndex_ = index;
self->endIndex_ = index + 1;
newStorage->at(0).set(value.get(), &runtime->getHeap());
return true;
}
{
NoAllocScope scope{runtime};
auto *const indexedStorage = self->indexedStorage_.getNonNull(runtime);
// Can we do it without reallocation for sure?
if (index >= endIndex && index - beginIndex < indexedStorage->capacity()) {
self->endIndex_ = index + 1;
StorageType::resizeWithinCapacity(
indexedStorage, runtime, index - beginIndex + 1);
// self shouldn't have moved since there haven't been any allocations.
indexedStorage->at(index - beginIndex)
.set(value.get(), &runtime->getHeap());
return true;
}
}
auto indexedStorageHandle = runtime->makeMutableHandle(self->indexedStorage_);
// We only shift an array if the shift amount is within the limit.
constexpr uint32_t shiftLimit = (1 << 20);
// Is the array empty?
if (LLVM_UNLIKELY(endIndex == beginIndex)) {
if (StorageType::resize(indexedStorageHandle, runtime, 1) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
indexedStorageHandle->at(0).set(
value.getHermesValue(), &runtime->getHeap());
self = vmcast(selfHandle.get());
self->beginIndex_ = index;
self->endIndex_ = index + 1;
} else if (LLVM_UNLIKELY(
(index > endIndex && index - endIndex > shiftLimit) ||
(index < beginIndex && beginIndex - index > shiftLimit))) {
// The new index is too far away from the current index range.
// Shifting will lead to a very large allocation.
// This is likely a misuse of the array (e.g. use array as an object).
// In this case, we should just treat the index access as
// a property access.
auto vr = valueToSymbolID(
runtime, runtime->makeHandle(HermesValue::encodeNumberValue(index)));
assert(
vr != ExecutionStatus::EXCEPTION &&
"valueToIdentifier() failed for uint32_t value");
if (LLVM_UNLIKELY(
JSObject::defineNewOwnProperty(
selfHandle,
runtime,
**vr,
PropertyFlags::defaultNewNamedPropertyFlags(),
value) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// self->indexedStorage_ is unmodified, we should return directly.
return true;
} else if (index >= endIndex) {
// Extending to the right.
if (StorageType::resize(
indexedStorageHandle, runtime, index - beginIndex + 1) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
self = vmcast(selfHandle.get());
self->endIndex_ = index + 1;
indexedStorageHandle->at(index - beginIndex)
.set(value.get(), &runtime->getHeap());
} else {
// Extending to the left. 'index' will become the new 'beginIndex'.
assert(index < beginIndex);
if (StorageType::resizeLeft(
indexedStorageHandle,
runtime,
indexedStorageHandle->size() + beginIndex - index) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
self = vmcast(selfHandle.get());
self->beginIndex_ = index;
indexedStorageHandle->at(0).set(value.get(), &runtime->getHeap());
}
// Update the potentially changed pointer.
self->indexedStorage_.set(
runtime, indexedStorageHandle.get(), &runtime->getHeap());
return true;
}
bool ArrayImpl::_deleteOwnIndexedImpl(
Handle selfHandle,
Runtime *runtime,
uint32_t index) {
auto *self = vmcast(selfHandle.get());
if (index >= self->beginIndex_ && index < self->endIndex_) {
auto &elem = self->indexedStorage_.getNonNull(runtime)->at(
index - self->beginIndex_);
// Cannot delete indexed elements if we are sealed.
if (LLVM_UNLIKELY(self->flags_.sealed))
if (!elem.isEmpty())
return false;
elem.setNonPtr(HermesValue::encodeEmptyValue(), &runtime->getHeap());
}
return true;
}
bool ArrayImpl::_checkAllOwnIndexedImpl(
JSObject *selfObj,
Runtime *runtime,
ObjectVTable::CheckAllOwnIndexedMode /*mode*/
) {
auto *self = vmcast(selfObj);
// If we have any indexed properties at all, they don't satisfy the
// requirements.
for (uint32_t i = 0, e = self->endIndex_ - self->beginIndex_; i != e; ++i) {
if (!self->indexedStorage_.getNonNull(runtime)->at(i).isEmpty())
return false;
}
return true;
}
//===----------------------------------------------------------------------===//
// class Arguments
ObjectVTable Arguments::vt{
VTable(
CellKind::ArgumentsKind,
cellSize(),
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, // externalMemorySize
VTable::HeapSnapshotMetadata{HeapSnapshot::NodeType::Object,
nullptr,
Arguments::_snapshotAddEdgesImpl,
nullptr,
nullptr}),
Arguments::_getOwnIndexedRangeImpl,
Arguments::_haveOwnIndexedImpl,
Arguments::_getOwnIndexedPropertyFlagsImpl,
Arguments::_getOwnIndexedImpl,
Arguments::_setOwnIndexedImpl,
Arguments::_deleteOwnIndexedImpl,
Arguments::_checkAllOwnIndexedImpl,
};
void ArgumentsBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots());
ArrayImplBuildMeta(cell, mb);
}
#ifdef HERMESVM_SERIALIZE
Arguments::Arguments(Deserializer &d) : ArrayImpl(d, &vt.base) {}
void ArgumentsSerialize(Serializer &s, const GCCell *cell) {
serializeArrayImpl(s, cell, JSObject::numOverlapSlots());
s.endObject(cell);
}
void ArgumentsDeserialize(Deserializer &d, CellKind kind) {
assert(kind == CellKind::ArgumentsKind && "Expected Arguments");
void *mem = d.getRuntime()->alloc(cellSize());
auto *cell = new (mem) Arguments(d);
d.endObject(cell);
}
#endif
CallResult> Arguments::create(
Runtime *runtime,
size_type length,
Handle curFunction,
bool strictMode) {
auto arrRes = StorageType::create(runtime, length);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto indexedStorage = runtime->makeHandle(std::move(*arrRes));
JSObjectAlloc mem{runtime};
auto selfHandle = mem.initToHandle(new (mem) Arguments(
runtime,
runtime->objectPrototypeRawPtr,
runtime->getHiddenClassForPrototypeRaw(
runtime->objectPrototypeRawPtr,
numOverlapSlots() + ANONYMOUS_PROPERTY_SLOTS),
*indexedStorage));
Arguments::setStorageEndIndex(selfHandle, runtime, length);
PropertyFlags pf{};
namespace P = Predefined;
/// Adds a property to the object in \p OBJ_HANDLE. \p SYMBOL provides its name
/// as a \c Predefined enum value, and its value is rooted in \p HANDLE. If
/// property definition fails, the exceptional execution status will be
/// propogated to the outer function.
#define DEFINE_PROP(OBJ_HANDLE, SYMBOL, HANDLE) \
do { \
auto status = JSObject::defineNewOwnProperty( \
OBJ_HANDLE, runtime, Predefined::getSymbolID(SYMBOL), pf, HANDLE); \
if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) { \
return ExecutionStatus::EXCEPTION; \
} \
} while (false)
// Define the length property.
pf.enumerable = 0;
pf.writable = 1;
pf.configurable = 1;
DEFINE_PROP(
selfHandle,
P::length,
runtime->makeHandle(HermesValue::encodeDoubleValue(length)));
DEFINE_PROP(
selfHandle, P::SymbolIterator, Handle<>(&runtime->arrayPrototypeValues));
if (strictMode) {
// Define .callee and .caller properties: throw always in strict mode.
auto accessor =
Handle::vmcast(&runtime->throwTypeErrorAccessor);
pf.clear();
pf.enumerable = 0;
pf.writable = 0;
pf.configurable = 0;
pf.accessor = 1;
DEFINE_PROP(selfHandle, P::callee, accessor);
DEFINE_PROP(selfHandle, P::caller, accessor);
} else {
// Define .callee in non-strict mode to point to the current function.
// Leave .caller undefined, because it's a non-standard ES extension.
assert(
vmisa(curFunction.getHermesValue()) &&
"attempt to reify arguments with a non-callable callee");
pf.clear();
pf.enumerable = 0;
pf.writable = 1;
pf.configurable = 1;
DEFINE_PROP(selfHandle, P::callee, curFunction);
}
return selfHandle;
#undef DEFINE_PROP
}
//===----------------------------------------------------------------------===//
// class JSArray
ObjectVTable JSArray::vt{
VTable(
CellKind::ArrayKind,
cellSize(),
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, // externalMemorySize
VTable::HeapSnapshotMetadata{HeapSnapshot::NodeType::Object,
nullptr,
JSArray::_snapshotAddEdgesImpl,
nullptr,
nullptr}),
JSArray::_getOwnIndexedRangeImpl,
JSArray::_haveOwnIndexedImpl,
JSArray::_getOwnIndexedPropertyFlagsImpl,
JSArray::_getOwnIndexedImpl,
JSArray::_setOwnIndexedImpl,
JSArray::_deleteOwnIndexedImpl,
JSArray::_checkAllOwnIndexedImpl,
};
void ArrayBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots());
ArrayImplBuildMeta(cell, mb);
}
#ifdef HERMESVM_SERIALIZE
JSArray::JSArray(Deserializer &d, const VTable *vt) : ArrayImpl(d, vt) {
shadowLength_ = d.readInt();
}
void ArraySerialize(Serializer &s, const GCCell *cell) {
auto *self = vmcast(cell);
serializeArrayImpl(s, cell, JSObject::numOverlapSlots());
s.writeInt(self->shadowLength_);
s.endObject(cell);
}
void ArrayDeserialize(Deserializer &d, CellKind kind) {
assert(kind == CellKind::ArrayKind && "Expected Array");
void *mem = d.getRuntime()->alloc(cellSize());
auto *cell = new (mem) JSArray(d, &JSArray::vt.base);
d.endObject(cell);
}
#endif
Handle JSArray::createClass(
Runtime *runtime,
Handle prototypeHandle) {
Handle classHandle{
runtime,
runtime->getHiddenClassForPrototypeRaw(
*prototypeHandle,
numOverlapSlots() + ANONYMOUS_PROPERTY_SLOTS)};
PropertyFlags pf{};
pf.enumerable = 0;
pf.writable = 1;
pf.configurable = 0;
pf.internalSetter = 1;
auto added = HiddenClass::addProperty(
classHandle, runtime, Predefined::getSymbolID(Predefined::length), pf);
assert(
added != ExecutionStatus::EXCEPTION &&
"Adding the first properties shouldn't cause overflow");
assert(
added->second == lengthPropIndex() && "JSArray.length has invalid index");
classHandle = added->first;
assert(
classHandle->getNumProperties() == jsArrayPropertyCount() &&
"JSArray class defined with incorrect number of properties");
return classHandle;
}
CallResult> JSArray::create(
Runtime *runtime,
Handle prototypeHandle,
Handle classHandle,
size_type capacity,
size_type length) {
assert(length <= capacity && "length must be <= capacity");
// Allocate property storage with size corresponding to number of properties
// in the hidden class.
assert(
classHandle->getNumProperties() == jsArrayPropertyCount() &&
"invalid number of properties in JSArray hidden class");
// Only allocate the storage if capacity is not zero.
MutableHandle indexedStorage{runtime, nullptr};
if (capacity) {
if (LLVM_UNLIKELY(capacity > StorageType::maxElements()))
return runtime->raiseRangeError("Out of memory for array elements");
auto arrRes = StorageType::create(runtime, capacity);
if (arrRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
indexedStorage = std::move(*arrRes);
}
JSObjectAlloc mem{runtime};
auto self = mem.initToPseudoHandle(new (mem) JSArray(
runtime,
*prototypeHandle,
*classHandle,
*indexedStorage,
GCPointerBase::NoBarriers()));
putLength(self.get(), runtime, length);
return self;
}
CallResult>
JSArray::create(Runtime *runtime, size_type capacity, size_type length) {
return JSArray::create(
runtime,
Handle::vmcast(&runtime->arrayPrototype),
Handle::vmcast(&runtime->arrayClass),
capacity,
length);
}
CallResult JSArray::setLength(
Handle selfHandle,
Runtime *runtime,
Handle<> newLength,
PropOpFlags opFlags) {
// Convert the value to uint32_t.
double d;
if (newLength->isNumber()) {
d = newLength->getNumber();
} else {
auto res = toNumber_RJS(runtime, newLength);
if (res == ExecutionStatus::EXCEPTION)
return ExecutionStatus::EXCEPTION;
d = res->getNumber();
}
// NOTE: in theory this produces UB, however in practice it is well defined.
// Regardless what happens in the conversion, 'ulen' can never compare equal
// to 'd' unless 'd' is really an uint32 number, in which case the conversion
// would have succeeded.
// The only way this could fail is if the conversion throws an exception or
// aborts the application, which is not the case on any platform we are
// targeting.
uint32_t ulen = (uint32_t)d;
if (ulen != d)
return runtime->raiseRangeError("Invalid array length");
return setLength(selfHandle, runtime, ulen, opFlags);
}
CallResult JSArray::setLength(
Handle selfHandle,
Runtime *runtime,
uint32_t newLength,
PropOpFlags opFlags) {
// Fast-path: if we are enlarging, do nothing.
if (LLVM_LIKELY(newLength >= selfHandle->shadowLength_)) {
putLength(*selfHandle, runtime, newLength);
return true;
}
// Length adjusted to the index of the highest non-deletable property + 1.
// Nothing smaller than it can be deleted.
uint32_t adjustedLength = newLength;
// If we are sealed, we can't shrink past non-empty properties.
if (LLVM_UNLIKELY(selfHandle->flags_.sealed)) {
// We must scan backwards looking for a non-empty property. We only have
// to scan in the intersection between the range of present values and
// the range between the current length and the new length.
// newLength shadowLength
// | |
// +--------------------+
// begin end
// | |
// +-------------------+
// +-----------+
// | |
// lowestScanLen highestLen
//
auto *self = selfHandle.get();
auto range = _getOwnIndexedRangeImpl(self, runtime);
uint32_t lowestScanLen = std::max(range.first, newLength);
uint32_t highestLen = std::min(range.second, self->shadowLength_);
for (; highestLen > lowestScanLen; --highestLen) {
if (!self->unsafeAt(runtime, highestLen - 1).isEmpty()) {
adjustedLength = highestLen;
break;
}
}
}
if (LLVM_UNLIKELY(selfHandle->clazz_.getNonNull(runtime)
->getHasIndexLikeProperties())) {
// Uh-oh. We are making the array smaller and we have index-like named
// properties, so we may have to delete some of them: the ones greater or
// equal to 'newLength'.
// We iterate all named properties to find all index-like ones that need to
// be deleted. At the same time we keep track of the highest index of a non-
// deletable property - nothing smaller than that can be deleted because the
// highest non-deletable would have terminated the deletion process.
using IndexProp = std::pair;
llvh::SmallVector toBeDeleted;
GCScope scope{runtime};
HiddenClass::forEachProperty(
runtime->makeHandle(selfHandle->clazz_),
runtime,
[runtime, &adjustedLength, &toBeDeleted, &scope](
SymbolID id, NamedPropertyDescriptor desc) {
GCScopeMarkerRAII marker{scope};
// If this property is not an integer index, or it doesn't need to be
// deleted (it is less than 'adjustedLength'), ignore it.
auto propNameAsIndex = toArrayIndex(
runtime->getIdentifierTable().getStringView(runtime, id));
if (!propNameAsIndex || *propNameAsIndex < adjustedLength)
return;
if (!desc.flags.configurable) {
adjustedLength = *propNameAsIndex + 1;
} else {
toBeDeleted.push_back({*propNameAsIndex, id});
}
});
// Scan the properties to be deleted in reverse order (to make deletion more
// efficient) and delete those >= adjustedLength.
for (auto it = toBeDeleted.rbegin(), e = toBeDeleted.rend(); it != e;
++it) {
if (it->first >= adjustedLength) {
GCScopeMarkerRAII marker{scope};
auto cr = JSObject::deleteNamed(selfHandle, runtime, it->second);
assert(
cr != ExecutionStatus::EXCEPTION && *cr &&
"Failed to delete a configurable property");
(void)cr;
}
}
}
if (adjustedLength < selfHandle->getEndIndex()) {
auto cr = setStorageEndIndex(selfHandle, runtime, adjustedLength);
if (cr == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
}
putLength(*selfHandle, runtime, adjustedLength);
if (adjustedLength != newLength) {
if (opFlags.getThrowOnError()) {
return runtime->raiseTypeError(
TwineChar16("Cannot delete property '") + (adjustedLength - 1) + "'");
}
return false;
}
return true;
}
//===----------------------------------------------------------------------===//
// class JSArrayIterator
ObjectVTable JSArrayIterator::vt{
VTable(CellKind::ArrayIteratorKind, cellSize()),
JSArrayIterator::_getOwnIndexedRangeImpl,
JSArrayIterator::_haveOwnIndexedImpl,
JSArrayIterator::_getOwnIndexedPropertyFlagsImpl,
JSArrayIterator::_getOwnIndexedImpl,
JSArrayIterator::_setOwnIndexedImpl,
JSArrayIterator::_deleteOwnIndexedImpl,
JSArrayIterator::_checkAllOwnIndexedImpl,
};
void ArrayIteratorBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots());
ObjectBuildMeta(cell, mb);
const auto *self = static_cast(cell);
mb.addField("iteratedObject", &self->iteratedObject_);
}
#ifdef HERMESVM_SERIALIZE
JSArrayIterator::JSArrayIterator(Deserializer &d) : JSObject(d, &vt.base) {
d.readRelocation(&iteratedObject_, RelocationKind::GCPointer);
nextIndex_ = d.readInt();
iterationKind_ = (IterationKind)d.readInt();
}
void ArrayIteratorSerialize(Serializer &s, const GCCell *cell) {
auto *self = vmcast(cell);
JSObject::serializeObjectImpl(
s, cell, JSObject::numOverlapSlots());
s.writeRelocation(self->iteratedObject_.get(s.getRuntime()));
s.writeInt(self->nextIndex_);
s.writeInt((uint8_t)self->iterationKind_);
s.endObject(cell);
}
void ArrayIteratorDeserialize(Deserializer &d, CellKind kind) {
assert(kind == CellKind::ArrayIteratorKind && "Expected ArrayIterator");
void *mem = d.getRuntime()->alloc(cellSize());
auto *cell = new (mem) JSArrayIterator(d);
d.endObject(cell);
}
#endif
PseudoHandle JSArrayIterator::create(
Runtime *runtime,
Handle array,
IterationKind iterationKind) {
auto proto = Handle::vmcast(&runtime->arrayIteratorPrototype);
JSObjectAlloc mem{runtime};
return mem.initToPseudoHandle(new (mem) JSArrayIterator(
runtime,
*proto,
runtime->getHiddenClassForPrototypeRaw(
*proto,
numOverlapSlots() + ANONYMOUS_PROPERTY_SLOTS),
*array,
iterationKind));
}
/// Iterate to the next element and return.
CallResult JSArrayIterator::nextElement(
Handle self,
Runtime *runtime) {
if (!self->iteratedObject_) {
// 5. If a is undefined, return CreateIterResultObject(undefined, true).
return createIterResultObject(runtime, Runtime::getUndefinedValue(), true)
.getHermesValue();
}
// 4. Let a be the value of the [[IteratedObject]] internal slot of O.
Handle a = runtime->makeHandle(self->iteratedObject_);
// 6. Let index be the value of the [[ArrayIteratorNextIndex]] internal slot
// of O.
uint64_t index = self->nextIndex_;
uint64_t len;
if (auto ta = Handle::dyn_vmcast(a)) {
// 8. If a has a [[TypedArrayName]] internal slot, then
// a. If IsDetachedBuffer(a.[[ViewedArrayBuffer]]) is true,
// throw a TypeError exception.
// b. Let len be the value of Oâs [[ArrayLength]] internal slot.
if (LLVM_UNLIKELY(!ta->attached(runtime))) {
return runtime->raiseTypeError("TypedArray detached during iteration");
}
len = ta->getLength();
} else {
// 9. Else,
// a. Let len be ToLength(Get(a, "length")).
auto propRes = JSObject::getNamed_RJS(
a, 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->getNumber();
}
if (index >= len) {
// 10. If index ⥠len, then
// a. Set the value of the [[IteratedObject]] internal slot of O to
// undefined.
self->iteratedObject_.setNull(&runtime->getHeap());
// b. Return CreateIterResultObject(undefined, true).
return createIterResultObject(runtime, Runtime::getUndefinedValue(), true)
.getHermesValue();
}
// 11. Set the value of the [[ArrayIteratorNextIndex]] internal slot of O to
// index+1.
++self->nextIndex_;
auto indexHandle = runtime->makeHandle(HermesValue::encodeNumberValue(index));
if (self->iterationKind_ == IterationKind::Key) {
// 12. If itemKind is "key", return CreateIterResultObject(index, false).
return createIterResultObject(runtime, indexHandle, false).getHermesValue();
}
// 13. Let elementKey be ToString(index).
// 14. Let elementValue be Get(a, elementKey).
CallResult> valueRes =
JSObject::getComputed_RJS(a, runtime, indexHandle);
if (LLVM_UNLIKELY(valueRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
Handle<> valueHandle = runtime->makeHandle(std::move(*valueRes));
switch (self->iterationKind_) {
case IterationKind::Key:
llvm_unreachable("Early return already occurred in Key case");
return HermesValue::encodeEmptyValue();
case IterationKind::Value:
// 16. If itemKind is "value", let result be elementValue.
return createIterResultObject(runtime, valueHandle, false)
.getHermesValue();
case IterationKind::Entry: {
// 17. b. Let result be CreateArrayFromList(«index, elementValue»).
auto resultRes = JSArray::create(runtime, 2, 2);
if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
Handle result = runtime->makeHandle(std::move(*resultRes));
JSArray::setElementAt(result, runtime, 0, indexHandle);
JSArray::setElementAt(result, runtime, 1, valueHandle);
// 18. Return CreateIterResultObject(result, false).
return createIterResultObject(runtime, result, false).getHermesValue();
}
case IterationKind::NumKinds:
llvm_unreachable("Invalid iteration kind");
return HermesValue::encodeEmptyValue();
}
llvm_unreachable("Invalid iteration kind");
return HermesValue::encodeEmptyValue();
}
} // namespace vm
} // namespace hermes