lib/VM/JSArray.cpp (625 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. */ #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/PropertyAccessor.h" namespace hermes { namespace vm { //===----------------------------------------------------------------------===// // class ArrayImpl void ArrayImplBuildMeta(const GCCell *cell, Metadata::Builder &mb) { mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<ArrayImpl>()); JSObjectBuildMeta(cell, mb); const auto *self = static_cast<const ArrayImpl *>(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<ArrayImpl>(cell); // Add the super type's edges too. JSObject::_snapshotAddEdgesImpl(self, gc, snap); if (!self->getIndexedStorage(gc->getPointerBase())) { return; } // 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". snap.addNamedEdge( HeapSnapshot::EdgeType::Internal, "elements", gc->getObjectID(self->getIndexedStorage(gc->getPointerBase()))); auto *const indexedStorage = self->getIndexedStorage(gc->getPointerBase()); const auto beginIndex = self->beginIndex_; const auto endIndex = self->endIndex_; for (uint32_t i = beginIndex; i < endIndex; i++) { const auto &elem = indexedStorage->at(i - beginIndex); const llvh::Optional<HeapSnapshot::NodeID> elemID = gc->getSnapshotID(elem); if (!elemID) { continue; } snap.addIndexedEdge(HeapSnapshot::EdgeType::Element, i, elemID.getValue()); } } bool ArrayImpl::_haveOwnIndexedImpl( JSObject *selfObj, Runtime &runtime, uint32_t index) { auto *self = vmcast<ArrayImpl>(selfObj); // Check whether the index is within the storage. if (index >= self->beginIndex_ && index < self->endIndex_) return !self->getIndexedStorage(runtime) ->at(index - self->beginIndex_) .isEmpty(); return false; } OptValue<PropertyFlags> ArrayImpl::_getOwnIndexedPropertyFlagsImpl( JSObject *selfObj, Runtime &runtime, uint32_t index) { auto *self = vmcast<ArrayImpl>(selfObj); // Check whether the index is within the storage. if (index >= self->beginIndex_ && index < self->endIndex_ && !self->getIndexedStorage(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<uint32_t, uint32_t> ArrayImpl::_getOwnIndexedRangeImpl( JSObject *selfObj, Runtime &runtime) { auto *self = vmcast<ArrayImpl>(selfObj); return {self->beginIndex_, self->endIndex_}; } HermesValue ArrayImpl::_getOwnIndexedImpl( JSObject *selfObj, Runtime &runtime, uint32_t index) { return vmcast<ArrayImpl>(selfObj)->at(runtime, index); } ExecutionStatus ArrayImpl::setStorageEndIndex( Handle<ArrayImpl> 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->getIndexedStorage(runtime))) { 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<StorageType>(std::move(*arrRes)); selfHandle->setIndexedStorage( 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->getIndexedStorage(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->setIndexedStorage(runtime, nullptr, &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->getIndexedStorage(runtime)); if (StorageType::resize(indexedStorage, runtime, newLength - beginIndex) == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } selfHandle->endIndex_ = newLength; selfHandle->setIndexedStorage( runtime, indexedStorage.get(), &runtime.getHeap()); return ExecutionStatus::RETURNED; } CallResult<bool> ArrayImpl::_setOwnIndexedImpl( Handle<JSObject> selfHandle, Runtime &runtime, uint32_t index, Handle<> value) { auto *self = vmcast<ArrayImpl>(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->getIndexedStorage(runtime)->set( index - beginIndex, value.get(), &runtime.getHeap()); return true; } // If indexedStorage hasn't even been allocated. if (LLVM_UNLIKELY(!self->getIndexedStorage(runtime))) { // 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<StorageType>(std::move(*arrRes)); self = vmcast<ArrayImpl>(selfHandle.get()); self->setIndexedStorage(runtime, newStorage.get(), &runtime.getHeap()); self->beginIndex_ = index; self->endIndex_ = index + 1; newStorage->set(0, value.get(), &runtime.getHeap()); return true; } { NoAllocScope scope{runtime}; auto *const indexedStorage = self->getIndexedStorage(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->set(index - beginIndex, value.get(), &runtime.getHeap()); return true; } } auto indexedStorageHandle = runtime.makeMutableHandle(self->getIndexedStorage(runtime)); // 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->set(0, value.getHermesValue(), &runtime.getHeap()); self = vmcast<ArrayImpl>(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<ArrayImpl>(selfHandle.get()); self->endIndex_ = index + 1; indexedStorageHandle->set( index - beginIndex, 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<ArrayImpl>(selfHandle.get()); self->beginIndex_ = index; indexedStorageHandle->set(0, value.get(), &runtime.getHeap()); } // Update the potentially changed pointer. self->setIndexedStorage( runtime, indexedStorageHandle.get(), &runtime.getHeap()); return true; } bool ArrayImpl::_deleteOwnIndexedImpl( Handle<JSObject> selfHandle, Runtime &runtime, uint32_t index) { auto *self = vmcast<ArrayImpl>(selfHandle.get()); NoAllocScope noAlloc{runtime}; if (index >= self->beginIndex_ && index < self->endIndex_) { auto *indexedStorage = self->getIndexedStorage(runtime); // Cannot delete indexed elements if we are sealed. if (LLVM_UNLIKELY(self->flags_.sealed)) { HermesValue elem = indexedStorage->at(index - self->beginIndex_); if (!elem.isEmpty()) return false; } indexedStorage->setNonPtr( index - self->beginIndex_, HermesValue::encodeEmptyValue(), &runtime.getHeap()); } return true; } bool ArrayImpl::_checkAllOwnIndexedImpl( JSObject *selfObj, Runtime &runtime, ObjectVTable::CheckAllOwnIndexedMode /*mode*/ ) { auto *self = vmcast<ArrayImpl>(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->getIndexedStorage(runtime)->at(i).isEmpty()) return false; } return true; } //===----------------------------------------------------------------------===// // class Arguments const ObjectVTable Arguments::vt{ VTable( CellKind::ArgumentsKind, cellSize<Arguments>(), nullptr, nullptr, nullptr, nullptr, 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<Arguments>()); ArrayImplBuildMeta(cell, mb); mb.setVTable(&Arguments::vt); } CallResult<Handle<Arguments>> Arguments::create( Runtime &runtime, size_type length, Handle<Callable> curFunction, bool strictMode) { auto clazz = runtime.getHiddenClassForPrototype( runtime.objectPrototypeRawPtr, numOverlapSlots<Arguments>()); auto obj = runtime.makeAFixed<Arguments>( runtime, Handle<JSObject>::vmcast(&runtime.objectPrototype), clazz); auto selfHandle = JSObjectInit::initToHandle(runtime, obj); { auto arrRes = StorageType::create(runtime, length); if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) return ExecutionStatus::EXCEPTION; selfHandle->setIndexedStorage(runtime, arrRes->get(), &runtime.getHeap()); } 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 /// propagated 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<PropertyAccessor>::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<Callable>(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 const ObjectVTable JSArray::vt{ VTable( CellKind::JSArrayKind, cellSize<JSArray>(), nullptr, nullptr, nullptr, nullptr, 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 JSArrayBuildMeta(const GCCell *cell, Metadata::Builder &mb) { mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSArray>()); ArrayImplBuildMeta(cell, mb); mb.setVTable(&JSArray::vt); } Handle<HiddenClass> JSArray::createClass( Runtime &runtime, Handle<JSObject> prototypeHandle) { Handle<HiddenClass> classHandle = runtime.getHiddenClassForPrototype( *prototypeHandle, numOverlapSlots<JSArray>()); 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<Handle<JSArray>> JSArray::create( Runtime &runtime, Handle<JSObject> prototypeHandle, Handle<HiddenClass> 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"); auto self = JSObjectInit::initToHandle( runtime, runtime.makeAFixed<JSArray>( runtime, prototypeHandle, classHandle, GCPointerBase::NoBarriers())); // Only allocate the storage if capacity is not zero. 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; } self->setIndexedStorage(runtime, arrRes->get(), &runtime.getHeap()); } auto shv = SmallHermesValue::encodeNumberValue(length, runtime); putLength(self.get(), runtime, shv); return self; } CallResult<Handle<JSArray>> JSArray::create(Runtime &runtime, size_type capacity, size_type length) { return JSArray::create( runtime, Handle<JSObject>::vmcast(&runtime.arrayPrototype), Handle<HiddenClass>::vmcast(&runtime.arrayClass), capacity, length); } CallResult<bool> JSArray::setLength( Handle<JSArray> 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<bool> JSArray::setLength( Handle<JSArray> selfHandle, Runtime &runtime, uint32_t newLength, PropOpFlags opFlags) { // Fast-path: if we are enlarging, do nothing. const auto currentLength = getLength(*selfHandle, runtime); if (LLVM_LIKELY(newLength >= currentLength)) { auto shv = SmallHermesValue::encodeNumberValue(newLength, runtime); putLength(*selfHandle, runtime, shv); 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 currentLength // | | // +--------------------+ // 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, currentLength); 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<uint32_t, SymbolID>; llvh::SmallVector<IndexProp, 8> 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; } } auto shv = SmallHermesValue::encodeNumberValue(adjustedLength, runtime); putLength(*selfHandle, runtime, shv); if (adjustedLength != newLength) { if (opFlags.getThrowOnError()) { return runtime.raiseTypeError( TwineChar16("Cannot delete property '") + (adjustedLength - 1) + "'"); } return false; } return true; } //===----------------------------------------------------------------------===// // class JSArrayIterator const ObjectVTable JSArrayIterator::vt{ VTable(CellKind::JSArrayIteratorKind, cellSize<JSArrayIterator>()), JSArrayIterator::_getOwnIndexedRangeImpl, JSArrayIterator::_haveOwnIndexedImpl, JSArrayIterator::_getOwnIndexedPropertyFlagsImpl, JSArrayIterator::_getOwnIndexedImpl, JSArrayIterator::_setOwnIndexedImpl, JSArrayIterator::_deleteOwnIndexedImpl, JSArrayIterator::_checkAllOwnIndexedImpl, }; void JSArrayIteratorBuildMeta(const GCCell *cell, Metadata::Builder &mb) { mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSArrayIterator>()); JSObjectBuildMeta(cell, mb); const auto *self = static_cast<const JSArrayIterator *>(cell); mb.setVTable(&JSArrayIterator::vt); mb.addField("iteratedObject", &self->iteratedObject_); } PseudoHandle<JSArrayIterator> JSArrayIterator::create( Runtime &runtime, Handle<JSObject> array, IterationKind iterationKind) { auto proto = Handle<JSObject>::vmcast(&runtime.arrayIteratorPrototype); auto clazz = runtime.getHiddenClassForPrototype( *proto, numOverlapSlots<JSArrayIterator>()); auto *obj = runtime.makeAFixed<JSArrayIterator>( runtime, proto, clazz, array, iterationKind); return JSObjectInit::initToPseudoHandle(runtime, obj); } /// Iterate to the next element and return. CallResult<HermesValue> JSArrayIterator::nextElement( Handle<JSArrayIterator> 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<JSObject> 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<JSTypedArrayBase>::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<PseudoHandle<>> 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<JSArray> result = *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