commit 38ad18e48ec3aba49a6e6a523d0e167c5654f783
parent 92ba58a5ebe5e191cf8956d02fa17116ed0facbc
Author: alexical <dothayer@mozilla.com>
Date: Wed, 1 Oct 2025 18:32:47 +0000
Bug 1868857 - Mark deleted props instead of removing in NativeIterator r=jandem,jonco
This is adapted from Iain's patch in the linked bug. We want
to be able to scalar replace Object.keys when its result is just
going to be iterated over and not escape. We should be able to
use all the NativeIterator infrastructure for this purpose, but
a critical difference is that if properties are deleted in the
loop, a for in loop will skip those properties, while Object.keys,
being a snapshot, won't. This patch allows us to support both
cases by marking deleted properties with a flag which will be
utilized to filter when iterating normally, and ignored when
iterating an Object.keys result.
Differential Revision: https://phabricator.services.mozilla.com/D264287
Diffstat:
8 files changed, 197 insertions(+), 131 deletions(-)
diff --git a/js/src/builtin/Array.cpp b/js/src/builtin/Array.cpp
@@ -5548,16 +5548,16 @@ ArrayObject* js::NewDenseCopiedArray(
return arr;
}
-// values must point at already-rooted Value objects
+// strings in props must point at already-rooted strings
ArrayObject* js::NewDenseCopiedArray(
- JSContext* cx, uint32_t length, JSLinearString** values,
+ JSContext* cx, uint32_t length, IteratorProperty* props,
NewObjectKind newKind /* = GenericObject */) {
ArrayObject* arr = NewArray<UINT32_MAX>(cx, length, newKind);
if (!arr) {
return nullptr;
}
- arr->initDenseElements(values, length);
+ arr->initDenseElements(props, length);
return arr;
}
diff --git a/js/src/builtin/Array.h b/js/src/builtin/Array.h
@@ -22,6 +22,7 @@ class TrampolineNativeFrameLayout;
}
class ArrayObject;
+class IteratorProperty;
MOZ_ALWAYS_INLINE bool IdIsIndex(jsid id, uint32_t* indexp) {
if (id.isInt()) {
@@ -77,10 +78,10 @@ extern ArrayObject* NewDenseCopiedArray(JSContext* cx, uint32_t length,
const Value* values,
NewObjectKind newKind = GenericObject);
-// Create a dense array from the given (linear)string values, which must be
-// rooted
+// Create a dense array from the given IteratorProperty values, which must be
+// rooted.
extern ArrayObject* NewDenseCopiedArray(JSContext* cx, uint32_t length,
- JSLinearString** values,
+ IteratorProperty* props,
NewObjectKind newKind = GenericObject);
// Like NewDenseCopiedArray, but the array will have |proto| as prototype (or
diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp
@@ -1553,8 +1553,7 @@ static bool TryEnumerableOwnPropertiesNative(JSContext* cx, HandleObject obj,
break;
}
- JSLinearString** properties =
- ni->propertiesBegin()->unbarrieredAddress();
+ IteratorProperty* properties = ni->propertiesBegin();
JSObject* array = NewDenseCopiedArray(cx, ni->numKeys(), properties);
if (!array) {
return false;
diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp
@@ -9510,7 +9510,8 @@ void MacroAssembler::iteratorMore(Register obj, ValueOperand output,
// If propertyCursor_ < propertiesEnd_, load the next string and advance
// the cursor. Otherwise return MagicValue(JS_NO_ITER_VALUE).
- Label iterDone;
+ Label iterDone, restart;
+ bind(&restart);
Address cursorAddr(outputScratch, NativeIterator::offsetOfPropertyCursor());
Address cursorEndAddr(outputScratch, NativeIterator::offsetOfPropertiesEnd());
loadPtr(cursorAddr, temp);
@@ -9520,7 +9521,11 @@ void MacroAssembler::iteratorMore(Register obj, ValueOperand output,
loadPtr(Address(temp, 0), temp);
// Increase the cursor.
- addPtr(Imm32(sizeof(GCPtr<JSLinearString*>)), cursorAddr);
+ addPtr(Imm32(sizeof(IteratorProperty)), cursorAddr);
+
+ // Check if the property has been deleted while iterating. Skip it if so.
+ branchTestPtr(Assembler::NonZero, temp,
+ Imm32(uint32_t(IteratorProperty::DeletedBit)), &restart);
tagValue(JSVAL_TYPE_STRING, temp, output);
jump(&done);
@@ -9535,17 +9540,14 @@ void MacroAssembler::iteratorClose(Register obj, Register temp1, Register temp2,
Register temp3) {
LoadNativeIterator(*this, obj, temp1);
+ Address flagsAddr(temp1, NativeIterator::offsetOfFlagsAndCount());
+
// The shared iterator used for for-in with null/undefined is immutable and
// unlinked. See NativeIterator::isEmptyIteratorSingleton.
Label done;
- branchTest32(Assembler::NonZero,
- Address(temp1, NativeIterator::offsetOfFlagsAndCount()),
+ branchTest32(Assembler::NonZero, flagsAddr,
Imm32(NativeIterator::Flags::IsEmptyIteratorSingleton), &done);
- // Clear active bit.
- and32(Imm32(~NativeIterator::Flags::Active),
- Address(temp1, NativeIterator::offsetOfFlagsAndCount()));
-
// Clear objectBeingIterated.
Address iterObjAddr(temp1, NativeIterator::offsetOfObjectBeingIterated());
guardedCallPreBarrierAnyZone(iterObjAddr, MIRType::Object, temp2);
@@ -9555,6 +9557,26 @@ void MacroAssembler::iteratorClose(Register obj, Register temp1, Register temp2,
loadPtr(Address(temp1, NativeIterator::offsetOfShapesEnd()), temp2);
storePtr(temp2, Address(temp1, NativeIterator::offsetOfPropertyCursor()));
+ // Clear deleted bits (only if we have unvisited deletions)
+ Label clearDeletedLoopStart, clearDeletedLoopEnd;
+ branchTest32(Assembler::Zero, flagsAddr,
+ Imm32(NativeIterator::Flags::HasUnvisitedPropertyDeletion),
+ &clearDeletedLoopEnd);
+
+ loadPtr(Address(temp1, NativeIterator::offsetOfPropertiesEnd()), temp3);
+
+ bind(&clearDeletedLoopStart);
+ and32(Imm32(~uint32_t(IteratorProperty::DeletedBit)), Address(temp2, 0));
+ addPtr(Imm32(sizeof(IteratorProperty)), temp2);
+ branchPtr(Assembler::Below, temp2, temp3, &clearDeletedLoopStart);
+
+ bind(&clearDeletedLoopEnd);
+
+ // Clear active and unvisited deletions bits
+ and32(Imm32(~(NativeIterator::Flags::Active |
+ NativeIterator::Flags::HasUnvisitedPropertyDeletion)),
+ flagsAddr);
+
// Unlink from the iterator list.
const Register next = temp2;
const Register prev = temp3;
diff --git a/js/src/vm/Iteration.cpp b/js/src/vm/Iteration.cpp
@@ -40,6 +40,8 @@
#include "vm/StringType.h"
#include "vm/TypedArrayObject.h"
#include "vm/Watchtower.h"
+
+#include "gc/StoreBuffer-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/PlainObject-inl.h" // js::PlainObject::createWithTemplate
@@ -78,15 +80,18 @@ void NativeIterator::trace(JSTracer* trc) {
// 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 =
+ IteratorProperty* 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");
- });
+ std::for_each(begin, propertiesEnd(),
+ [trc](IteratorProperty& prop) { prop.traceString(trc); });
+}
+
+void IteratorProperty::traceString(JSTracer* trc) {
+ // Properties begin life non-null and never *become* null.
+ // Trace the underlying string while preserving the deleted bit.
+ JSLinearString* str = asString();
+ TraceManuallyBarrieredEdge(trc, &str, "iterator-property-string");
+ raw_ = uintptr_t(str) | (deleted() ? DeletedBit : 0);
}
using PropertyKeySet = GCHashSet<PropertyKey, DefaultHasher<PropertyKey>>;
@@ -768,10 +773,10 @@ static PropertyIteratorObject* NewPropertyIteratorObject(JSContext* cx) {
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(IteratorProperty) <= alignof(NativeIterator));
+ static_assert(alignof(GCPtr<Shape*>) <= alignof(IteratorProperty));
static_assert(alignof(PropertyIndex) <= alignof(GCPtr<Shape*>));
- size_t result = propertyCount * sizeof(GCPtr<JSLinearString*>) +
+ size_t result = propertyCount * sizeof(IteratorProperty) +
shapeCount * sizeof(GCPtr<Shape*>);
if (hasIndices) {
result += propertyCount * sizeof(PropertyIndex);
@@ -852,7 +857,7 @@ NativeIterator::NativeIterator(JSContext* cx,
shapesEnd_(shapesBegin()),
// ...and no properties.
propertyCursor_(
- reinterpret_cast<GCPtr<JSLinearString*>*>(shapesBegin() + numShapes)),
+ reinterpret_cast<IteratorProperty*>(shapesBegin() + numShapes)),
propertiesEnd_(propertyCursor_),
shapesHash_(0),
flagsAndCount_(
@@ -929,6 +934,8 @@ NativeIterator::NativeIterator(JSContext* cx,
// from |propIter| which will be tenured.
AutoSelectGCHeap gcHeap(cx);
+ bool maybeNeedGC = !gc::IsInsideNursery(propIter);
+ uint64_t gcNumber = cx->runtime()->gc.gcNumber();
size_t numProps = props.length();
for (size_t i = 0; i < numProps; i++) {
JSLinearString* str = IdToString(cx, props[i], gcHeap);
@@ -936,8 +943,20 @@ NativeIterator::NativeIterator(JSContext* cx,
*hadError = true;
return;
}
- new (propertiesEnd_) GCPtr<JSLinearString*>(str);
+ uint64_t newGcNumber = cx->runtime()->gc.gcNumber();
+ if (newGcNumber != gcNumber) {
+ gcNumber = newGcNumber;
+ maybeNeedGC = true;
+ }
+ // We write to our IteratorProperty children only here and in
+ // PropertyIteratorObject::trace. Here we do not need a pre-barrier
+ // because we are not overwriting a previous value.
+ new (propertiesEnd_) IteratorProperty(str);
propertiesEnd_++;
+ if (maybeNeedGC && gc::IsInsideNursery(str)) {
+ maybeNeedGC = false;
+ cx->runtime()->gc.storeBuffer().putWholeCell(propIter);
+ }
}
if (hasActualIndices) {
@@ -1128,7 +1147,7 @@ static bool IndicesAreValid(NativeObject* obj, NativeIterator* ni) {
size_t numFixedSlots = obj->numFixedSlots();
const Value* elements = obj->getDenseElements();
- GCPtr<JSLinearString*>* keys = ni->propertiesBegin();
+ IteratorProperty* keys = ni->propertiesBegin();
PropertyIndex* indices = ni->indicesBegin();
for (uint32_t i = 0; i < ni->numKeys(); i++) {
@@ -1145,7 +1164,7 @@ static bool IndicesAreValid(NativeObject* obj, NativeIterator* ni) {
// Verify that the slot exists and is an enumerable data property with
// the expected key.
Maybe<PropertyInfo> prop =
- obj->lookupPure(AtomToId(&keys[i]->asAtom()));
+ obj->lookupPure(AtomToId(&keys[i].asString()->asAtom()));
if (!prop.isSome() || !prop->hasSlot() || !prop->enumerable() ||
!prop->isDataProperty() || prop->slot() != index.index()) {
return false;
@@ -1156,7 +1175,7 @@ static bool IndicesAreValid(NativeObject* obj, NativeIterator* ni) {
// Verify that the slot exists and is an enumerable data property with
// the expected key.
Maybe<PropertyInfo> prop =
- obj->lookupPure(AtomToId(&keys[i]->asAtom()));
+ obj->lookupPure(AtomToId(&keys[i].asString()->asAtom()));
if (!prop.isSome() || !prop->hasSlot() || !prop->enumerable() ||
!prop->isDataProperty() ||
prop->slot() - numFixedSlots != index.index()) {
@@ -1748,76 +1767,54 @@ static bool SuppressDeletedProperty(JSContext* cx, NativeIterator* ni,
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 id is still to come.
+ Rooted<JSLinearString*> idStr(cx);
+ IteratorProperty* cursor = ni->nextProperty();
+ for (; cursor < ni->propertiesEnd(); ++cursor) {
+ idStr = cursor->asString();
+ // Common case: both strings are atoms.
+ if (idStr->isAtom() && str->isAtom()) {
+ if (idStr != 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;
+ } else {
+ if (!EqualStrings(idStr, str)) {
+ continue;
}
- 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;
- }
+ // 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(idStr));
+ if (!PrimitiveValueToId<CanGC>(cx, idv, &id)) {
+ return false;
}
- // If GetPropertyDescriptor above removed a property from ni, start
- // over.
- if (end != ni->propertiesEnd() || cursor != ni->nextProperty()) {
- restart = true;
- break;
+ Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ RootedObject holder(cx);
+ if (!GetPropertyDescriptor(cx, proto, id, &desc, &holder)) {
+ return false;
}
- // 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();
+ // If deletion just made something up the chain visible, no need to
+ // do anything.
+ if (desc.isSome() && desc->enumerable()) {
+ return true;
}
-
- ni->markHasUnvisitedPropertyDeletion();
- return true;
}
- if (!restart) {
- return true;
- }
+ cursor->markDeleted();
+ ni->markHasUnvisitedPropertyDeletion();
+ return true;
}
+
+ return true;
}
/*
@@ -1898,10 +1895,10 @@ void js::AssertDenseElementsNotIterated(NativeObject* obj) {
NativeIterator* ni = iter.next();
if (ni->objectBeingIterated() == obj &&
!ni->maybeHasIndexedPropertiesFromProto()) {
- for (GCPtr<JSLinearString*>* idp = ni->nextProperty();
+ for (IteratorProperty* idp = ni->nextProperty();
idp < ni->propertiesEnd(); ++idp) {
uint32_t index;
- if (idp->get()->isIndex(&index)) {
+ if (idp->asString()->isIndex(&index)) {
MOZ_ASSERT(!obj->containsDenseElement(index));
}
if (++propsChecked > MaxPropsToCheck) {
diff --git a/js/src/vm/Iteration.h b/js/src/vm/Iteration.h
@@ -214,6 +214,34 @@ enum class NativeIteratorIndices : uint32_t {
Valid = 3
};
+class IteratorProperty {
+ uintptr_t raw_ = 0;
+
+ public:
+ static constexpr uintptr_t DeletedBit = 0x1;
+
+ // The copy constructor is deleted as a safeguard against writing code which
+ // would overwrite existing IteratorProperties on NativeIterators. They are
+ // intended to be written once and never changed, outside of being moved in
+ // GC callbacks.
+ IteratorProperty(const IteratorProperty&) = delete;
+
+ IteratorProperty() = default;
+ explicit IteratorProperty(JSLinearString* str) : raw_(uintptr_t(str)) {}
+ IteratorProperty(JSLinearString* str, bool deleted)
+ : raw_(uintptr_t(str) | (deleted ? DeletedBit : 0)) {}
+
+ JSLinearString* asString() const {
+ return reinterpret_cast<JSLinearString*>(raw_ & ~DeletedBit);
+ }
+
+ bool deleted() const { return raw_ & DeletedBit; }
+ void markDeleted() { raw_ |= DeletedBit; }
+ void clearDeleted() { raw_ &= ~DeletedBit; }
+
+ void traceString(JSTracer* trc);
+} JS_HAZ_GC_POINTER;
+
struct NativeIterator : public NativeIteratorListNode {
private:
// Object being iterated. Non-null except in NativeIterator sentinels,
@@ -233,15 +261,19 @@ struct NativeIterator : public NativeIteratorListNode {
// The next property, pointing into an array of strings directly after any
// GCPtr<Shape*>s that appear directly after |*this|, as part of an overall
// allocation that stores |*this|, shapes, iterated strings, and maybe
- // indices.
- GCPtr<JSLinearString*>* propertyCursor_; // initialized by constructor
+ // indices. Strings are stored as a JSLinearString* with a low-bit tag
+ // indicating whether they were deleted while iterating this object, in which
+ // case they should be skipped. The post barrier for writing to this is
+ // handled in NativeIterator::NativeIterator by adding iterObj_ to the
+ // whole cell buffer, and no pre barrier is required because we never modify
+ // these after initialization.
+ IteratorProperty* propertyCursor_; // initialized by constructor
// The limit/end of properties to iterate. Once |this| has been fully
// initialized, it also equals the start of indices, if indices are present,
// or the end of the full allocation storing |*this|, shapes, and strings, if
- // indices are not present. Beware! This value may change as properties are
- // deleted from the observed object.
- GCPtr<JSLinearString*>* propertiesEnd_; // initialized by constructor
+ // indices are not present.
+ IteratorProperty* propertiesEnd_; // initialized by constructor
HashNumber shapesHash_; // initialized by constructor
@@ -361,15 +393,15 @@ struct NativeIterator : public NativeIteratorListNode {
return mozilla::PointerRangeSize(shapesBegin(), shapesEnd());
}
- GCPtr<JSLinearString*>* propertiesBegin() const {
+ IteratorProperty* propertiesBegin() const {
static_assert(
- alignof(GCPtr<Shape*>) >= alignof(GCPtr<JSLinearString*>),
- "GCPtr<JSLinearString*>s for properties must be able to appear "
+ alignof(GCPtr<Shape*>) >= alignof(IteratorProperty),
+ "IteratorPropertys for properties must be able to appear "
"directly after any GCPtr<Shape*>s after this NativeIterator, "
"with no padding space required for correct alignment");
static_assert(
- alignof(NativeIterator) >= alignof(GCPtr<JSLinearString*>),
- "GCPtr<JSLinearString*>s for properties must be able to appear "
+ alignof(NativeIterator) >= alignof(IteratorProperty),
+ "IteratorPropertys for properties must be able to appear "
"directly after this NativeIterator when no GCPtr<Shape*>s are "
"present, with no padding space required for correct "
"alignment");
@@ -383,17 +415,17 @@ struct NativeIterator : public NativeIteratorListNode {
"isn't necessarily the start of properties and instead "
"|propertyCursor_| is");
- return reinterpret_cast<GCPtr<JSLinearString*>*>(shapesEnd_);
+ return reinterpret_cast<IteratorProperty*>(shapesEnd_);
}
- GCPtr<JSLinearString*>* propertiesEnd() const { return propertiesEnd_; }
+ IteratorProperty* propertiesEnd() const { return propertiesEnd_; }
- GCPtr<JSLinearString*>* nextProperty() const { return propertyCursor_; }
+ IteratorProperty* nextProperty() const { return propertyCursor_; }
PropertyIndex* indicesBegin() const {
// PropertyIndex must be able to be appear directly after the properties
// array, with no padding required for correct alignment.
- static_assert(alignof(GCPtr<JSLinearString*>) >= alignof(PropertyIndex));
+ static_assert(alignof(IteratorProperty) >= alignof(PropertyIndex));
return reinterpret_cast<PropertyIndex*>(propertiesEnd_);
}
@@ -403,14 +435,17 @@ struct NativeIterator : public NativeIteratorListNode {
}
MOZ_ALWAYS_INLINE JS::Value nextIteratedValueAndAdvance() {
- if (propertyCursor_ >= propertiesEnd_) {
- MOZ_ASSERT(propertyCursor_ == propertiesEnd_);
- return JS::MagicValue(JS_NO_ITER_VALUE);
+ while (propertyCursor_ < propertiesEnd_) {
+ IteratorProperty& prop = *propertyCursor_;
+ incCursor();
+ if (prop.deleted()) {
+ continue;
+ }
+ return JS::StringValue(prop.asString());
}
- JSLinearString* str = *propertyCursor_;
- incCursor();
- return JS::StringValue(str);
+ MOZ_ASSERT(propertyCursor_ == propertiesEnd_);
+ return JS::MagicValue(JS_NO_ITER_VALUE);
}
void resetPropertyCursorForReuse() {
@@ -421,33 +456,30 @@ struct NativeIterator : public NativeIteratorListNode {
// this NativeIterator is reusable. (Should we not bother resetting
// the cursor in that case?)
+ // If properties were marked as deleted, unmark them.
+ if (hasUnvisitedPropertyDeletion()) {
+ for (IteratorProperty* prop = propertiesBegin(); prop < propertiesEnd();
+ prop++) {
+ prop->clearDeleted();
+ }
+ unmarkHasUnvisitedPropertyDeletion();
+ }
+
// Note: JIT code inlines |propertyCursor_| resetting when an iterator
- // ends: see |CodeGenerator::visitIteratorEnd|.
+ // ends: see |MacroAssembler::iteratorClose|.
propertyCursor_ = propertiesBegin();
}
bool previousPropertyWas(JS::Handle<JSLinearString*> str) {
MOZ_ASSERT(isInitialized());
- return propertyCursor_ > propertiesBegin() && propertyCursor_[-1] == str;
+ return propertyCursor_ > propertiesBegin() &&
+ propertyCursor_[-1].asString() == str;
}
size_t numKeys() const {
return mozilla::PointerRangeSize(propertiesBegin(), propertiesEnd());
}
- void trimLastProperty() {
- MOZ_ASSERT(isInitialized());
- propertiesEnd_--;
-
- // This invokes the pre barrier on this property, since it's no longer
- // going to be marked, and it ensures that any existing remembered set
- // entry will be dropped.
- *propertiesEnd_ = nullptr;
-
- // Indices are no longer valid.
- disableIndices();
- }
-
JSObject* iterObj() const { return iterObj_; }
void incCursor() {
@@ -568,6 +600,20 @@ struct NativeIterator : public NativeIteratorListNode {
flagsAndCount_ |= Flags::HasUnvisitedPropertyDeletion;
}
+ void unmarkHasUnvisitedPropertyDeletion() {
+ MOZ_ASSERT(isInitialized());
+ MOZ_ASSERT(!isEmptyIteratorSingleton());
+ MOZ_ASSERT(hasUnvisitedPropertyDeletion());
+
+ flagsAndCount_ &= ~Flags::HasUnvisitedPropertyDeletion;
+ }
+
+ bool hasUnvisitedPropertyDeletion() const {
+ MOZ_ASSERT(isInitialized());
+
+ return flags() & Flags::HasUnvisitedPropertyDeletion;
+ }
+
bool hasValidIndices() const {
return indicesState() == NativeIteratorIndices::Valid;
}
diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h
@@ -162,7 +162,7 @@ inline void NativeObject::initDenseElements(const Value* src, uint32_t count) {
elementsRangePostWriteBarrier(0, count);
}
-inline void NativeObject::initDenseElements(JSLinearString** src,
+inline void NativeObject::initDenseElements(IteratorProperty* src,
uint32_t count) {
MOZ_ASSERT(getDenseInitializedLength() == 0);
MOZ_ASSERT(count <= getDenseCapacity());
@@ -172,7 +172,7 @@ inline void NativeObject::initDenseElements(JSLinearString** src,
setDenseInitializedLength(count);
Value* elementsBase = reinterpret_cast<Value*>(elements_);
for (size_t i = 0; i < count; i++) {
- elementsBase[i].setString(src[i]);
+ elementsBase[i].setString(src[i].asString());
}
elementsRangePostWriteBarrier(0, count);
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h
@@ -32,6 +32,7 @@
namespace js {
class JS_PUBLIC_API GenericPrinter;
+class IteratorProperty;
class PropertyResult;
namespace gc {
@@ -1521,7 +1522,7 @@ class NativeObject : public JSObject {
uint32_t count);
inline void initDenseElements(const Value* src, uint32_t count);
- inline void initDenseElements(JSLinearString** src, uint32_t count);
+ inline void initDenseElements(IteratorProperty* src, uint32_t count);
inline void initDenseElements(NativeObject* src, uint32_t srcStart,
uint32_t count);