Bug 1635185 - Part 3: Store dictionary object slot span in the slots header r=jandem

This stores the slot span in the object slots error for dictionary mode
objects. Since it's possible to have dictionary mode objects without dynamic
slots, this adds sentinel ObjectSlots instances for each possible dictionary
slot span up to the maximum number of fixed slots.

Differential Revision: https://phabricator.services.mozilla.com/D87888
This commit is contained in:
Jon Coppeard 2020-08-27 15:20:39 +00:00
parent d73df31279
commit f544bc0287
13 changed files with 191 additions and 72 deletions

View File

@ -138,7 +138,7 @@ JSObject* GCRuntime::tryNewTenuredObject(JSContext* cx, AllocKind kind,
return nullptr;
}
slotsHeader = new (allocation) ObjectSlots(nDynamicSlots);
slotsHeader = new (allocation) ObjectSlots(nDynamicSlots, 0);
Debug_SetSlotRangeToCrashOnTouch(slotsHeader->slots(), nDynamicSlots);
}

View File

@ -3561,7 +3561,8 @@ size_t js::TenuringTracer::moveSlotsToTenured(NativeObject* dst,
"Failed to allocate slots while tenuring.");
}
ObjectSlots* slotsHeader = new (allocation) ObjectSlots(count);
ObjectSlots* slotsHeader = new (allocation)
ObjectSlots(count, src->getSlotsHeader()->dictionarySlotSpan());
dst->slots_ = slotsHeader->slots();
}
@ -3569,6 +3570,7 @@ size_t js::TenuringTracer::moveSlotsToTenured(NativeObject* dst,
PodCopy(dst->slots_, src->slots_, count);
nursery().setSlotsForwardingPointer(src->slots_, dst->slots_, count);
return count * sizeof(HeapSlot);
}

View File

@ -452,7 +452,7 @@ JSObject* js::Nursery::allocateObject(JSContext* cx, size_t size,
// do not visit unallocated things in the nursery.
return nullptr;
}
slotsHeader = new (allocation) ObjectSlots(nDynamicSlots);
slotsHeader = new (allocation) ObjectSlots(nDynamicSlots, 0);
}
// Store slots pointer directly in new object. If no dynamic slots were

View File

@ -11751,10 +11751,10 @@ void CodeGenerator::visitAddAndStoreSlot(LAddAndStoreSlot* ins) {
const ValueOperand value = ToValue(ins, LAddAndStoreSlot::Value);
const Register maybeTemp = ToTempRegisterOrInvalid(ins->getTemp(0));
masm.storeObjShape(ins->mir()->shape(), obj,
[](MacroAssembler& masm, const Address& addr) {
EmitPreBarrier(masm, addr, MIRType::Shape);
});
Shape* shape = ins->mir()->shape();
masm.storeObjShape(shape, obj, [](MacroAssembler& masm, const Address& addr) {
EmitPreBarrier(masm, addr, MIRType::Shape);
});
// Perform the store. No pre-barrier required since this is a new
// initialization.

View File

@ -678,6 +678,9 @@ void MacroAssembler::nurseryAllocateObject(Register result, Register temp,
if (nDynamicSlots) {
store32(Imm32(nDynamicSlots),
Address(result, thingSize + ObjectSlots::offsetOfCapacity()));
store32(
Imm32(0),
Address(result, thingSize + ObjectSlots::offsetOfDictionarySlotSpan()));
computeEffectiveAddress(
Address(result, thingSize + ObjectSlots::offsetOfSlots()), temp);
storePtr(temp, Address(result, NativeObject::offsetOfSlots()));
@ -1211,7 +1214,8 @@ void MacroAssembler::initGCThing(Register obj, Register temp,
// If the object has dynamic slots, the slots member has already been
// filled in.
if (!ntemplate.hasDynamicSlots()) {
storePtr(ImmPtr(nullptr), Address(obj, NativeObject::offsetOfSlots()));
storePtr(ImmPtr(emptyObjectSlots),
Address(obj, NativeObject::offsetOfSlots()));
}
if (ntemplate.denseElementsAreCopyOnWrite()) {

View File

@ -61,7 +61,7 @@ inline void ArrayObject::setLength(JSContext* cx, uint32_t length) {
aobj->initShape(shape);
// NOTE: Dynamic slots are created internally by Allocate<JSObject>.
if (!nDynamicSlots) {
aobj->initSlots(nullptr);
aobj->initEmptyDynamicSlots();
}
MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());

View File

@ -110,7 +110,7 @@ inline JSFunction* CloneFunctionObjectIfNotSingleton(
nobj->initGroup(group);
nobj->initShape(shape);
nobj->initSlots(nullptr);
nobj->initEmptyDynamicSlots();
nobj->setEmptyElements();
MOZ_ASSERT(!clasp->hasPrivate());

View File

@ -39,8 +39,10 @@ static inline gc::AllocKind NewObjectGCKind(const JSClass* clasp) {
} // namespace js
MOZ_ALWAYS_INLINE uint32_t js::NativeObject::numDynamicSlots() const {
uint32_t slots = hasDynamicSlots() ? getSlotsHeader()->capacity() : 0;
uint32_t slots = getSlotsHeader()->capacity();
MOZ_ASSERT(slots == calculateDynamicSlots());
MOZ_ASSERT_IF(hasDynamicSlots(), slots != 0);
return slots;
}

View File

@ -1471,8 +1471,6 @@ bool NativeObject::fillInAfterSwap(JSContext* cx, HandleNativeObject obj,
MOZ_ASSERT(obj->slotSpan() == values.length());
MOZ_ASSERT(!IsInsideNursery(obj));
size_t oldSlotCount = obj->numDynamicSlots();
// Make sure the shape's numFixedSlots() is correct.
size_t nfixed =
gc::GetGCKindSlots(obj->asTenured().getAllocKind(), obj->getClass());
@ -1490,29 +1488,26 @@ bool NativeObject::fillInAfterSwap(JSContext* cx, HandleNativeObject obj,
}
Zone* zone = obj->zone();
if (obj->slots_) {
size_t size = ObjectSlots::allocSize(oldSlotCount);
if (obj->hasDynamicSlots()) {
ObjectSlots* slotsHeader = obj->getSlotsHeader();
uint32_t oldDictionarySlotSpan = slotsHeader->dictionarySlotSpan();
size_t size = ObjectSlots::allocSize(slotsHeader->capacity());
zone->removeCellMemory(old, size, MemoryUse::ObjectSlots);
js_free(obj->getSlotsHeader());
obj->slots_ = nullptr;
js_free(slotsHeader);
obj->setEmptyDynamicSlots(oldDictionarySlotSpan);
}
if (size_t ndynamic =
calculateDynamicSlots(nfixed, values.length(), obj->getClass())) {
HeapSlot* allocation =
cx->pod_malloc<HeapSlot>(ObjectSlots::allocCount(ndynamic));
if (!allocation) {
size_t ndynamic =
calculateDynamicSlots(nfixed, values.length(), obj->getClass());
MOZ_ASSERT(ndynamic >= obj->numDynamicSlots());
if (ndynamic > obj->numDynamicSlots()) {
if (!obj->growSlots(cx, obj->numDynamicSlots(), ndynamic)) {
return false;
}
auto* slotsHeader = new (allocation) ObjectSlots(ndynamic);
obj->slots_ = slotsHeader->slots();
zone->addCellMemory(obj, ObjectSlots::allocSize(ndynamic),
MemoryUse::ObjectSlots);
Debug_SetSlotRangeToCrashOnTouch(obj->slots_, ndynamic);
}
obj->initSlotRange(0, values.begin(), values.length());
return true;
}

View File

@ -522,7 +522,7 @@ inline bool NativeObject::isInWholeCellBuffer() const {
nobj->initShape(shape);
// NOTE: Dynamic slots are created internally by Allocate<JSObject>.
if (!nDynamicSlots) {
nobj->initSlots(nullptr);
nobj->initEmptyDynamicSlots();
}
nobj->setEmptyElements();
@ -550,11 +550,12 @@ MOZ_ALWAYS_INLINE bool NativeObject::updateSlotsForSpan(JSContext* cx,
size_t newSpan) {
MOZ_ASSERT(oldSpan != newSpan);
size_t oldCount = numDynamicSlots();
size_t newCount = calculateDynamicSlots(numFixedSlots(), newSpan, getClass());
size_t oldCapacity = numDynamicSlots();
size_t newCapacity =
calculateDynamicSlots(numFixedSlots(), newSpan, getClass());
if (oldSpan < newSpan) {
if (oldCount < newCount && !growSlots(cx, oldCount, newCount)) {
if (oldCapacity < newCapacity && !growSlots(cx, oldCapacity, newCapacity)) {
return false;
}
@ -568,14 +569,38 @@ MOZ_ALWAYS_INLINE bool NativeObject::updateSlotsForSpan(JSContext* cx,
prepareSlotRangeForOverwrite(newSpan, oldSpan);
invalidateSlotRange(newSpan, oldSpan - newSpan);
if (oldCount > newCount) {
shrinkSlots(cx, oldCount, newCount);
if (oldCapacity > newCapacity) {
shrinkSlots(cx, oldCapacity, newCapacity);
}
}
return true;
}
MOZ_ALWAYS_INLINE void NativeObject::initEmptyDynamicSlots() {
setEmptyDynamicSlots(0);
}
MOZ_ALWAYS_INLINE void NativeObject::setDictionaryModeSlotSpan(uint32_t span) {
MOZ_ASSERT(inDictionaryMode());
if (!hasDynamicSlots()) {
setEmptyDynamicSlots(span);
return;
}
getSlotsHeader()->setDictionarySlotSpan(span);
}
MOZ_ALWAYS_INLINE void NativeObject::setEmptyDynamicSlots(
uint32_t dictionarySlotSpan) {
MOZ_ASSERT_IF(!inDictionaryMode(), dictionarySlotSpan == 0);
MOZ_ASSERT(dictionarySlotSpan <= MAX_FIXED_SLOTS);
slots_ = emptyObjectSlotsForDictionaryObject[dictionarySlotSpan];
MOZ_ASSERT(getSlotsHeader()->capacity() == 0);
MOZ_ASSERT(getSlotsHeader()->dictionarySlotSpan() == dictionarySlotSpan);
}
MOZ_ALWAYS_INLINE bool NativeObject::setLastProperty(JSContext* cx,
Shape* shape) {
MOZ_ASSERT(!inDictionaryMode());

View File

@ -72,6 +72,38 @@ static constexpr EmptyObjectElements emptyElementsHeaderShared(
HeapSlot* const js::emptyObjectElementsShared = reinterpret_cast<HeapSlot*>(
uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
struct EmptyObjectSlots : public ObjectSlots {
explicit constexpr EmptyObjectSlots(size_t dictionarySlotSpan)
: ObjectSlots(0, dictionarySlotSpan) {}
};
static constexpr EmptyObjectSlots emptyObjectSlotsHeaders[17] = {
EmptyObjectSlots(0), EmptyObjectSlots(1), EmptyObjectSlots(2),
EmptyObjectSlots(3), EmptyObjectSlots(4), EmptyObjectSlots(5),
EmptyObjectSlots(6), EmptyObjectSlots(7), EmptyObjectSlots(8),
EmptyObjectSlots(9), EmptyObjectSlots(10), EmptyObjectSlots(11),
EmptyObjectSlots(12), EmptyObjectSlots(13), EmptyObjectSlots(14),
EmptyObjectSlots(15), EmptyObjectSlots(16)};
static_assert(ArrayLength(emptyObjectSlotsHeaders) ==
NativeObject::MAX_FIXED_SLOTS + 1);
HeapSlot* const js::emptyObjectSlotsForDictionaryObject[17] = {
emptyObjectSlotsHeaders[0].slots(), emptyObjectSlotsHeaders[1].slots(),
emptyObjectSlotsHeaders[2].slots(), emptyObjectSlotsHeaders[3].slots(),
emptyObjectSlotsHeaders[4].slots(), emptyObjectSlotsHeaders[5].slots(),
emptyObjectSlotsHeaders[6].slots(), emptyObjectSlotsHeaders[7].slots(),
emptyObjectSlotsHeaders[8].slots(), emptyObjectSlotsHeaders[9].slots(),
emptyObjectSlotsHeaders[10].slots(), emptyObjectSlotsHeaders[11].slots(),
emptyObjectSlotsHeaders[12].slots(), emptyObjectSlotsHeaders[13].slots(),
emptyObjectSlotsHeaders[14].slots(), emptyObjectSlotsHeaders[15].slots(),
emptyObjectSlotsHeaders[16].slots()};
static_assert(ArrayLength(emptyObjectSlotsForDictionaryObject) ==
NativeObject::MAX_FIXED_SLOTS + 1);
HeapSlot* const js::emptyObjectSlots = emptyObjectSlotsForDictionaryObject[0];
#ifdef DEBUG
bool NativeObject::canHaveNonEmptyElements() {
@ -289,11 +321,13 @@ void js::NativeObject::initSlotRange(uint32_t start, const Value* vector,
HeapSlot* slotsStart;
HeapSlot* slotsEnd;
getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
uint32_t offset = start;
for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++) {
sp->init(this, HeapSlot::Slot, start++, *vector++);
sp->init(this, HeapSlot::Slot, offset++, *vector++);
}
for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++) {
sp->init(this, HeapSlot::Slot, start++, *vector++);
sp->init(this, HeapSlot::Slot, offset++, *vector++);
}
}
@ -354,10 +388,11 @@ void NativeObject::setLastPropertyShrinkFixedSlots(Shape* shape) {
setShape(shape);
}
bool NativeObject::setSlotSpan(JSContext* cx, uint32_t span) {
bool NativeObject::ensureSlotsForDictionaryObject(JSContext* cx,
uint32_t span) {
MOZ_ASSERT(inDictionaryMode());
size_t oldSpan = lastProperty()->base()->slotSpan();
size_t oldSpan = dictionaryModeSlotSpan();
if (oldSpan == span) {
return true;
}
@ -367,6 +402,7 @@ bool NativeObject::setSlotSpan(JSContext* cx, uint32_t span) {
}
lastProperty()->base()->setSlotSpan(span);
setDictionaryModeSlotSpan(span);
return true;
}
@ -383,26 +419,13 @@ bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity,
NativeObject::slotsSizeMustNotOverflow();
MOZ_ASSERT(newCapacity <= MAX_SLOTS_COUNT);
if (!hasDynamicSlots()) {
return allocateSlots(cx, newCapacity);
}
uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
if (!oldCapacity) {
MOZ_ASSERT(!slots_);
HeapSlot* allocation =
AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
if (!allocation) {
return false;
}
auto* newHeaderSlots = new (allocation) ObjectSlots(newCapacity);
slots_ = newHeaderSlots->slots();
Debug_SetSlotRangeToCrashOnTouch(slots_, newCapacity);
AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
MemoryUse::ObjectSlots);
return true;
}
uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity);
@ -416,7 +439,8 @@ bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity,
return false; /* Leave slots at its old size. */
}
auto* newHeaderSlots = new (allocation) ObjectSlots(newCapacity);
auto* newHeaderSlots =
new (allocation) ObjectSlots(newCapacity, dictionarySpan);
slots_ = newHeaderSlots->slots();
Debug_SetSlotRangeToCrashOnTouch(slots_ + oldCapacity,
@ -427,6 +451,32 @@ bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity,
AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
MemoryUse::ObjectSlots);
MOZ_ASSERT(hasDynamicSlots());
return true;
}
bool NativeObject::allocateSlots(JSContext* cx, uint32_t newCapacity) {
MOZ_ASSERT(!hasDynamicSlots());
uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
HeapSlot* allocation = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
if (!allocation) {
return false;
}
auto* newHeaderSlots =
new (allocation) ObjectSlots(newCapacity, dictionarySpan);
slots_ = newHeaderSlots->slots();
Debug_SetSlotRangeToCrashOnTouch(slots_, newCapacity);
AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
MemoryUse::ObjectSlots);
MOZ_ASSERT(hasDynamicSlots());
return true;
}
@ -440,6 +490,7 @@ bool NativeObject::growSlotsPure(JSContext* cx, NativeObject* obj,
cx->recoverFromOutOfMemory();
return false;
}
return true;
}
@ -484,7 +535,9 @@ static inline void FreeSlots(JSContext* cx, NativeObject* obj,
void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity,
uint32_t newCapacity) {
MOZ_ASSERT(newCapacity < oldCapacity);
MOZ_ASSERT(oldCapacity == numDynamicSlots());
MOZ_ASSERT(oldCapacity == getSlotsHeader()->capacity());
uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_);
MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity);
@ -495,7 +548,7 @@ void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity,
size_t nbytes = ObjectSlots::allocSize(oldCapacity);
RemoveCellMemory(this, nbytes, MemoryUse::ObjectSlots);
FreeSlots(cx, this, oldHeaderSlots, nbytes);
slots_ = nullptr;
setEmptyDynamicSlots(dictionarySpan);
return;
}
@ -520,7 +573,8 @@ void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity,
AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
MemoryUse::ObjectSlots);
auto* newHeaderSlots = new (allocation) ObjectSlots(newCapacity);
auto* newHeaderSlots =
new (allocation) ObjectSlots(newCapacity, dictionarySpan);
slots_ = newHeaderSlots->slots();
}
@ -1190,7 +1244,7 @@ bool NativeObject::allocDictionarySlot(JSContext* cx, HandleNativeObject obj,
*slotp = slot;
return obj->setSlotSpan(cx, slot + 1);
return obj->ensureSlotsForDictionaryObject(cx, slot + 1);
}
void NativeObject::freeSlot(JSContext* cx, uint32_t slot) {

View File

@ -452,6 +452,7 @@ static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) ==
*/
class alignas(HeapSlot) ObjectSlots {
uint32_t capacity_;
uint32_t dictionarySlotSpan_;
public:
static constexpr size_t VALUES_PER_HEADER = 1;
@ -475,13 +476,23 @@ class alignas(HeapSlot) ObjectSlots {
static constexpr size_t offsetOfCapacity() {
return offsetof(ObjectSlots, capacity_);
}
static constexpr size_t offsetOfDictionarySlotSpan() {
return offsetof(ObjectSlots, dictionarySlotSpan_);
}
static constexpr size_t offsetOfSlots() { return sizeof(ObjectSlots); }
static constexpr int32_t offsetOfDictionarySlotSpanFromSlots() {
return int32_t(offsetOfDictionarySlotSpan()) - int32_t(offsetOfSlots());
}
explicit ObjectSlots(uint32_t capacity) : capacity_(capacity) {}
constexpr explicit ObjectSlots(uint32_t capacity, uint32_t dictionarySlotSpan)
: capacity_(capacity), dictionarySlotSpan_(dictionarySlotSpan) {}
uint32_t capacity() const { return capacity_; }
uint32_t dictionarySlotSpan() const { return dictionarySlotSpan_; }
HeapSlot* slots() {
void setDictionarySlotSpan(uint32_t span) { dictionarySlotSpan_ = span; }
HeapSlot* slots() const {
return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectSlots));
}
};
@ -494,6 +505,12 @@ class alignas(HeapSlot) ObjectSlots {
extern HeapSlot* const emptyObjectElements;
extern HeapSlot* const emptyObjectElementsShared;
/*
* Shared singletons for objects with no dynamic slots.
*/
extern HeapSlot* const emptyObjectSlots;
extern HeapSlot* const emptyObjectSlotsForDictionaryObject[];
class AutoCheckShapeConsistency;
class GCMarker;
class Shape;
@ -663,12 +680,16 @@ class NativeObject : public JSObject {
* Update the slot span directly for a dictionary object, and allocate
* slots to cover the new span if necessary.
*/
bool setSlotSpan(JSContext* cx, uint32_t span);
bool ensureSlotsForDictionaryObject(JSContext* cx, uint32_t span);
static MOZ_MUST_USE bool toDictionaryMode(JSContext* cx,
HandleNativeObject obj);
private:
inline void setEmptyDynamicSlots(uint32_t dictonarySlotSpan);
inline void setDictionaryModeSlotSpan(uint32_t span);
friend class TenuringTracer;
/*
@ -767,7 +788,11 @@ class NativeObject : public JSObject {
public:
/* Object allocation may directly initialize slots so this is public. */
void initSlots(HeapSlot* slots) { slots_ = slots; }
void initSlots(HeapSlot* slots) {
MOZ_ASSERT(slots);
slots_ = slots;
}
inline void initEmptyDynamicSlots();
static MOZ_MUST_USE bool generateOwnShape(JSContext* cx,
HandleNativeObject obj,
@ -818,11 +843,20 @@ class NativeObject : public JSObject {
uint32_t slotSpan() const {
if (inDictionaryMode()) {
return lastProperty()->base()->slotSpan();
return dictionaryModeSlotSpan();
} else {
MOZ_ASSERT(getSlotsHeader()->dictionarySlotSpan() == 0);
// Get the class from the object group rather than the base shape to avoid
// a race between Shape::ensureOwnBaseShape and background sweeping.
return lastProperty()->slotSpan(getClass());
}
// Get the class from the object group rather than the base shape to avoid a
// race between Shape::ensureOwnBaseShape and background sweeping.
return lastProperty()->slotSpan(getClass());
}
uint32_t dictionaryModeSlotSpan() const {
MOZ_ASSERT(inDictionaryMode());
uint32_t span = getSlotsHeader()->dictionarySlotSpan();
MOZ_ASSERT(span == lastProperty()->base()->slotSpan());
return span;
}
/* Whether a slot is at a fixed offset from this object. */
@ -880,6 +914,8 @@ class NativeObject : public JSObject {
bool growSlots(JSContext* cx, uint32_t oldCapacity, uint32_t newCapacity);
void shrinkSlots(JSContext* cx, uint32_t oldCapacity, uint32_t newCapacity);
bool allocateSlots(JSContext* cx, uint32_t newCapacity);
/*
* This method is static because it's called from JIT code. On OOM, returns
* false without leaving a pending exception on the context.
@ -894,7 +930,7 @@ class NativeObject : public JSObject {
*/
static bool addDenseElementPure(JSContext* cx, NativeObject* obj);
bool hasDynamicSlots() const { return !!slots_; }
bool hasDynamicSlots() const { return getSlotsHeader()->capacity(); }
/* Compute the number of dynamic slots required for this object. */
MOZ_ALWAYS_INLINE uint32_t calculateDynamicSlots() const;

View File

@ -425,7 +425,7 @@ Shape* Shape::replaceLastProperty(JSContext* cx, StackBaseShape& base,
return nullptr;
}
if (child.slot() >= obj->lastProperty()->base()->slotSpan()) {
if (!obj->setSlotSpan(cx, child.slot() + 1)) {
if (!obj->ensureSlotsForDictionaryObject(cx, child.slot() + 1)) {
new (shape) Shape(obj->lastProperty()->base()->unowned(), 0);
return nullptr;
}
@ -540,6 +540,7 @@ bool js::NativeObject::toDictionaryMode(JSContext* cx, HandleNativeObject obj) {
obj->setShape(root);
MOZ_ASSERT(obj->inDictionaryMode());
obj->setDictionaryModeSlotSpan(span);
root->base()->setSlotSpan(span);
return true;
@ -867,7 +868,7 @@ Shape* NativeObject::addEnumerableDataProperty(JSContext* cx,
return nullptr;
}
if (slot >= obj->lastProperty()->base()->slotSpan()) {
if (MOZ_UNLIKELY(!obj->setSlotSpan(cx, slot + 1))) {
if (MOZ_UNLIKELY(!obj->ensureSlotsForDictionaryObject(cx, slot + 1))) {
new (shape) Shape(obj->lastProperty()->base()->unowned(), 0);
return nullptr;
}