diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/vm/Iteration.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/Iteration.cpp')
-rw-r--r-- | js/src/vm/Iteration.cpp | 2168 |
1 files changed, 2168 insertions, 0 deletions
diff --git a/js/src/vm/Iteration.cpp b/js/src/vm/Iteration.cpp new file mode 100644 index 0000000000..a7d7287770 --- /dev/null +++ b/js/src/vm/Iteration.cpp @@ -0,0 +1,2168 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* JavaScript iterators. */ + +#include "vm/Iteration.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/PodOperations.h" + +#include <algorithm> +#include <new> + +#include "jsapi.h" +#include "jstypes.h" + +#include "builtin/Array.h" +#include "builtin/SelfHostingDefines.h" +#include "ds/Sort.h" +#include "gc/GCContext.h" +#include "js/ForOfIterator.h" // JS::ForOfIterator +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/PropertySpec.h" +#include "util/DifferentialTesting.h" +#include "util/Poison.h" +#include "vm/GlobalObject.h" +#include "vm/Interpreter.h" +#include "vm/JSContext.h" +#include "vm/JSObject.h" +#include "vm/NativeObject.h" // js::PlainObject +#include "vm/Shape.h" +#include "vm/StringType.h" +#include "vm/TypedArrayObject.h" +#include "vm/WellKnownAtom.h" // js_*_str + +#ifdef ENABLE_RECORD_TUPLE +# include "builtin/RecordObject.h" +# include "builtin/TupleObject.h" +#endif + +#include "vm/NativeObject-inl.h" +#include "vm/PlainObject-inl.h" // js::PlainObject::createWithTemplate + +using namespace js; + +using mozilla::ArrayEqual; +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::PodCopy; + +using RootedPropertyIteratorObject = Rooted<PropertyIteratorObject*>; + +static const gc::AllocKind ITERATOR_FINALIZE_KIND = + gc::AllocKind::OBJECT2_BACKGROUND; + +// Beware! This function may have to trace incompletely-initialized +// |NativeIterator| allocations if the |IdToString| in that constructor recurs +// into this code. +void NativeIterator::trace(JSTracer* trc) { + TraceNullableEdge(trc, &objectBeingIterated_, "objectBeingIterated_"); + TraceNullableEdge(trc, &iterObj_, "iterObj"); + + // The limits below are correct at every instant of |NativeIterator| + // initialization, with the end-pointer incremented as each new shape is + // created, so they're safe to use here. + std::for_each(shapesBegin(), shapesEnd(), [trc](GCPtr<Shape*>& shape) { + TraceEdge(trc, &shape, "iterator_shape"); + }); + + // But as properties must be created *before* shapes, |propertiesBegin()| + // that depends on |shapesEnd()| having its final value can't safely be + // used. Until this is fully initialized, use |propertyCursor_| instead, + // which points at the start of properties even in partially initialized + // |NativeIterator|s. (|propertiesEnd()| is safe at all times with respect + // to the properly-chosen beginning.) + // + // Note that we must trace all properties (not just those not yet visited, + // or just visited, due to |NativeIterator::previousPropertyWas|) for + // |NativeIterator|s to be reusable. + GCPtr<JSLinearString*>* begin = + MOZ_LIKELY(isInitialized()) ? propertiesBegin() : propertyCursor_; + std::for_each(begin, propertiesEnd(), [trc](GCPtr<JSLinearString*>& prop) { + // Properties begin life non-null and never *become* + // null. (Deletion-suppression will shift trailing + // properties over a deleted property in the properties + // array, but it doesn't null them out.) + TraceEdge(trc, &prop, "prop"); + }); +} + +using PropertyKeySet = GCHashSet<PropertyKey, DefaultHasher<PropertyKey>>; + +class PropertyEnumerator { + RootedObject obj_; + MutableHandleIdVector props_; + PropertyIndexVector* indices_; + + uint32_t flags_; + Rooted<PropertyKeySet> visited_; + + bool enumeratingProtoChain_ = false; + + enum class IndicesState { + // Every property that has been enumerated so far can be represented as a + // PropertyIndex, but we are not currently producing a list of indices. If + // the state is Valid when we are done enumerating, then the resulting + // iterator can be marked as NativeIteratorIndices::AvailableOnRequest. + Valid, + + // Every property that has been enumerated so far can be represented as a + // PropertyIndex, and |indices_| points to a PropertyIndexVector containing + // those indices. This is used when we want to create a NativeIterator with + // valid indices. + Allocating, + + // It is not possible to represent every property of the object being + // enumerated as a PropertyIndex. For example, enumerated properties on the + // prototype chain are unsupported. We can transition to this state from + // either of the other two. + Unsupported + }; + IndicesState indicesState_; + + public: + PropertyEnumerator(JSContext* cx, JSObject* obj, uint32_t flags, + MutableHandleIdVector props, + PropertyIndexVector* indices = nullptr) + : obj_(cx, obj), + props_(props), + indices_(indices), + flags_(flags), + visited_(cx, PropertyKeySet(cx)), + indicesState_(indices ? IndicesState::Allocating + : IndicesState::Valid) {} + + bool snapshot(JSContext* cx); + + void markIndicesUnsupported() { indicesState_ = IndicesState::Unsupported; } + bool supportsIndices() const { + return indicesState_ != IndicesState::Unsupported; + } + bool allocatingIndices() const { + return indicesState_ == IndicesState::Allocating; + } + + private: + template <bool CheckForDuplicates> + bool enumerate(JSContext* cx, jsid id, bool enumerable, + PropertyIndex index = PropertyIndex::Invalid()); + + bool enumerateExtraProperties(JSContext* cx); + + template <bool CheckForDuplicates> + bool enumerateNativeProperties(JSContext* cx); + + bool enumerateNativeProperties(JSContext* cx, bool checkForDuplicates) { + if (checkForDuplicates) { + return enumerateNativeProperties<true>(cx); + } + return enumerateNativeProperties<false>(cx); + } + + template <bool CheckForDuplicates> + bool enumerateProxyProperties(JSContext* cx); + + void reversePropsAndIndicesAfter(size_t initialLength) { + // We iterate through prop maps in descending order of property creation, + // but we need our return value to be in ascending order. If we are tracking + // property indices, make sure to keep them in sync. + MOZ_ASSERT(props_.begin() + initialLength <= props_.end()); + MOZ_ASSERT_IF(allocatingIndices(), props_.length() == indices_->length()); + + std::reverse(props_.begin() + initialLength, props_.end()); + if (allocatingIndices()) { + std::reverse(indices_->begin() + initialLength, indices_->end()); + } + } +}; + +template <bool CheckForDuplicates> +bool PropertyEnumerator::enumerate(JSContext* cx, jsid id, bool enumerable, + PropertyIndex index) { + if (CheckForDuplicates) { + // If we've already seen this, we definitely won't add it. + PropertyKeySet::AddPtr p = visited_.lookupForAdd(id); + if (MOZ_UNLIKELY(!!p)) { + return true; + } + + // It's not necessary to add properties to the hash set at the end of + // the prototype chain, but custom enumeration behaviors might return + // duplicated properties, so always add in such cases. + if (obj_->is<ProxyObject>() || obj_->staticPrototype() || + obj_->getClass()->getNewEnumerate()) { + if (!visited_.add(p, id)) { + return false; + } + } + } + + if (!enumerable && !(flags_ & JSITER_HIDDEN)) { + return true; + } + + // Symbol-keyed properties and nonenumerable properties are skipped unless + // the caller specifically asks for them. A caller can also filter out + // non-symbols by asking for JSITER_SYMBOLSONLY. PrivateName symbols are + // skipped unless JSITER_PRIVATE is passed. + if (id.isSymbol()) { + if (!(flags_ & JSITER_SYMBOLS)) { + return true; + } + if (!(flags_ & JSITER_PRIVATE) && id.isPrivateName()) { + return true; + } + } else { + if ((flags_ & JSITER_SYMBOLSONLY)) { + return true; + } + } + + MOZ_ASSERT_IF(allocatingIndices(), indices_->length() == props_.length()); + if (!props_.append(id)) { + return false; + } + + if (!supportsIndices()) { + return true; + } + if (index.kind() == PropertyIndex::Kind::Invalid || enumeratingProtoChain_) { + markIndicesUnsupported(); + return true; + } + + if (allocatingIndices() && !indices_->append(index)) { + return false; + } + + return true; +} + +bool PropertyEnumerator::enumerateExtraProperties(JSContext* cx) { + MOZ_ASSERT(obj_->getClass()->getNewEnumerate()); + + RootedIdVector properties(cx); + bool enumerableOnly = !(flags_ & JSITER_HIDDEN); + if (!obj_->getClass()->getNewEnumerate()(cx, obj_, &properties, + enumerableOnly)) { + return false; + } + + RootedId id(cx); + for (size_t n = 0; n < properties.length(); n++) { + id = properties[n]; + + // The enumerate hook does not indicate whether the properties + // it returns are enumerable or not. Since we already passed + // `enumerableOnly` to the hook to filter out non-enumerable + // properties, it doesn't really matter what we pass here. + bool enumerable = true; + if (!enumerate<true>(cx, id, enumerable)) { + return false; + } + } + + return true; +} + +static bool SortComparatorIntegerIds(jsid a, jsid b, bool* lessOrEqualp) { + uint32_t indexA, indexB; + MOZ_ALWAYS_TRUE(IdIsIndex(a, &indexA)); + MOZ_ALWAYS_TRUE(IdIsIndex(b, &indexB)); + *lessOrEqualp = (indexA <= indexB); + return true; +} + +template <bool CheckForDuplicates> +bool PropertyEnumerator::enumerateNativeProperties(JSContext* cx) { + Handle<NativeObject*> pobj = obj_.as<NativeObject>(); + + // We don't need to iterate over the shape's properties if we're only + // interested in enumerable properties and the object is known to have no + // enumerable properties. + // + // Don't optimize if CheckForDuplicates is true, because non-enumerable + // properties still have to participate in duplicate-property checking. + const bool iterShapeProperties = CheckForDuplicates || + (flags_ & JSITER_HIDDEN) || + pobj->hasEnumerableProperty(); + + bool enumerateSymbols; + if (flags_ & JSITER_SYMBOLSONLY) { + if (!iterShapeProperties) { + return true; + } + enumerateSymbols = true; + } else { + // Collect any dense elements from this object. + size_t firstElemIndex = props_.length(); + size_t initlen = pobj->getDenseInitializedLength(); + const Value* elements = pobj->getDenseElements(); + bool hasHoles = false; + for (uint32_t i = 0; i < initlen; ++i) { + if (elements[i].isMagic(JS_ELEMENTS_HOLE)) { + hasHoles = true; + } else { + // Dense arrays never get so large that i would not fit into an + // integer id. + if (!enumerate<CheckForDuplicates>(cx, PropertyKey::Int(i), + /* enumerable = */ true, + PropertyIndex::ForElement(i))) { + return false; + } + } + } + + // Collect any typed array or shared typed array elements from this + // object. + if (pobj->is<TypedArrayObject>()) { + size_t len = pobj->as<TypedArrayObject>().length(); + + // Fail early if the typed array is enormous, because this will be very + // slow and will likely report OOM. This also means we don't need to + // handle indices greater than PropertyKey::IntMax in the loop below. + static_assert(PropertyKey::IntMax == INT32_MAX); + if (len > INT32_MAX) { + ReportOutOfMemory(cx); + return false; + } + + for (uint32_t i = 0; i < len; i++) { + if (!enumerate<CheckForDuplicates>(cx, PropertyKey::Int(i), + /* enumerable = */ true)) { + return false; + } + } + } +#ifdef ENABLE_RECORD_TUPLE + else { + Rooted<RecordType*> rec(cx); + if (RecordObject::maybeUnbox(pobj, &rec)) { + Rooted<ArrayObject*> keys(cx, rec->keys()); + + for (size_t i = 0; i < keys->length(); i++) { + JSAtom* key = &keys->getDenseElement(i).toString()->asAtom(); + PropertyKey id = AtomToId(key); + if (!enumerate<CheckForDuplicates>(cx, id, + /* enumerable = */ true)) { + return false; + } + } + + return true; + } else { + mozilla::Maybe<TupleType&> tup = TupleObject::maybeUnbox(pobj); + if (tup) { + uint32_t len = tup->length(); + + for (size_t i = 0; i < len; i++) { + // We expect tuple indices not to get so large that `i` won't + // fit into an `int32_t`. + MOZ_ASSERT(PropertyKey::fitsInInt(i)); + PropertyKey id = PropertyKey::Int(i); + if (!enumerate<CheckForDuplicates>(cx, id, + /* enumerable = */ true)) { + return false; + } + } + + return true; + } + } + } +#endif + + // The code below enumerates shape properties (including sparse elements) so + // if we can ignore those we're done. + if (!iterShapeProperties) { + return true; + } + + // Collect any sparse elements from this object. + bool isIndexed = pobj->isIndexed(); + if (isIndexed) { + // If the dense elements didn't have holes, we don't need to include + // them in the sort. + if (!hasHoles) { + firstElemIndex = props_.length(); + } + + for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) { + jsid id = iter->key(); + uint32_t dummy; + if (IdIsIndex(id, &dummy)) { + if (!enumerate<CheckForDuplicates>(cx, id, iter->enumerable())) { + return false; + } + } + } + + MOZ_ASSERT(firstElemIndex <= props_.length()); + + jsid* ids = props_.begin() + firstElemIndex; + size_t n = props_.length() - firstElemIndex; + + RootedIdVector tmp(cx); + if (!tmp.resize(n)) { + return false; + } + PodCopy(tmp.begin(), ids, n); + + if (!MergeSort(ids, n, tmp.begin(), SortComparatorIntegerIds)) { + return false; + } + } + + size_t initialLength = props_.length(); + + /* Collect all unique property names from this object's shape. */ + bool symbolsFound = false; + for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) { + jsid id = iter->key(); + + if (id.isSymbol()) { + symbolsFound = true; + continue; + } + + uint32_t dummy; + if (isIndexed && IdIsIndex(id, &dummy)) { + continue; + } + + PropertyIndex index = iter->isDataProperty() + ? PropertyIndex::ForSlot(pobj, iter->slot()) + : PropertyIndex::Invalid(); + if (!enumerate<CheckForDuplicates>(cx, id, iter->enumerable(), index)) { + return false; + } + } + reversePropsAndIndicesAfter(initialLength); + + enumerateSymbols = symbolsFound && (flags_ & JSITER_SYMBOLS); + } + + if (enumerateSymbols) { + MOZ_ASSERT(iterShapeProperties); + MOZ_ASSERT(!allocatingIndices()); + + // Do a second pass to collect symbols. The spec requires that all symbols + // appear after all strings in [[OwnPropertyKeys]] for ordinary objects: + // https://tc39.es/ecma262/#sec-ordinaryownpropertykeys + size_t initialLength = props_.length(); + for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) { + jsid id = iter->key(); + if (id.isSymbol()) { + if (!enumerate<CheckForDuplicates>(cx, id, iter->enumerable())) { + return false; + } + } + } + reversePropsAndIndicesAfter(initialLength); + } + + return true; +} + +template <bool CheckForDuplicates> +bool PropertyEnumerator::enumerateProxyProperties(JSContext* cx) { + MOZ_ASSERT(obj_->is<ProxyObject>()); + + RootedIdVector proxyProps(cx); + + if (flags_ & JSITER_HIDDEN || flags_ & JSITER_SYMBOLS) { + // This gets all property keys, both strings and symbols. The call to + // enumerate in the loop below will filter out unwanted keys, per the + // flags. + if (!Proxy::ownPropertyKeys(cx, obj_, &proxyProps)) { + return false; + } + + Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); + for (size_t n = 0, len = proxyProps.length(); n < len; n++) { + bool enumerable = false; + + // We need to filter, if the caller just wants enumerable symbols. + if (!(flags_ & JSITER_HIDDEN)) { + if (!Proxy::getOwnPropertyDescriptor(cx, obj_, proxyProps[n], &desc)) { + return false; + } + enumerable = desc.isSome() && desc->enumerable(); + } + + if (!enumerate<CheckForDuplicates>(cx, proxyProps[n], enumerable)) { + return false; + } + } + + return true; + } + + // Returns enumerable property names (no symbols). + if (!Proxy::getOwnEnumerablePropertyKeys(cx, obj_, &proxyProps)) { + return false; + } + + for (size_t n = 0, len = proxyProps.length(); n < len; n++) { + if (!enumerate<CheckForDuplicates>(cx, proxyProps[n], true)) { + return false; + } + } + + return true; +} + +#ifdef DEBUG + +struct SortComparatorIds { + JSContext* const cx; + + explicit SortComparatorIds(JSContext* cx) : cx(cx) {} + + bool operator()(jsid aArg, jsid bArg, bool* lessOrEqualp) { + RootedId a(cx, aArg); + RootedId b(cx, bArg); + + // Pick an arbitrary order on jsids that is as stable as possible + // across executions. + if (a == b) { + *lessOrEqualp = true; + return true; + } + + enum class KeyType { Void, Int, String, Symbol }; + + auto keyType = [](PropertyKey key) { + if (key.isString()) { + return KeyType::String; + } + if (key.isInt()) { + return KeyType::Int; + } + if (key.isSymbol()) { + return KeyType::Symbol; + } + MOZ_ASSERT(key.isVoid()); + return KeyType::Void; + }; + + if (keyType(a) != keyType(b)) { + *lessOrEqualp = (keyType(a) <= keyType(b)); + return true; + } + + if (a.isInt()) { + *lessOrEqualp = (a.toInt() <= b.toInt()); + return true; + } + + RootedString astr(cx), bstr(cx); + if (a.isSymbol()) { + MOZ_ASSERT(b.isSymbol()); + JS::SymbolCode ca = a.toSymbol()->code(); + JS::SymbolCode cb = b.toSymbol()->code(); + if (ca != cb) { + *lessOrEqualp = uint32_t(ca) <= uint32_t(cb); + return true; + } + MOZ_ASSERT(ca == JS::SymbolCode::PrivateNameSymbol || + ca == JS::SymbolCode::InSymbolRegistry || + ca == JS::SymbolCode::UniqueSymbol); + astr = a.toSymbol()->description(); + bstr = b.toSymbol()->description(); + if (!astr || !bstr) { + *lessOrEqualp = !astr; + return true; + } + + // Fall through to string comparison on the descriptions. The sort + // order is nondeterministic if two different unique symbols have + // the same description. + } else { + astr = IdToString(cx, a); + if (!astr) { + return false; + } + bstr = IdToString(cx, b); + if (!bstr) { + return false; + } + } + + int32_t result; + if (!CompareStrings(cx, astr, bstr, &result)) { + return false; + } + + *lessOrEqualp = (result <= 0); + return true; + } +}; + +#endif /* DEBUG */ + +static void AssertNoEnumerableProperties(NativeObject* obj) { +#ifdef DEBUG + // Verify the object has no enumerable properties if the HasEnumerable + // ObjectFlag is not set. + + MOZ_ASSERT(!obj->hasEnumerableProperty()); + + static constexpr size_t MaxPropsToCheck = 5; + + size_t count = 0; + for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) { + MOZ_ASSERT(!iter->enumerable()); + if (++count > MaxPropsToCheck) { + break; + } + } +#endif // DEBUG +} + +// Typed arrays and classes with an enumerate hook can have extra properties not +// included in the shape's property map or the object's dense elements. +static bool ClassCanHaveExtraEnumeratedProperties(const JSClass* clasp) { + return IsTypedArrayClass(clasp) || clasp->getNewEnumerate() || + clasp->getEnumerate(); +} + +static bool ProtoMayHaveEnumerableProperties(JSObject* obj) { + if (!obj->is<NativeObject>()) { + return true; + } + + JSObject* proto = obj->as<NativeObject>().staticPrototype(); + while (proto) { + if (!proto->is<NativeObject>()) { + return true; + } + NativeObject* nproto = &proto->as<NativeObject>(); + if (nproto->hasEnumerableProperty() || + nproto->getDenseInitializedLength() > 0 || + ClassCanHaveExtraEnumeratedProperties(nproto->getClass())) { + return true; + } + AssertNoEnumerableProperties(nproto); + proto = nproto->staticPrototype(); + } + + return false; +} + +bool PropertyEnumerator::snapshot(JSContext* cx) { + // If we're only interested in enumerable properties and the proto chain has + // no enumerable properties (the common case), we can optimize this to ignore + // the proto chain. This also lets us take advantage of the no-duplicate-check + // optimization below. + if (!(flags_ & JSITER_HIDDEN) && !(flags_ & JSITER_OWNONLY) && + !ProtoMayHaveEnumerableProperties(obj_)) { + flags_ |= JSITER_OWNONLY; + } + + // Don't check for duplicates if we're only interested in own properties. + // This does the right thing for most objects: native objects don't have + // duplicate property ids and we allow the [[OwnPropertyKeys]] proxy trap to + // return duplicates. + // + // The only special case is when the object has a newEnumerate hook: it + // can return duplicate properties and we have to filter them. This is + // handled below. + bool checkForDuplicates = !(flags_ & JSITER_OWNONLY); + + do { + if (obj_->getClass()->getNewEnumerate()) { + markIndicesUnsupported(); + + if (!enumerateExtraProperties(cx)) { + return false; + } + + if (obj_->is<NativeObject>()) { + if (!enumerateNativeProperties(cx, /*checkForDuplicates*/ true)) { + return false; + } + } + + } else if (obj_->is<NativeObject>()) { + // Give the object a chance to resolve all lazy properties + if (JSEnumerateOp enumerateOp = obj_->getClass()->getEnumerate()) { + markIndicesUnsupported(); + if (!enumerateOp(cx, obj_.as<NativeObject>())) { + return false; + } + } + if (!enumerateNativeProperties(cx, checkForDuplicates)) { + return false; + } + } else if (obj_->is<ProxyObject>()) { + markIndicesUnsupported(); + if (checkForDuplicates) { + if (!enumerateProxyProperties<true>(cx)) { + return false; + } + } else { + if (!enumerateProxyProperties<false>(cx)) { + return false; + } + } + } else { + MOZ_CRASH("non-native objects must have an enumerate op"); + } + + if (flags_ & JSITER_OWNONLY) { + break; + } + + if (!GetPrototype(cx, obj_, &obj_)) { + return false; + } + enumeratingProtoChain_ = true; + + // The [[Prototype]] chain might be cyclic. + if (!CheckForInterrupt(cx)) { + return false; + } + } while (obj_ != nullptr); + +#ifdef DEBUG + if (js::SupportDifferentialTesting() && !supportsIndices()) { + /* + * In some cases the enumeration order for an object depends on the + * execution mode (interpreter vs. JIT), especially for native objects + * with a class enumerate hook (where resolving a property changes the + * resulting enumeration order). These aren't really bugs, but the + * differences can change the generated output and confuse correctness + * fuzzers, so we sort the ids if such a fuzzer is running. + * + * We don't do this in the general case because (a) doing so is slow, + * and (b) it also breaks the web, which expects enumeration order to + * follow the order in which properties are added, in certain cases. + * Since ECMA does not specify an enumeration order for objects, both + * behaviors are technically correct to do. + */ + + jsid* ids = props_.begin(); + size_t n = props_.length(); + + RootedIdVector tmp(cx); + if (!tmp.resize(n)) { + return false; + } + PodCopy(tmp.begin(), ids, n); + + if (!MergeSort(ids, n, tmp.begin(), SortComparatorIds(cx))) { + return false; + } + } +#endif + + return true; +} + +JS_PUBLIC_API bool js::GetPropertyKeys(JSContext* cx, HandleObject obj, + unsigned flags, + MutableHandleIdVector props) { + uint32_t validFlags = + flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | + JSITER_SYMBOLSONLY | JSITER_PRIVATE); + + PropertyEnumerator enumerator(cx, obj, validFlags, props); + return enumerator.snapshot(cx); +} + +static inline void RegisterEnumerator(JSContext* cx, NativeIterator* ni) { + MOZ_ASSERT(ni->objectBeingIterated()); + + // Register non-escaping native enumerators (for-in) with the current + // context. + ni->link(cx->compartment()->enumeratorsAddr()); + + MOZ_ASSERT(!ni->isActive()); + ni->markActive(); +} + +static PropertyIteratorObject* NewPropertyIteratorObject(JSContext* cx) { + const JSClass* clasp = &PropertyIteratorObject::class_; + Rooted<SharedShape*> shape( + cx, + SharedShape::getInitialShape(cx, clasp, cx->realm(), TaggedProto(nullptr), + ITERATOR_FINALIZE_KIND)); + if (!shape) { + return nullptr; + } + + JSObject* obj = NativeObject::create( + cx, ITERATOR_FINALIZE_KIND, GetInitialHeap(GenericObject, clasp), shape); + if (!obj) { + return nullptr; + } + + PropertyIteratorObject* res = &obj->as<PropertyIteratorObject>(); + + // CodeGenerator::visitIteratorStartO assumes the iterator object is not + // inside the nursery when deciding whether a barrier is necessary. + MOZ_ASSERT(!js::gc::IsInsideNursery(res)); + return res; +} + +static inline size_t NumTrailingBytes(size_t propertyCount, size_t shapeCount, + bool hasIndices) { + static_assert(alignof(GCPtr<JSLinearString*>) <= alignof(NativeIterator)); + static_assert(alignof(GCPtr<Shape*>) <= alignof(GCPtr<JSLinearString*>)); + static_assert(alignof(PropertyIndex) <= alignof(GCPtr<Shape*>)); + size_t result = propertyCount * sizeof(GCPtr<JSLinearString*>) + + shapeCount * sizeof(GCPtr<Shape*>); + if (hasIndices) { + result += propertyCount * sizeof(PropertyIndex); + } + return result; +} + +static inline size_t AllocationSize(size_t propertyCount, size_t shapeCount, + bool hasIndices) { + return sizeof(NativeIterator) + + NumTrailingBytes(propertyCount, shapeCount, hasIndices); +} + +static PropertyIteratorObject* CreatePropertyIterator( + JSContext* cx, Handle<JSObject*> objBeingIterated, HandleIdVector props, + bool supportsIndices, PropertyIndexVector* indices, + uint32_t cacheableProtoChainLength) { + MOZ_ASSERT_IF(indices, supportsIndices); + if (props.length() > NativeIterator::PropCountLimit) { + ReportAllocationOverflow(cx); + return nullptr; + } + + bool hasIndices = !!indices; + + // If the iterator is cacheable, we store the shape of each object + // along the proto chain in the iterator. If the iterator is not + // cacheable, but has indices, then we store one shape (the shape of + // the object being iterated.) + uint32_t numShapes = cacheableProtoChainLength; + if (numShapes == 0 && hasIndices) { + numShapes = 1; + } + + Rooted<PropertyIteratorObject*> propIter(cx, NewPropertyIteratorObject(cx)); + if (!propIter) { + return nullptr; + } + + void* mem = cx->pod_malloc_with_extra<NativeIterator, uint8_t>( + NumTrailingBytes(props.length(), numShapes, hasIndices)); + if (!mem) { + return nullptr; + } + + // This also registers |ni| with |propIter|. + bool hadError = false; + new (mem) NativeIterator(cx, propIter, objBeingIterated, props, + supportsIndices, indices, numShapes, &hadError); + if (hadError) { + return nullptr; + } + + return propIter; +} + +static HashNumber HashIteratorShape(Shape* shape) { + return DefaultHasher<Shape*>::hash(shape); +} + +/** + * Initialize a fresh NativeIterator. + * + * This definition is a bit tricky: some parts of initializing are fallible, so + * as we initialize, we must carefully keep this in GC-safe state (see + * NativeIterator::trace). + */ +NativeIterator::NativeIterator(JSContext* cx, + Handle<PropertyIteratorObject*> propIter, + Handle<JSObject*> objBeingIterated, + HandleIdVector props, bool supportsIndices, + PropertyIndexVector* indices, uint32_t numShapes, + bool* hadError) + : objectBeingIterated_(objBeingIterated), + iterObj_(propIter), + // NativeIterator initially acts (before full initialization) as if it + // contains no shapes... + shapesEnd_(shapesBegin()), + // ...and no properties. + propertyCursor_( + reinterpret_cast<GCPtr<JSLinearString*>*>(shapesBegin() + numShapes)), + propertiesEnd_(propertyCursor_), + shapesHash_(0), + flagsAndCount_( + initialFlagsAndCount(props.length())) // note: no Flags::Initialized +{ + // If there are shapes, the object and all objects on its prototype chain must + // be native objects. See CanCompareIterableObjectToCache. + MOZ_ASSERT_IF(numShapes > 0, + objBeingIterated && objBeingIterated->is<NativeObject>()); + + MOZ_ASSERT(!*hadError); + + bool hasActualIndices = !!indices; + MOZ_ASSERT_IF(hasActualIndices, indices->length() == props.length()); + + // NOTE: This must be done first thing: The caller can't free `this` on error + // because it has GCPtr fields whose barriers have already fired; the + // store buffer has pointers to them. Only the GC can free `this` (via + // PropertyIteratorObject::finalize). + propIter->initNativeIterator(this); + + // The GC asserts on finalization that `this->allocationSize()` matches the + // `nbytes` passed to `AddCellMemory`. So once these lines run, we must make + // `this->allocationSize()` correct. That means infallibly initializing the + // shapes, and ensuring that indicesState_.allocated() is true if we've + // allocated space for indices. It's OK for the constructor to fail after + // that. + size_t nbytes = AllocationSize(props.length(), numShapes, hasActualIndices); + AddCellMemory(propIter, nbytes, MemoryUse::NativeIterator); + if (supportsIndices) { + if (hasActualIndices) { + // If the string allocation fails, indicesAllocated() must be true + // so that this->allocationSize() is correct. Set it to Disabled. It will + // be updated below. + setIndicesState(NativeIteratorIndices::Disabled); + } else { + // This object supports indices (ie it only has own enumerable + // properties), but we didn't allocate them because we haven't seen a + // consumer yet. We mark the iterator so that potential consumers know to + // request a fresh iterator with indices. + setIndicesState(NativeIteratorIndices::AvailableOnRequest); + } + } + + if (numShapes > 0) { + // Construct shapes into the shapes array. Also compute the shapesHash, + // which incorporates Shape* addresses that could have changed during a GC + // triggered in (among other places) |IdToString| above. + JSObject* pobj = objBeingIterated; + HashNumber shapesHash = 0; + for (uint32_t i = 0; i < numShapes; i++) { + MOZ_ASSERT(pobj->is<NativeObject>()); + Shape* shape = pobj->shape(); + new (shapesEnd_) GCPtr<Shape*>(shape); + shapesEnd_++; + shapesHash = mozilla::AddToHash(shapesHash, HashIteratorShape(shape)); + pobj = pobj->staticPrototype(); + } + shapesHash_ = shapesHash; + + // There are two cases in which we need to store shapes. If this + // iterator is cacheable, we store the shapes for the entire proto + // chain so we can check that the cached iterator is still valid + // (see MacroAssembler::maybeLoadIteratorFromShape). If this iterator + // has indices, then even if it isn't cacheable we need to store the + // shape of the iterated object itself (see IteratorHasIndicesAndBranch). + // In the former case, assert that we're storing the entire proto chain. + MOZ_ASSERT_IF(numShapes > 1, pobj == nullptr); + } + MOZ_ASSERT(static_cast<void*>(shapesEnd_) == propertyCursor_); + + size_t numProps = props.length(); + for (size_t i = 0; i < numProps; i++) { + JSLinearString* str = IdToString(cx, props[i]); + if (!str) { + *hadError = true; + return; + } + new (propertiesEnd_) GCPtr<JSLinearString*>(str); + propertiesEnd_++; + } + + if (hasActualIndices) { + PropertyIndex* cursor = indicesBegin(); + for (size_t i = 0; i < numProps; i++) { + *cursor++ = (*indices)[i]; + } + MOZ_ASSERT(uintptr_t(cursor) == uintptr_t(this) + nbytes); + setIndicesState(NativeIteratorIndices::Valid); + } + + markInitialized(); + + MOZ_ASSERT(!*hadError); +} + +inline size_t NativeIterator::allocationSize() const { + size_t numShapes = shapesEnd() - shapesBegin(); + + return AllocationSize(initialPropertyCount(), numShapes, indicesAllocated()); +} + +/* static */ +bool IteratorHashPolicy::match(PropertyIteratorObject* obj, + const Lookup& lookup) { + NativeIterator* ni = obj->getNativeIterator(); + if (ni->shapesHash() != lookup.shapesHash || + ni->shapeCount() != lookup.numShapes) { + return false; + } + + return ArrayEqual(reinterpret_cast<Shape**>(ni->shapesBegin()), lookup.shapes, + ni->shapeCount()); +} + +static inline bool CanCompareIterableObjectToCache(JSObject* obj) { + if (obj->is<NativeObject>()) { + return obj->as<NativeObject>().getDenseInitializedLength() == 0; + } + return false; +} + +static bool CanStoreInIteratorCache(JSObject* obj) { + do { + MOZ_ASSERT(obj->as<NativeObject>().getDenseInitializedLength() == 0); + + // Typed arrays have indexed properties not captured by the Shape guard. + // Enumerate hooks may add extra properties. + if (MOZ_UNLIKELY(ClassCanHaveExtraEnumeratedProperties(obj->getClass()))) { + return false; + } + + obj = obj->staticPrototype(); + } while (obj); + + return true; +} + +static MOZ_ALWAYS_INLINE PropertyIteratorObject* LookupInIteratorCache( + JSContext* cx, JSObject* obj, uint32_t* cacheableProtoChainLength) { + MOZ_ASSERT(*cacheableProtoChainLength == 0); + + if (obj->shape()->cache().isIterator() && + CanCompareIterableObjectToCache(obj)) { + PropertyIteratorObject* iterobj = obj->shape()->cache().toIterator(); + NativeIterator* ni = iterobj->getNativeIterator(); + MOZ_ASSERT(*ni->shapesBegin() == obj->shape()); + if (!ni->isReusable()) { + return nullptr; + } + + // Verify shapes of proto chain. + JSObject* pobj = obj; + for (GCPtr<Shape*>* s = ni->shapesBegin() + 1; s != ni->shapesEnd(); s++) { + Shape* shape = *s; + pobj = pobj->staticPrototype(); + if (pobj->shape() != shape) { + return nullptr; + } + if (!CanCompareIterableObjectToCache(pobj)) { + return nullptr; + } + } + MOZ_ASSERT(CanStoreInIteratorCache(obj)); + *cacheableProtoChainLength = ni->shapeCount(); + return iterobj; + } + + Vector<Shape*, 8> shapes(cx); + HashNumber shapesHash = 0; + JSObject* pobj = obj; + do { + if (!CanCompareIterableObjectToCache(pobj)) { + return nullptr; + } + + MOZ_ASSERT(pobj->is<NativeObject>()); + Shape* shape = pobj->shape(); + shapesHash = mozilla::AddToHash(shapesHash, HashIteratorShape(shape)); + + if (MOZ_UNLIKELY(!shapes.append(shape))) { + cx->recoverFromOutOfMemory(); + return nullptr; + } + + pobj = pobj->staticPrototype(); + } while (pobj); + + MOZ_ASSERT(!shapes.empty()); + *cacheableProtoChainLength = shapes.length(); + + IteratorHashPolicy::Lookup lookup(shapes.begin(), shapes.length(), + shapesHash); + auto p = ObjectRealm::get(obj).iteratorCache.lookup(lookup); + if (!p) { + return nullptr; + } + + PropertyIteratorObject* iterobj = *p; + MOZ_ASSERT(iterobj->compartment() == cx->compartment()); + + NativeIterator* ni = iterobj->getNativeIterator(); + if (!ni->isReusable()) { + return nullptr; + } + + return iterobj; +} + +[[nodiscard]] static bool StoreInIteratorCache( + JSContext* cx, JSObject* obj, PropertyIteratorObject* iterobj) { + MOZ_ASSERT(CanStoreInIteratorCache(obj)); + + NativeIterator* ni = iterobj->getNativeIterator(); + MOZ_ASSERT(ni->shapeCount() > 0); + + obj->shape()->maybeCacheIterator(cx, iterobj); + + IteratorHashPolicy::Lookup lookup( + reinterpret_cast<Shape**>(ni->shapesBegin()), ni->shapeCount(), + ni->shapesHash()); + + ObjectRealm::IteratorCache& cache = ObjectRealm::get(obj).iteratorCache; + bool ok; + auto p = cache.lookupForAdd(lookup); + if (MOZ_LIKELY(!p)) { + ok = cache.add(p, iterobj); + } else { + // If we weren't able to use an existing cached iterator, just + // replace it. + cache.remove(p); + ok = cache.relookupOrAdd(p, lookup, iterobj); + } + if (!ok) { + ReportOutOfMemory(cx); + return false; + } + + return true; +} + +bool js::EnumerateProperties(JSContext* cx, HandleObject obj, + MutableHandleIdVector props) { + MOZ_ASSERT(props.empty()); + + if (MOZ_UNLIKELY(obj->is<ProxyObject>())) { + return Proxy::enumerate(cx, obj, props); + } + + uint32_t flags = 0; + PropertyEnumerator enumerator(cx, obj, flags, props); + return enumerator.snapshot(cx); +} + +#ifdef DEBUG +static bool IndicesAreValid(NativeObject* obj, NativeIterator* ni) { + MOZ_ASSERT(ni->hasValidIndices()); + size_t numDenseElements = obj->getDenseInitializedLength(); + size_t numFixedSlots = obj->numFixedSlots(); + const Value* elements = obj->getDenseElements(); + + GCPtr<JSLinearString*>* keys = ni->propertiesBegin(); + PropertyIndex* indices = ni->indicesBegin(); + + for (uint32_t i = 0; i < ni->numKeys(); i++) { + PropertyIndex index = indices[i]; + switch (index.kind()) { + case PropertyIndex::Kind::Element: + // Verify that the dense element exists and is not a hole. + if (index.index() >= numDenseElements || + elements[index.index()].isMagic(JS_ELEMENTS_HOLE)) { + return false; + } + break; + case PropertyIndex::Kind::FixedSlot: { + // Verify that the slot exists and is an enumerable data property with + // the expected key. + Maybe<PropertyInfo> prop = + obj->lookupPure(AtomToId(&keys[i]->asAtom())); + if (!prop.isSome() || !prop->hasSlot() || !prop->enumerable() || + !prop->isDataProperty() || prop->slot() != index.index()) { + return false; + } + break; + } + case PropertyIndex::Kind::DynamicSlot: { + // Verify that the slot exists and is an enumerable data property with + // the expected key. + Maybe<PropertyInfo> prop = + obj->lookupPure(AtomToId(&keys[i]->asAtom())); + if (!prop.isSome() || !prop->hasSlot() || !prop->enumerable() || + !prop->isDataProperty() || + prop->slot() - numFixedSlots != index.index()) { + return false; + } + break; + } + case PropertyIndex::Kind::Invalid: + return false; + } + } + return true; +} +#endif + +template <bool WantIndices> +static PropertyIteratorObject* GetIteratorImpl(JSContext* cx, + HandleObject obj) { + MOZ_ASSERT(!obj->is<PropertyIteratorObject>()); + MOZ_ASSERT(cx->compartment() == obj->compartment(), + "We may end up allocating shapes in the wrong zone!"); + + uint32_t cacheableProtoChainLength = 0; + if (PropertyIteratorObject* iterobj = + LookupInIteratorCache(cx, obj, &cacheableProtoChainLength)) { + NativeIterator* ni = iterobj->getNativeIterator(); + bool recreateWithIndices = WantIndices && ni->indicesAvailableOnRequest(); + if (!recreateWithIndices) { + MOZ_ASSERT_IF(WantIndices && ni->hasValidIndices(), + IndicesAreValid(&obj->as<NativeObject>(), ni)); + ni->initObjectBeingIterated(*obj); + RegisterEnumerator(cx, ni); + return iterobj; + } + } + + if (cacheableProtoChainLength > 0 && !CanStoreInIteratorCache(obj)) { + cacheableProtoChainLength = 0; + } + + RootedIdVector keys(cx); + PropertyIndexVector indices(cx); + bool supportsIndices = false; + + if (MOZ_UNLIKELY(obj->is<ProxyObject>())) { + if (!Proxy::enumerate(cx, obj, &keys)) { + return nullptr; + } + } else { + uint32_t flags = 0; + PropertyEnumerator enumerator(cx, obj, flags, &keys, &indices); + if (!enumerator.snapshot(cx)) { + return nullptr; + } + supportsIndices = enumerator.supportsIndices(); + MOZ_ASSERT_IF(WantIndices && supportsIndices, + keys.length() == indices.length()); + } + + // If the object has dense elements, mark the dense elements as + // maybe-in-iteration. + // + // The iterator is a snapshot so if indexed properties are added after this + // point we don't need to do anything. However, the object might have sparse + // elements now that can be densified later. To account for this, we set the + // maybe-in-iteration flag also in NativeObject::maybeDensifySparseElements. + // + // In debug builds, AssertDenseElementsNotIterated is used to check the flag + // is set correctly. + if (obj->is<NativeObject>() && + obj->as<NativeObject>().getDenseInitializedLength() > 0) { + obj->as<NativeObject>().markDenseElementsMaybeInIteration(); + } + + PropertyIndexVector* indicesPtr = + WantIndices && supportsIndices ? &indices : nullptr; + PropertyIteratorObject* iterobj = CreatePropertyIterator( + cx, obj, keys, supportsIndices, indicesPtr, cacheableProtoChainLength); + if (!iterobj) { + return nullptr; + } + RegisterEnumerator(cx, iterobj->getNativeIterator()); + + cx->check(iterobj); + MOZ_ASSERT_IF( + WantIndices && supportsIndices, + IndicesAreValid(&obj->as<NativeObject>(), iterobj->getNativeIterator())); + +#ifdef DEBUG + if (obj->is<NativeObject>()) { + if (PrototypeMayHaveIndexedProperties(&obj->as<NativeObject>())) { + iterobj->getNativeIterator()->setMaybeHasIndexedPropertiesFromProto(); + } + } +#endif + + // Cache the iterator object. + if (cacheableProtoChainLength > 0) { + if (!StoreInIteratorCache(cx, obj, iterobj)) { + return nullptr; + } + } + + return iterobj; +} + +PropertyIteratorObject* js::GetIterator(JSContext* cx, HandleObject obj) { + return GetIteratorImpl<false>(cx, obj); +} + +PropertyIteratorObject* js::GetIteratorWithIndices(JSContext* cx, + HandleObject obj) { + return GetIteratorImpl<true>(cx, obj); +} + +PropertyIteratorObject* js::LookupInIteratorCache(JSContext* cx, + HandleObject obj) { + uint32_t dummy = 0; + return LookupInIteratorCache(cx, obj, &dummy); +} + +// ES 2017 draft 7.4.7. +PlainObject* js::CreateIterResultObject(JSContext* cx, HandleValue value, + bool done) { + // Step 1 (implicit). + + // Step 2. + Rooted<PlainObject*> templateObject( + cx, GlobalObject::getOrCreateIterResultTemplateObject(cx)); + if (!templateObject) { + return nullptr; + } + + PlainObject* resultObj = PlainObject::createWithTemplate(cx, templateObject); + if (!resultObj) { + return nullptr; + } + + // Step 3. + resultObj->setSlot(GlobalObject::IterResultObjectValueSlot, value); + + // Step 4. + resultObj->setSlot(GlobalObject::IterResultObjectDoneSlot, + done ? TrueHandleValue : FalseHandleValue); + + // Step 5. + return resultObj; +} + +PlainObject* GlobalObject::getOrCreateIterResultTemplateObject(JSContext* cx) { + HeapPtr<PlainObject*>& obj = cx->global()->data().iterResultTemplate; + if (obj) { + return obj; + } + + PlainObject* templateObj = + createIterResultTemplateObject(cx, WithObjectPrototype::Yes); + obj.init(templateObj); + return obj; +} + +/* static */ +PlainObject* GlobalObject::getOrCreateIterResultWithoutPrototypeTemplateObject( + JSContext* cx) { + HeapPtr<PlainObject*>& obj = + cx->global()->data().iterResultWithoutPrototypeTemplate; + if (obj) { + return obj; + } + + PlainObject* templateObj = + createIterResultTemplateObject(cx, WithObjectPrototype::No); + obj.init(templateObj); + return obj; +} + +/* static */ +PlainObject* GlobalObject::createIterResultTemplateObject( + JSContext* cx, WithObjectPrototype withProto) { + // Create template plain object + Rooted<PlainObject*> templateObject( + cx, withProto == WithObjectPrototype::Yes + ? NewPlainObject(cx, TenuredObject) + : NewPlainObjectWithProto(cx, nullptr)); + if (!templateObject) { + return nullptr; + } + + // Set dummy `value` property + if (!NativeDefineDataProperty(cx, templateObject, cx->names().value, + UndefinedHandleValue, JSPROP_ENUMERATE)) { + return nullptr; + } + + // Set dummy `done` property + if (!NativeDefineDataProperty(cx, templateObject, cx->names().done, + TrueHandleValue, JSPROP_ENUMERATE)) { + return nullptr; + } + +#ifdef DEBUG + // Make sure that the properties are in the right slots. + ShapePropertyIter<NoGC> iter(templateObject->shape()); + MOZ_ASSERT(iter->slot() == GlobalObject::IterResultObjectDoneSlot && + iter->key() == NameToId(cx->names().done)); + iter++; + MOZ_ASSERT(iter->slot() == GlobalObject::IterResultObjectValueSlot && + iter->key() == NameToId(cx->names().value)); +#endif + + return templateObject; +} + +/*** Iterator objects *******************************************************/ + +size_t PropertyIteratorObject::sizeOfMisc( + mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(getNativeIterator()); +} + +void PropertyIteratorObject::trace(JSTracer* trc, JSObject* obj) { + if (NativeIterator* ni = + obj->as<PropertyIteratorObject>().getNativeIterator()) { + ni->trace(trc); + } +} + +void PropertyIteratorObject::finalize(JS::GCContext* gcx, JSObject* obj) { + if (NativeIterator* ni = + obj->as<PropertyIteratorObject>().getNativeIterator()) { + gcx->free_(obj, ni, ni->allocationSize(), MemoryUse::NativeIterator); + } +} + +const JSClassOps PropertyIteratorObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + finalize, // finalize + nullptr, // call + nullptr, // construct + trace, // trace +}; + +const JSClass PropertyIteratorObject::class_ = { + "Iterator", + JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE, + &PropertyIteratorObject::classOps_}; + +static const JSClass ArrayIteratorPrototypeClass = {"Array Iterator", 0}; + +enum { + ArrayIteratorSlotIteratedObject, + ArrayIteratorSlotNextIndex, + ArrayIteratorSlotItemKind, + ArrayIteratorSlotCount +}; + +const JSClass ArrayIteratorObject::class_ = { + "Array Iterator", JSCLASS_HAS_RESERVED_SLOTS(ArrayIteratorSlotCount)}; + +ArrayIteratorObject* js::NewArrayIteratorTemplate(JSContext* cx) { + RootedObject proto( + cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global())); + if (!proto) { + return nullptr; + } + + return NewTenuredObjectWithGivenProto<ArrayIteratorObject>(cx, proto); +} + +ArrayIteratorObject* js::NewArrayIterator(JSContext* cx) { + RootedObject proto( + cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global())); + if (!proto) { + return nullptr; + } + + return NewObjectWithGivenProto<ArrayIteratorObject>(cx, proto); +} + +static const JSFunctionSpec array_iterator_methods[] = { + JS_SELF_HOSTED_FN("next", "ArrayIteratorNext", 0, 0), JS_FS_END}; + +static const JSClass StringIteratorPrototypeClass = {"String Iterator", 0}; + +enum { + StringIteratorSlotIteratedObject, + StringIteratorSlotNextIndex, + StringIteratorSlotCount +}; + +const JSClass StringIteratorObject::class_ = { + "String Iterator", JSCLASS_HAS_RESERVED_SLOTS(StringIteratorSlotCount)}; + +static const JSFunctionSpec string_iterator_methods[] = { + JS_SELF_HOSTED_FN("next", "StringIteratorNext", 0, 0), JS_FS_END}; + +StringIteratorObject* js::NewStringIteratorTemplate(JSContext* cx) { + RootedObject proto( + cx, GlobalObject::getOrCreateStringIteratorPrototype(cx, cx->global())); + if (!proto) { + return nullptr; + } + + return NewTenuredObjectWithGivenProto<StringIteratorObject>(cx, proto); +} + +StringIteratorObject* js::NewStringIterator(JSContext* cx) { + RootedObject proto( + cx, GlobalObject::getOrCreateStringIteratorPrototype(cx, cx->global())); + if (!proto) { + return nullptr; + } + + return NewObjectWithGivenProto<StringIteratorObject>(cx, proto); +} + +static const JSClass RegExpStringIteratorPrototypeClass = { + "RegExp String Iterator", 0}; + +enum { + // The regular expression used for iteration. May hold the original RegExp + // object when it is reused instead of a new RegExp object. + RegExpStringIteratorSlotRegExp, + + // The String value being iterated upon. + RegExpStringIteratorSlotString, + + // The source string of the original RegExp object. Used to validate we can + // reuse the original RegExp object for matching. + RegExpStringIteratorSlotSource, + + // The flags of the original RegExp object. + RegExpStringIteratorSlotFlags, + + // When non-negative, this slot holds the current lastIndex position when + // reusing the original RegExp object for matching. When set to |-1|, the + // iterator has finished. When set to any other negative value, the + // iterator is not yet exhausted and we're not on the fast path and we're + // not reusing the input RegExp object. + RegExpStringIteratorSlotLastIndex, + + RegExpStringIteratorSlotCount +}; + +static_assert(RegExpStringIteratorSlotRegExp == + REGEXP_STRING_ITERATOR_REGEXP_SLOT, + "RegExpStringIteratorSlotRegExp must match self-hosting define " + "for regexp slot."); +static_assert(RegExpStringIteratorSlotString == + REGEXP_STRING_ITERATOR_STRING_SLOT, + "RegExpStringIteratorSlotString must match self-hosting define " + "for string slot."); +static_assert(RegExpStringIteratorSlotSource == + REGEXP_STRING_ITERATOR_SOURCE_SLOT, + "RegExpStringIteratorSlotString must match self-hosting define " + "for source slot."); +static_assert(RegExpStringIteratorSlotFlags == + REGEXP_STRING_ITERATOR_FLAGS_SLOT, + "RegExpStringIteratorSlotFlags must match self-hosting define " + "for flags slot."); +static_assert(RegExpStringIteratorSlotLastIndex == + REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, + "RegExpStringIteratorSlotLastIndex must match self-hosting " + "define for lastIndex slot."); + +const JSClass RegExpStringIteratorObject::class_ = { + "RegExp String Iterator", + JSCLASS_HAS_RESERVED_SLOTS(RegExpStringIteratorSlotCount)}; + +static const JSFunctionSpec regexp_string_iterator_methods[] = { + JS_SELF_HOSTED_FN("next", "RegExpStringIteratorNext", 0, 0), + + JS_FS_END}; + +RegExpStringIteratorObject* js::NewRegExpStringIteratorTemplate(JSContext* cx) { + RootedObject proto(cx, GlobalObject::getOrCreateRegExpStringIteratorPrototype( + cx, cx->global())); + if (!proto) { + return nullptr; + } + + return NewTenuredObjectWithGivenProto<RegExpStringIteratorObject>(cx, proto); +} + +RegExpStringIteratorObject* js::NewRegExpStringIterator(JSContext* cx) { + RootedObject proto(cx, GlobalObject::getOrCreateRegExpStringIteratorPrototype( + cx, cx->global())); + if (!proto) { + return nullptr; + } + + return NewObjectWithGivenProto<RegExpStringIteratorObject>(cx, proto); +} + +// static +PropertyIteratorObject* GlobalObject::getOrCreateEmptyIterator(JSContext* cx) { + if (!cx->global()->data().emptyIterator) { + RootedIdVector props(cx); // Empty + PropertyIteratorObject* iter = + CreatePropertyIterator(cx, nullptr, props, false, nullptr, 0); + if (!iter) { + return nullptr; + } + iter->getNativeIterator()->markEmptyIteratorSingleton(); + cx->global()->data().emptyIterator.init(iter); + } + return cx->global()->data().emptyIterator; +} + +PropertyIteratorObject* js::ValueToIterator(JSContext* cx, HandleValue vp) { + RootedObject obj(cx); + if (vp.isObject()) { + /* Common case. */ + obj = &vp.toObject(); + } else if (vp.isNullOrUndefined()) { + /* + * Enumerating over null and undefined gives an empty enumerator, so + * that |for (var p in <null or undefined>) <loop>;| never executes + * <loop>, per ES5 12.6.4. + */ + return GlobalObject::getOrCreateEmptyIterator(cx); + } else { + obj = ToObject(cx, vp); + if (!obj) { + return nullptr; + } + } + + return GetIterator(cx, obj); +} + +void js::CloseIterator(JSObject* obj) { + if (!obj->is<PropertyIteratorObject>()) { + return; + } + + // Remove iterator from the active list, which is a stack. The shared iterator + // used for for-in with null/undefined is immutable and unlinked. + + NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator(); + if (ni->isEmptyIteratorSingleton()) { + return; + } + + ni->unlink(); + + MOZ_ASSERT(ni->isActive()); + ni->markInactive(); + + ni->clearObjectBeingIterated(); + + // Reset the enumerator; it may still be in the cached iterators for + // this thread and can be reused. + ni->resetPropertyCursorForReuse(); +} + +bool js::IteratorCloseForException(JSContext* cx, HandleObject obj) { + MOZ_ASSERT(cx->isExceptionPending()); + + bool isClosingGenerator = cx->isClosingGenerator(); + JS::AutoSaveExceptionState savedExc(cx); + + // Implements IteratorClose (ES 7.4.6) for exception unwinding. See + // also the bytecode generated by BytecodeEmitter::emitIteratorClose. + + // Step 3. + // + // Get the "return" method. + RootedValue returnMethod(cx); + if (!GetProperty(cx, obj, obj, cx->names().return_, &returnMethod)) { + return false; + } + + // Step 4. + // + // Do nothing if "return" is null or undefined. Throw a TypeError if the + // method is not IsCallable. + if (returnMethod.isNullOrUndefined()) { + return true; + } + if (!IsCallable(returnMethod)) { + return ReportIsNotFunction(cx, returnMethod); + } + + // Step 5, 6, 8. + // + // Call "return" if it is not null or undefined. + RootedValue rval(cx); + bool ok = Call(cx, returnMethod, obj, &rval); + if (isClosingGenerator) { + // Closing an iterator is implemented as an exception, but in spec + // terms it is a Completion value with [[Type]] return. In this case + // we *do* care if the call threw and if it returned an object. + if (!ok) { + return false; + } + if (!rval.isObject()) { + return ThrowCheckIsObject(cx, CheckIsObjectKind::IteratorReturn); + } + } else { + // We don't care if the call threw or that it returned an Object, as + // Step 6 says if IteratorClose is being called during a throw, the + // original throw has primacy. + savedExc.restore(); + } + + return true; +} + +void js::UnwindIteratorForUncatchableException(JSObject* obj) { + if (obj->is<PropertyIteratorObject>()) { + NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator(); + if (ni->isEmptyIteratorSingleton()) { + return; + } + ni->unlink(); + } +} + +static bool SuppressDeletedProperty(JSContext* cx, NativeIterator* ni, + HandleObject obj, + Handle<JSLinearString*> str) { + if (ni->objectBeingIterated() != obj) { + return true; + } + + ni->disableIndices(); + + // Optimization for the following common case: + // + // for (var p in o) { + // delete o[p]; + // } + // + // Note that usually both strings will be atoms so we only check for pointer + // equality here. + if (ni->previousPropertyWas(str)) { + return true; + } + + while (true) { + bool restart = false; + + // Check whether id is still to come. + GCPtr<JSLinearString*>* const cursor = ni->nextProperty(); + GCPtr<JSLinearString*>* const end = ni->propertiesEnd(); + for (GCPtr<JSLinearString*>* idp = cursor; idp < end; ++idp) { + // Common case: both strings are atoms. + if ((*idp)->isAtom() && str->isAtom()) { + if (*idp != str) { + continue; + } + } else { + if (!EqualStrings(*idp, str)) { + continue; + } + } + + // Check whether another property along the prototype chain became + // visible as a result of this deletion. + RootedObject proto(cx); + if (!GetPrototype(cx, obj, &proto)) { + return false; + } + if (proto) { + RootedId id(cx); + RootedValue idv(cx, StringValue(*idp)); + if (!PrimitiveValueToId<CanGC>(cx, idv, &id)) { + return false; + } + + Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); + RootedObject holder(cx); + if (!GetPropertyDescriptor(cx, proto, id, &desc, &holder)) { + return false; + } + + if (desc.isSome() && desc->enumerable()) { + continue; + } + } + + // If GetPropertyDescriptor above removed a property from ni, start + // over. + if (end != ni->propertiesEnd() || cursor != ni->nextProperty()) { + restart = true; + break; + } + + // No property along the prototype chain stepped in to take the + // property's place, so go ahead and delete id from the list. + // If it is the next property to be enumerated, just skip it. + if (idp == cursor) { + ni->incCursor(); + } else { + for (GCPtr<JSLinearString*>* p = idp; p + 1 != end; p++) { + *p = *(p + 1); + } + + ni->trimLastProperty(); + } + + ni->markHasUnvisitedPropertyDeletion(); + return true; + } + + if (!restart) { + return true; + } + } +} + +/* + * Suppress enumeration of deleted properties. This function must be called + * when a property is deleted and there might be active enumerators. + * + * We maintain a list of active non-escaping for-in enumerators. To suppress + * a property, we check whether each active enumerator contains the (obj, id) + * pair and has not yet enumerated |id|. If so, and |id| is the next property, + * we simply advance the cursor. Otherwise, we delete |id| from the list. + * + * We do not suppress enumeration of a property deleted along an object's + * prototype chain. Only direct deletions on the object are handled. + */ +static bool SuppressDeletedPropertyHelper(JSContext* cx, HandleObject obj, + Handle<JSLinearString*> str) { + NativeIteratorListIter iter(obj->compartment()->enumeratorsAddr()); + while (!iter.done()) { + NativeIterator* ni = iter.next(); + if (!SuppressDeletedProperty(cx, ni, obj, str)) { + return false; + } + } + + return true; +} + +bool js::SuppressDeletedProperty(JSContext* cx, HandleObject obj, jsid id) { + if (MOZ_LIKELY(!obj->compartment()->objectMaybeInIteration(obj))) { + return true; + } + + if (id.isSymbol()) { + return true; + } + + Rooted<JSLinearString*> str(cx, IdToString(cx, id)); + if (!str) { + return false; + } + return SuppressDeletedPropertyHelper(cx, obj, str); +} + +bool js::SuppressDeletedElement(JSContext* cx, HandleObject obj, + uint32_t index) { + if (MOZ_LIKELY(!obj->compartment()->objectMaybeInIteration(obj))) { + return true; + } + + RootedId id(cx); + if (!IndexToId(cx, index, &id)) { + return false; + } + + Rooted<JSLinearString*> str(cx, IdToString(cx, id)); + if (!str) { + return false; + } + return SuppressDeletedPropertyHelper(cx, obj, str); +} + +#ifdef DEBUG +void js::AssertDenseElementsNotIterated(NativeObject* obj) { + // Search for active iterators for |obj| and assert they don't contain any + // property keys that are dense elements. This is used to check correctness + // of the MAYBE_IN_ITERATION flag on ObjectElements. + // + // Ignore iterators that may contain indexed properties from objects on the + // prototype chain, as that can result in false positives. See bug 1656744. + + // Limit the number of properties we check to avoid slowing down debug builds + // too much. + static constexpr uint32_t MaxPropsToCheck = 10; + uint32_t propsChecked = 0; + + NativeIteratorListIter iter(obj->compartment()->enumeratorsAddr()); + while (!iter.done()) { + NativeIterator* ni = iter.next(); + if (ni->objectBeingIterated() == obj && + !ni->maybeHasIndexedPropertiesFromProto()) { + for (GCPtr<JSLinearString*>* idp = ni->nextProperty(); + idp < ni->propertiesEnd(); ++idp) { + uint32_t index; + if (idp->get()->isIndex(&index)) { + MOZ_ASSERT(!obj->containsDenseElement(index)); + } + if (++propsChecked > MaxPropsToCheck) { + return; + } + } + } + } +} +#endif + +static const JSFunctionSpec iterator_methods[] = { + JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0), JS_FS_END}; + +static const JSFunctionSpec iterator_static_methods[] = { + JS_SELF_HOSTED_FN("from", "IteratorFrom", 1, 0), JS_FS_END}; + +// These methods are only attached to Iterator.prototype when the +// Iterator Helpers feature is enabled. +static const JSFunctionSpec iterator_methods_with_helpers[] = { + JS_SELF_HOSTED_FN("map", "IteratorMap", 1, 0), + JS_SELF_HOSTED_FN("filter", "IteratorFilter", 1, 0), + JS_SELF_HOSTED_FN("take", "IteratorTake", 1, 0), + JS_SELF_HOSTED_FN("drop", "IteratorDrop", 1, 0), + JS_SELF_HOSTED_FN("asIndexedPairs", "IteratorAsIndexedPairs", 0, 0), + JS_SELF_HOSTED_FN("flatMap", "IteratorFlatMap", 1, 0), + JS_SELF_HOSTED_FN("reduce", "IteratorReduce", 1, 0), + JS_SELF_HOSTED_FN("toArray", "IteratorToArray", 0, 0), + JS_SELF_HOSTED_FN("forEach", "IteratorForEach", 1, 0), + JS_SELF_HOSTED_FN("some", "IteratorSome", 1, 0), + JS_SELF_HOSTED_FN("every", "IteratorEvery", 1, 0), + JS_SELF_HOSTED_FN("find", "IteratorFind", 1, 0), + JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0), + JS_FS_END}; + +/* static */ +bool GlobalObject::initIteratorProto(JSContext* cx, + Handle<GlobalObject*> global) { + if (global->hasBuiltinProto(ProtoKind::IteratorProto)) { + return true; + } + + RootedObject proto( + cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global)); + if (!proto) { + return false; + } + + // %IteratorPrototype%.map.[[Prototype]] is %Generator% and + // %Generator%.prototype.[[Prototype]] is %IteratorPrototype%. + // Populate the slot early, to prevent runaway mutual recursion. + global->initBuiltinProto(ProtoKind::IteratorProto, proto); + + if (!DefinePropertiesAndFunctions(cx, proto, nullptr, iterator_methods)) { + // In this case, we leave a partially initialized object in the + // slot. There's no obvious way to do better, since this object may already + // be in the prototype chain of %GeneratorPrototype%. + return false; + } + + return true; +} + +/* static */ +template <GlobalObject::ProtoKind Kind, const JSClass* ProtoClass, + const JSFunctionSpec* Methods> +bool GlobalObject::initObjectIteratorProto(JSContext* cx, + Handle<GlobalObject*> global, + Handle<JSAtom*> tag) { + if (global->hasBuiltinProto(Kind)) { + return true; + } + + RootedObject iteratorProto( + cx, GlobalObject::getOrCreateIteratorPrototype(cx, global)); + if (!iteratorProto) { + return false; + } + + RootedObject proto(cx, GlobalObject::createBlankPrototypeInheriting( + cx, ProtoClass, iteratorProto)); + if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, Methods) || + (tag && !DefineToStringTag(cx, proto, tag))) { + return false; + } + + global->initBuiltinProto(Kind, proto); + return true; +} + +/* static */ +NativeObject* GlobalObject::getOrCreateArrayIteratorPrototype( + JSContext* cx, Handle<GlobalObject*> global) { + return MaybeNativeObject(getOrCreateBuiltinProto( + cx, global, ProtoKind::ArrayIteratorProto, + cx->names().ArrayIterator.toHandle(), + initObjectIteratorProto<ProtoKind::ArrayIteratorProto, + &ArrayIteratorPrototypeClass, + array_iterator_methods>)); +} + +/* static */ +JSObject* GlobalObject::getOrCreateStringIteratorPrototype( + JSContext* cx, Handle<GlobalObject*> global) { + return getOrCreateBuiltinProto( + cx, global, ProtoKind::StringIteratorProto, + cx->names().StringIterator.toHandle(), + initObjectIteratorProto<ProtoKind::StringIteratorProto, + &StringIteratorPrototypeClass, + string_iterator_methods>); +} + +/* static */ +JSObject* GlobalObject::getOrCreateRegExpStringIteratorPrototype( + JSContext* cx, Handle<GlobalObject*> global) { + return getOrCreateBuiltinProto( + cx, global, ProtoKind::RegExpStringIteratorProto, + cx->names().RegExpStringIterator.toHandle(), + initObjectIteratorProto<ProtoKind::RegExpStringIteratorProto, + &RegExpStringIteratorPrototypeClass, + regexp_string_iterator_methods>); +} + +// Iterator Helper Proposal 2.1.3.1 Iterator() +// https://tc39.es/proposal-iterator-helpers/#sec-iterator as of revision +// ed6e15a +static bool IteratorConstructor(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, js_Iterator_str)) { + return false; + } + // Throw TypeError if NewTarget is the active function object, preventing the + // Iterator constructor from being used directly. + if (args.callee() == args.newTarget().toObject()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_BOGUS_CONSTRUCTOR, js_Iterator_str); + return false; + } + + // Step 2. + RootedObject proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Iterator, &proto)) { + return false; + } + + JSObject* obj = NewObjectWithClassProto<IteratorObject>(cx, proto); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +static const ClassSpec IteratorObjectClassSpec = { + GenericCreateConstructor<IteratorConstructor, 0, gc::AllocKind::FUNCTION>, + GenericCreatePrototype<IteratorObject>, + iterator_static_methods, + nullptr, + iterator_methods_with_helpers, + nullptr, + nullptr, +}; + +const JSClass IteratorObject::class_ = { + js_Iterator_str, + JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator), + JS_NULL_CLASS_OPS, + &IteratorObjectClassSpec, +}; + +const JSClass IteratorObject::protoClass_ = { + "Iterator.prototype", + JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator), + JS_NULL_CLASS_OPS, + &IteratorObjectClassSpec, +}; + +// Set up WrapForValidIteratorObject class and its prototype. +static const JSFunctionSpec wrap_for_valid_iterator_methods[] = { + JS_SELF_HOSTED_FN("next", "WrapForValidIteratorNext", 1, 0), + JS_SELF_HOSTED_FN("return", "WrapForValidIteratorReturn", 1, 0), + JS_SELF_HOSTED_FN("throw", "WrapForValidIteratorThrow", 1, 0), + JS_FS_END, +}; + +static const JSClass WrapForValidIteratorPrototypeClass = { + "Wrap For Valid Iterator", 0}; + +const JSClass WrapForValidIteratorObject::class_ = { + "Wrap For Valid Iterator", + JSCLASS_HAS_RESERVED_SLOTS(WrapForValidIteratorObject::SlotCount), +}; + +/* static */ +NativeObject* GlobalObject::getOrCreateWrapForValidIteratorPrototype( + JSContext* cx, Handle<GlobalObject*> global) { + return MaybeNativeObject(getOrCreateBuiltinProto( + cx, global, ProtoKind::WrapForValidIteratorProto, + Handle<JSAtom*>(nullptr), + initObjectIteratorProto<ProtoKind::WrapForValidIteratorProto, + &WrapForValidIteratorPrototypeClass, + wrap_for_valid_iterator_methods>)); +} + +WrapForValidIteratorObject* js::NewWrapForValidIterator(JSContext* cx) { + RootedObject proto(cx, GlobalObject::getOrCreateWrapForValidIteratorPrototype( + cx, cx->global())); + if (!proto) { + return nullptr; + } + return NewObjectWithGivenProto<WrapForValidIteratorObject>(cx, proto); +} + +// Common iterator object returned by Iterator Helper methods. +static const JSFunctionSpec iterator_helper_methods[] = { + JS_SELF_HOSTED_FN("next", "IteratorHelperNext", 1, 0), + JS_SELF_HOSTED_FN("return", "IteratorHelperReturn", 1, 0), + JS_SELF_HOSTED_FN("throw", "IteratorHelperThrow", 1, 0), JS_FS_END}; + +static const JSClass IteratorHelperPrototypeClass = {"Iterator Helper", 0}; + +const JSClass IteratorHelperObject::class_ = { + "Iterator Helper", + JSCLASS_HAS_RESERVED_SLOTS(IteratorHelperObject::SlotCount), +}; + +/* static */ +NativeObject* GlobalObject::getOrCreateIteratorHelperPrototype( + JSContext* cx, Handle<GlobalObject*> global) { + return MaybeNativeObject(getOrCreateBuiltinProto( + cx, global, ProtoKind::IteratorHelperProto, Handle<JSAtom*>(nullptr), + initObjectIteratorProto<ProtoKind::IteratorHelperProto, + &IteratorHelperPrototypeClass, + iterator_helper_methods>)); +} + +IteratorHelperObject* js::NewIteratorHelper(JSContext* cx) { + RootedObject proto( + cx, GlobalObject::getOrCreateIteratorHelperPrototype(cx, cx->global())); + if (!proto) { + return nullptr; + } + return NewObjectWithGivenProto<IteratorHelperObject>(cx, proto); +} + +bool js::IterableToArray(JSContext* cx, HandleValue iterable, + MutableHandle<ArrayObject*> array) { + JS::ForOfIterator iterator(cx); + if (!iterator.init(iterable, JS::ForOfIterator::ThrowOnNonIterable)) { + return false; + } + + array.set(NewDenseEmptyArray(cx)); + if (!array) { + return false; + } + + RootedValue nextValue(cx); + while (true) { + bool done; + if (!iterator.next(&nextValue, &done)) { + return false; + } + if (done) { + break; + } + + if (!NewbornArrayPush(cx, array, nextValue)) { + return false; + } + } + return true; +} |