Source code

Revision control

Other Tools

/* -*- 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/. */
/* JS symbol tables. */
#include "vm/Shape-inl.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/PodOperations.h"
#include "gc/FreeOp.h"
#include "gc/HashUtil.h"
#include "gc/Policy.h"
#include "gc/PublicIterators.h"
#include "js/friend/WindowProxy.h" // js::IsWindow
#include "js/HashTable.h"
#include "js/UniquePtr.h"
#include "util/Text.h"
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/Caches-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/Realm-inl.h"
using namespace js;
using mozilla::CeilingLog2Size;
using mozilla::PodZero;
using JS::AutoCheckCannotGC;
Shape* const ShapeTable::Entry::SHAPE_REMOVED =
(Shape*)ShapeTable::Entry::SHAPE_COLLISION;
bool ShapeIC::init(JSContext* cx) {
size_ = MAX_SIZE;
entries_.reset(cx->pod_calloc<Entry>(size_));
return (!entries_) ? false : true;
}
bool ShapeTable::init(JSContext* cx, Shape* lastProp) {
uint32_t sizeLog2 = CeilingLog2Size(entryCount_);
uint32_t size = Bit(sizeLog2);
if (entryCount_ >= size - (size >> 2)) {
sizeLog2++;
}
if (sizeLog2 < MIN_SIZE_LOG2) {
sizeLog2 = MIN_SIZE_LOG2;
}
size = Bit(sizeLog2);
entries_.reset(cx->pod_calloc<Entry>(size));
if (!entries_) {
return false;
}
MOZ_ASSERT(sizeLog2 <= HASH_BITS);
hashShift_ = HASH_BITS - sizeLog2;
for (Shape::Range<NoGC> r(lastProp); !r.empty(); r.popFront()) {
Shape& shape = r.front();
Entry& entry = searchUnchecked<MaybeAdding::Adding>(shape.propid());
/*
* Beware duplicate args and arg vs. var conflicts: the youngest shape
* (nearest to lastProp) must win. See bug 600067.
*/
if (!entry.shape()) {
entry.setPreservingCollision(&shape);
}
}
MOZ_ASSERT(capacity() == size);
MOZ_ASSERT(size >= MIN_SIZE);
MOZ_ASSERT(!needsToGrow());
return true;
}
void Shape::removeFromDictionary(NativeObject* obj) {
MOZ_ASSERT(inDictionary());
MOZ_ASSERT(obj->inDictionaryMode());
MOZ_ASSERT(!dictNext.isNone());
MOZ_ASSERT(obj->shape()->inDictionary());
MOZ_ASSERT(obj->shape()->dictNext.toObject() == obj);
if (parent) {
parent->setDictionaryNextPtr(dictNext);
}
dictNext.setPrev(parent);
clearDictionaryNextPtr();
obj->shape()->clearCachedBigEnoughForShapeTable();
}
void Shape::insertIntoDictionaryBefore(DictionaryShapeLink next) {
// Don't assert inDictionaryMode() here because we may be called from
// NativeObject::toDictionaryMode via Shape::initDictionaryShape.
MOZ_ASSERT(inDictionary());
MOZ_ASSERT(dictNext.isNone());
Shape* prev = next.prev();
#ifdef DEBUG
if (prev) {
MOZ_ASSERT(prev->inDictionary());
MOZ_ASSERT(prev->dictNext == next);
MOZ_ASSERT(zone() == prev->zone());
}
#endif
setParent(prev);
if (parent) {
parent->setNextDictionaryShape(this);
}
setDictionaryNextPtr(next);
dictNext.setPrev(this);
}
void Shape::handoffTableTo(Shape* shape) {
MOZ_ASSERT(inDictionary() && shape->inDictionary());
MOZ_ASSERT(this != shape);
ShapeTable* table = cache_.getTablePointer();
shape->setTable(table);
cache_ = ShapeCachePtr();
// Note: for shape tables only sizeof(ShapeTable) is tracked. See the TODO in
// Shape::hashify.
RemoveCellMemory(this, sizeof(ShapeTable), MemoryUse::ShapeCache);
AddCellMemory(shape, sizeof(ShapeTable), MemoryUse::ShapeCache);
}
/* static */
bool Shape::hashify(JSContext* cx, Shape* shape) {
MOZ_ASSERT(!shape->hasTable());
UniquePtr<ShapeTable> table =
cx->make_unique<ShapeTable>(shape->entryCount());
if (!table) {
return false;
}
if (!table->init(cx, shape)) {
return false;
}
shape->maybePurgeCache(cx->defaultFreeOp());
shape->setTable(table.release());
// TODO: The contents of ShapeTable is not currently tracked, only the object
// itself.
AddCellMemory(shape, sizeof(ShapeTable), MemoryUse::ShapeCache);
return true;
}
void ShapeCachePtr::maybePurgeCache(JSFreeOp* fop, Shape* shape) {
if (isTable()) {
ShapeTable* table = getTablePointer();
if (table->freeList() == SHAPE_INVALID_SLOT) {
fop->delete_(shape, getTablePointer(), MemoryUse::ShapeCache);
p = 0;
}
} else if (isIC()) {
fop->delete_<ShapeIC>(shape, getICPointer(), MemoryUse::ShapeCache);
p = 0;
}
}
/* static */
bool Shape::cachify(JSContext* cx, Shape* shape) {
MOZ_ASSERT(!shape->hasTable() && !shape->hasIC());
UniquePtr<ShapeIC> ic = cx->make_unique<ShapeIC>();
if (!ic) {
return false;
}
if (!ic->init(cx)) {
return false;
}
shape->setIC(ic.release());
AddCellMemory(shape, sizeof(ShapeIC), MemoryUse::ShapeCache);
return true;
}
bool ShapeTable::change(JSContext* cx, int log2Delta) {
MOZ_ASSERT(entries_);
MOZ_ASSERT(-1 <= log2Delta && log2Delta <= 1);
/*
* Grow, shrink, or compress by changing this->entries_.
*/
uint32_t oldLog2 = HASH_BITS - hashShift_;
uint32_t newLog2 = oldLog2 + log2Delta;
uint32_t oldSize = Bit(oldLog2);
uint32_t newSize = Bit(newLog2);
Entry* newTable = cx->maybe_pod_calloc<Entry>(newSize);
if (!newTable) {
return false;
}
/* Now that we have newTable allocated, update members. */
MOZ_ASSERT(newLog2 <= HASH_BITS);
hashShift_ = HASH_BITS - newLog2;
removedCount_ = 0;
Entry* oldTable = entries_.release();
entries_.reset(newTable);
/* Copy only live entries, leaving removed and free ones behind. */
AutoCheckCannotGC nogc;
for (Entry* oldEntry = oldTable; oldSize != 0; oldEntry++) {
if (Shape* shape = oldEntry->shape()) {
Entry& entry = search<MaybeAdding::Adding>(shape->propid(), nogc);
MOZ_ASSERT(entry.isFree());
entry.setShape(shape);
}
oldSize--;
}
MOZ_ASSERT(capacity() == newSize);
/* Finally, free the old entries storage. */
js_free(oldTable);
return true;
}
bool ShapeTable::grow(JSContext* cx) {
MOZ_ASSERT(needsToGrow());
uint32_t size = capacity();
int delta = removedCount_ < (size >> 2);
MOZ_ASSERT(entryCount_ + removedCount_ <= size - 1);
if (!change(cx, delta)) {
if (entryCount_ + removedCount_ == size - 1) {
ReportOutOfMemory(cx);
return false;
}
}
return true;
}
void ShapeCachePtr::trace(JSTracer* trc) {
if (isIC()) {
getICPointer()->trace(trc);
} else if (isTable()) {
getTablePointer()->trace(trc);
}
}
void ShapeIC::trace(JSTracer* trc) {
for (size_t i = 0; i < entryCount(); i++) {
Entry& entry = entries_[i];
if (entry.shape_) {
TraceManuallyBarrieredEdge(trc, &entry.shape_, "ShapeIC shape");
}
}
}
void ShapeTable::trace(JSTracer* trc) {
for (size_t i = 0; i < capacity(); i++) {
Entry& entry = getEntry(i);
Shape* shape = entry.shape();
if (shape) {
TraceManuallyBarrieredEdge(trc, &shape, "ShapeTable shape");
if (shape != entry.shape()) {
entry.setPreservingCollision(shape);
}
}
}
}
inline void ShapeCachePtr::destroy(JSFreeOp* fop, Shape* shape) {
if (isTable()) {
fop->delete_(shape, getTablePointer(), MemoryUse::ShapeCache);
} else if (isIC()) {
fop->delete_(shape, getICPointer(), MemoryUse::ShapeCache);
}
p = 0;
}
#ifdef JSGC_HASH_TABLE_CHECKS
void ShapeCachePtr::checkAfterMovingGC() {
if (isIC()) {
getICPointer()->checkAfterMovingGC();
} else if (isTable()) {
getTablePointer()->checkAfterMovingGC();
}
}
void ShapeIC::checkAfterMovingGC() {
for (size_t i = 0; i < entryCount(); i++) {
Entry& entry = entries_[i];
Shape* shape = entry.shape_;
if (shape) {
CheckGCThingAfterMovingGC(shape);
}
}
}
void ShapeTable::checkAfterMovingGC() {
for (size_t i = 0; i < capacity(); i++) {
Entry& entry = getEntry(i);
Shape* shape = entry.shape();
if (shape) {
CheckGCThingAfterMovingGC(shape);
}
}
}
#endif
/* static */
Shape* Shape::replaceLastProperty(JSContext* cx, ObjectFlags objectFlags,
TaggedProto proto, HandleShape shape) {
MOZ_ASSERT(!shape->inDictionary());
if (!shape->parent) {
/* Treat as resetting the initial property of the shape hierarchy. */
gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
return EmptyShape::getInitialShape(
cx, shape->getObjectClass(), shape->realm(), proto, kind, objectFlags);
}
Rooted<StackShape> child(cx, StackShape(shape));
child.setObjectFlags(objectFlags);
if (proto != shape->proto()) {
Rooted<StackBaseShape> base(
cx, StackBaseShape(shape->getObjectClass(), shape->realm(), proto));
BaseShape* nbase = BaseShape::get(cx, base);
if (!nbase) {
return nullptr;
}
child.setBase(nbase);
}
return cx->zone()->propertyTree().getChild(cx, shape->parent, child);
}
/*
* Get or create a property-tree or dictionary child property of |parent|,
* which must be lastProperty() if inDictionaryMode(), else parent must be
* one of lastProperty() or lastProperty()->parent.
*/
/* static */ MOZ_ALWAYS_INLINE Shape* NativeObject::getChildDataProperty(
JSContext* cx, HandleNativeObject obj, HandleShape parent,
MutableHandle<StackShape> child) {
MOZ_ASSERT(child.isDataProperty());
if (child.hasMissingSlot()) {
uint32_t slot;
if (obj->inDictionaryMode()) {
if (!allocDictionarySlot(cx, obj, &slot)) {
return nullptr;
}
} else {
slot = obj->slotSpan();
MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
// Objects with many properties are converted to dictionary
// mode, so we can't overflow SHAPE_MAXIMUM_SLOT here.
MOZ_ASSERT(slot <
JSSLOT_FREE(obj->getClass()) + PropertyTree::MAX_HEIGHT);
MOZ_ASSERT(slot < SHAPE_MAXIMUM_SLOT);
}
child.setSlot(slot);
} else {
/*
* Slots can only be allocated out of order on objects in
* dictionary mode. Otherwise the child's slot must be after the
* parent's slot (if it has one), because slot number determines
* slot span for objects with that shape. Usually child slot
* *immediately* follows parent slot, but there may be a slot gap
* when the object uses some -- but not all -- of its reserved
* slots to store properties.
*/
MOZ_ASSERT(obj->inDictionaryMode() || parent->hasMissingSlot() ||
child.slot() == parent->maybeSlot() + 1 ||
(parent->maybeSlot() + 1 < JSSLOT_FREE(obj->getClass()) &&
child.slot() == JSSLOT_FREE(obj->getClass())));
}
if (obj->inDictionaryMode()) {
MOZ_ASSERT(parent == obj->lastProperty());
Shape* shape = Allocate<Shape>(cx);
if (!shape) {
return nullptr;
}
if (child.slot() >= obj->slotSpan()) {
if (!obj->ensureSlotsForDictionaryObject(cx, child.slot() + 1)) {
new (shape) Shape(obj->lastProperty()->base(), ObjectFlags(), 0);
return nullptr;
}
}
shape->initDictionaryShape(child, obj->numFixedSlots(),
DictionaryShapeLink(obj));
return shape;
}
Shape* shape = cx->zone()->propertyTree().inlinedGetChild(cx, parent, child);
if (!shape) {
return nullptr;
}
MOZ_ASSERT(shape->parent == parent);
MOZ_ASSERT_IF(parent != obj->lastProperty(),
parent == obj->lastProperty()->parent);
if (!obj->setLastProperty(cx, shape)) {
return nullptr;
}
return shape;
}
/* static */ MOZ_ALWAYS_INLINE Shape* NativeObject::getChildAccessorProperty(
JSContext* cx, HandleNativeObject obj, HandleShape parent,
MutableHandle<StackShape> child) {
MOZ_ASSERT(!child.isDataProperty());
// Accessor properties have no slot, but slot_ will reflect that of parent.
child.setSlot(parent->maybeSlot());
if (obj->inDictionaryMode()) {
MOZ_ASSERT(parent == obj->lastProperty());
Shape* shape = Allocate<AccessorShape>(cx);
if (!shape) {
return nullptr;
}
shape->initDictionaryShape(child, obj->numFixedSlots(),
DictionaryShapeLink(obj));
return shape;
}
Shape* shape = cx->zone()->propertyTree().inlinedGetChild(cx, parent, child);
if (!shape) {
return nullptr;
}
MOZ_ASSERT(shape->parent == parent);
MOZ_ASSERT_IF(parent != obj->lastProperty(),
parent == obj->lastProperty()->parent);
if (!obj->setLastProperty(cx, shape)) {
return nullptr;
}
return shape;
}
/* static */
bool js::NativeObject::toDictionaryMode(JSContext* cx, HandleNativeObject obj) {
MOZ_ASSERT(!obj->inDictionaryMode());
MOZ_ASSERT(cx->isInsideCurrentCompartment(obj));
uint32_t span = obj->slotSpan();
// Clone the shapes into a new dictionary list. Don't update the last
// property of this object until done, otherwise a GC triggered while
// creating the dictionary will get the wrong slot span for this object.
RootedShape root(cx);
RootedShape dictionaryShape(cx);
RootedShape shape(cx, obj->lastProperty());
while (shape) {
MOZ_ASSERT(!shape->inDictionary());
Shape* dprop = shape->isAccessorShape() ? Allocate<AccessorShape>(cx)
: Allocate<Shape>(cx);
if (!dprop) {
ReportOutOfMemory(cx);
return false;
}
DictionaryShapeLink next;
if (dictionaryShape) {
next.setShape(dictionaryShape);
}
StackShape child(shape);
dprop->initDictionaryShape(child, obj->numFixedSlots(), next);
if (!dictionaryShape) {
root = dprop;
}
MOZ_ASSERT(!dprop->hasTable());
dictionaryShape = dprop;
shape = shape->previous();
}
if (!Shape::hashify(cx, root)) {
ReportOutOfMemory(cx);
return false;
}
if (IsInsideNursery(obj) &&
!cx->nursery().queueDictionaryModeObjectToSweep(obj)) {
ReportOutOfMemory(cx);
return false;
}
MOZ_ASSERT(root->dictNext.isNone());
root->setDictionaryObject(obj);
obj->setShape(root);
MOZ_ASSERT(obj->inDictionaryMode());
obj->setDictionaryModeSlotSpan(span);
return true;
}
static bool ShouldConvertToDictionary(NativeObject* obj) {
/*
* Use a lower limit if this object is likely a hashmap (SETELEM was used
* to set properties).
*/
if (obj->hadElementsAccess()) {
return obj->lastProperty()->entryCount() >=
PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS;
}
return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT;
}
static MOZ_ALWAYS_INLINE ObjectFlags GetObjectFlagsForNewShape(Shape* last,
jsid id) {
ObjectFlags flags = last->objectFlags();
uint32_t index;
if (IdIsIndex(id, &index)) {
flags.setFlag(ObjectFlag::Indexed);
} else if (JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id)->isInterestingSymbol()) {
flags.setFlag(ObjectFlag::HasInterestingSymbol);
}
return flags;
}
namespace js {
class MOZ_RAII AutoCheckShapeConsistency {
#ifdef DEBUG
HandleNativeObject obj_;
#endif
public:
explicit AutoCheckShapeConsistency(HandleNativeObject obj)
#ifdef DEBUG
: obj_(obj)
#endif
{
}
#ifdef DEBUG
~AutoCheckShapeConsistency() { obj_->checkShapeConsistency(); }
#endif
};
} // namespace js
/* static */ MOZ_ALWAYS_INLINE bool
NativeObject::maybeConvertToOrGrowDictionaryForAdd(
JSContext* cx, HandleNativeObject obj, HandleId id, ShapeTable** table,
ShapeTable::Entry** entry, const AutoKeepShapeCaches& keep) {
MOZ_ASSERT(!!*table == !!*entry);
// The code below deals with either converting obj to dictionary mode or
// growing an object that's already in dictionary mode.
if (!obj->inDictionaryMode()) {
if (!ShouldConvertToDictionary(obj)) {
return true;
}
if (!toDictionaryMode(cx, obj)) {
return false;
}
*table = obj->lastProperty()->maybeTable(keep);
} else {
if (!(*table)->needsToGrow()) {
return true;
}
if (!(*table)->grow(cx)) {
return false;
}
}
*entry = &(*table)->search<MaybeAdding::Adding>(id, keep);
MOZ_ASSERT(!(*entry)->shape());
return true;
}
MOZ_ALWAYS_INLINE void Shape::updateDictionaryTable(
ShapeTable* table, ShapeTable::Entry* entry,
const AutoKeepShapeCaches& keep) {
MOZ_ASSERT(table);
MOZ_ASSERT(entry);
MOZ_ASSERT(inDictionary());
// Store this Shape in the table entry.
entry->setPreservingCollision(this);
table->incEntryCount();
// Pass the table along to the new last property, namely *this.
MOZ_ASSERT(parent->maybeTable(keep) == table);
parent->handoffTableTo(this);
}
static void AssertValidPropertyOp(NativeObject* obj, GetterOp getter,
SetterOp setter, unsigned attrs) {
// We only support PropertyOp accessors on ArrayObject and ArgumentsObject
// and we don't want to add more of these properties (bug 1404885).
#ifdef DEBUG
if ((getter && !(attrs & JSPROP_GETTER)) ||
(setter && !(attrs & JSPROP_SETTER))) {
MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<ArgumentsObject>());
}
#endif
}
/* static */
Shape* NativeObject::addAccessorPropertyInternal(
JSContext* cx, HandleNativeObject obj, HandleId id, GetterOp getter,
SetterOp setter, unsigned attrs, ShapeTable* table,
ShapeTable::Entry* entry, const AutoKeepShapeCaches& keep) {
AutoCheckShapeConsistency check(obj);
AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
AssertValidPropertyOp(obj, getter, setter, attrs);
if (!maybeConvertToOrGrowDictionaryForAdd(cx, obj, id, &table, &entry,
keep)) {
return nullptr;
}
// Find or create a property tree node labeled by our arguments.
RootedShape shape(cx);
{
RootedShape last(cx, obj->lastProperty());
ObjectFlags objectFlags = GetObjectFlagsForNewShape(last, id);
Rooted<StackShape> child(cx, StackShape(last->base(), objectFlags, id,
SHAPE_INVALID_SLOT, attrs));
child.updateGetterSetter(getter, setter);
shape = getChildAccessorProperty(cx, obj, last, &child);
if (!shape) {
return nullptr;
}
}
MOZ_ASSERT(shape == obj->lastProperty());
if (table) {
shape->updateDictionaryTable(table, entry, keep);
}
return shape;
}
/* static */
Shape* NativeObject::addDataPropertyInternal(JSContext* cx,
HandleNativeObject obj,
HandleId id, uint32_t slot,
unsigned attrs, ShapeTable* table,
ShapeTable::Entry* entry,
const AutoKeepShapeCaches& keep) {
AutoCheckShapeConsistency check(obj);
// The slot, if any, must be a reserved slot.
MOZ_ASSERT(slot == SHAPE_INVALID_SLOT ||
slot < JSCLASS_RESERVED_SLOTS(obj->getClass()));
if (!maybeConvertToOrGrowDictionaryForAdd(cx, obj, id, &table, &entry,
keep)) {
return nullptr;
}
// Find or create a property tree node labeled by our arguments.
RootedShape shape(cx);
{
RootedShape last(cx, obj->lastProperty());
ObjectFlags objectFlags = GetObjectFlagsForNewShape(last, id);
Rooted<StackShape> child(
cx, StackShape(last->base(), objectFlags, id, slot, attrs));
shape = getChildDataProperty(cx, obj, last, &child);
if (!shape) {
return nullptr;
}
}
MOZ_ASSERT(shape == obj->lastProperty());
if (table) {
shape->updateDictionaryTable(table, entry, keep);
}
return shape;
}
static MOZ_ALWAYS_INLINE Shape* PropertyTreeReadBarrier(JSContext* cx,
Shape* parent,
Shape* shape) {
JS::Zone* zone = shape->zone();
if (zone->needsIncrementalBarrier()) {
// We need a read barrier for the shape tree, since these are weak
// pointers.
ReadBarrier(shape);
return shape;
}
if (MOZ_LIKELY(!zone->isGCSweepingOrCompacting() ||
!IsAboutToBeFinalizedUnbarriered(&shape))) {
if (shape->isMarkedGray()) {
UnmarkGrayShapeRecursively(shape);
}
return shape;
}
// The shape we've found is unreachable and due to be finalized, so
// remove our weak reference to it and don't use it.
MOZ_ASSERT(parent->isMarkedAny());
parent->removeChild(cx->defaultFreeOp(), shape);
return nullptr;
}
/* static */
Shape* NativeObject::addEnumerableDataProperty(JSContext* cx,
HandleNativeObject obj,
HandleId id) {
// Like addProperty(Internal), but optimized for the common case of adding a
// new enumerable data property.
AutoCheckShapeConsistency check(obj);
ObjectFlags objectFlags = GetObjectFlagsForNewShape(obj->lastProperty(), id);
// Fast path for non-dictionary shapes with a single child.
do {
AutoCheckCannotGC nogc;
Shape* lastProperty = obj->lastProperty();
if (lastProperty->inDictionary()) {
break;
}
ShapeChildren* childp = &lastProperty->children;
if (!childp->isSingleShape()) {
break;
}
Shape* child = childp->toSingleShape();
MOZ_ASSERT(!child->inDictionary());
if (child->propidRaw() != id || child->objectFlags() != objectFlags ||
child->isAccessorShape() || child->attributes() != JSPROP_ENUMERATE ||
child->base() != lastProperty->base()) {
break;
}
MOZ_ASSERT(child->isDataProperty());
child = PropertyTreeReadBarrier(cx, lastProperty, child);
if (!child) {
break;
}
if (!obj->setLastProperty(cx, child)) {
return nullptr;
}
return child;
} while (0);
AutoKeepShapeCaches keep(cx);
ShapeTable* table = nullptr;
ShapeTable::Entry* entry = nullptr;
if (!obj->inDictionaryMode()) {
if (MOZ_UNLIKELY(ShouldConvertToDictionary(obj))) {
if (!toDictionaryMode(cx, obj)) {
return nullptr;
}
table = obj->lastProperty()->maybeTable(keep);
entry = &table->search<MaybeAdding::Adding>(id, keep);
}
} else {
table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table) {
return nullptr;
}
if (table->needsToGrow()) {
if (!table->grow(cx)) {
return nullptr;
}
}
entry = &table->search<MaybeAdding::Adding>(id, keep);
MOZ_ASSERT(!entry->shape());
}
MOZ_ASSERT(!!table == !!entry);
/* Find or create a property tree node labeled by our arguments. */
Shape* shape;
if (obj->inDictionaryMode()) {
uint32_t slot;
if (!allocDictionarySlot(cx, obj, &slot)) {
return nullptr;
}
Rooted<StackShape> child(
cx, StackShape(obj->lastProperty()->base(), objectFlags, id, slot,
JSPROP_ENUMERATE));
shape = Allocate<Shape>(cx);
if (!shape) {
return nullptr;
}
if (slot >= obj->slotSpan()) {
if (MOZ_UNLIKELY(!obj->ensureSlotsForDictionaryObject(cx, slot + 1))) {
new (shape) Shape(obj->lastProperty()->base(), ObjectFlags(), 0);
return nullptr;
}
}
shape->initDictionaryShape(child, obj->numFixedSlots(),
DictionaryShapeLink(obj));
} else {
uint32_t slot = obj->slotSpan();
MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
// Objects with many properties are converted to dictionary
// mode, so we can't overflow SHAPE_MAXIMUM_SLOT here.
MOZ_ASSERT(slot < JSSLOT_FREE(obj->getClass()) + PropertyTree::MAX_HEIGHT);
MOZ_ASSERT(slot < SHAPE_MAXIMUM_SLOT);
Shape* last = obj->lastProperty();
Rooted<StackShape> child(
cx, StackShape(last->base(), objectFlags, id, slot, JSPROP_ENUMERATE));
shape = cx->zone()->propertyTree().inlinedGetChild(cx, last, child);
if (!shape) {
return nullptr;
}
if (!obj->setLastProperty(cx, shape)) {
return nullptr;
}
}
MOZ_ASSERT(shape == obj->lastProperty());
if (table) {
shape->updateDictionaryTable(table, entry, keep);
}
return shape;
}
/*
* Assert some invariants that should hold when changing properties. It's the
* responsibility of the callers to ensure these hold.
*/
static void AssertCanChangeAttrs(Shape* shape, unsigned attrs) {
#ifdef DEBUG
if (shape->configurable()) {
return;
}
/* A permanent property must stay permanent. */
MOZ_ASSERT(attrs & JSPROP_PERMANENT);
/* Reject attempts to remove a slot from the permanent data property. */
MOZ_ASSERT_IF(shape->isDataProperty(),
!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
#endif
}
static void AssertValidArrayIndex(NativeObject* obj, jsid id) {
#ifdef DEBUG
if (obj->is<ArrayObject>()) {
ArrayObject* arr = &obj->as<ArrayObject>();
uint32_t index;
if (IdIsIndex(id, &index)) {
MOZ_ASSERT(index < arr->length() || arr->lengthIsWritable());
}
}
#endif
}
/* static */
bool NativeObject::maybeToDictionaryModeForPut(JSContext* cx,
HandleNativeObject obj,
MutableHandleShape shape) {
// Overwriting a non-last property requires switching to dictionary mode.
// The shape tree is shared immutable, and we can't removeProperty and then
// addAccessorPropertyInternal because a failure under add would lose data.
if (shape == obj->lastProperty() || obj->inDictionaryMode()) {
return true;
}
if (!toDictionaryMode(cx, obj)) {
return false;
}
AutoCheckCannotGC nogc;
ShapeTable* table = obj->lastProperty()->maybeTable(nogc);
MOZ_ASSERT(table);
shape.set(
table->search<MaybeAdding::NotAdding>(shape->propid(), nogc).shape());
return true;
}
/* static */
Shape* NativeObject::putDataProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, unsigned attrs) {
MOZ_ASSERT(!JSID_IS_VOID(id));
AutoCheckShapeConsistency check(obj);
AssertValidArrayIndex(obj, id);
// Search for id in order to claim its entry if table has been allocated.
AutoKeepShapeCaches keep(cx);
RootedShape shape(cx);
{
ShapeTable* table;
ShapeTable::Entry* entry;
if (!Shape::search<MaybeAdding::Adding>(cx, obj->lastProperty(), id, keep,
shape.address(), &table, &entry)) {
return nullptr;
}
if (!shape) {
MOZ_ASSERT(
obj->isExtensible() ||
(JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))),
"Can't add new property to non-extensible object");
return addDataPropertyInternal(cx, obj, id, SHAPE_INVALID_SLOT, attrs,
table, entry, keep);
}
// Property exists: search must have returned a valid entry.
MOZ_ASSERT_IF(entry, !entry->isRemoved());
}
AssertCanChangeAttrs(shape, attrs);
// If the caller wants to allocate a slot, but doesn't care which slot,
// copy the existing shape's slot into slot so we can match shape, if all
// other members match.
bool hadSlot = shape->isDataProperty();
uint32_t oldSlot = shape->maybeSlot();
uint32_t slot = hadSlot ? oldSlot : SHAPE_INVALID_SLOT;
#ifdef DEBUG
// Property |id| already exists, so object flags should already be set.
ObjectFlags objectFlags = GetObjectFlagsForNewShape(obj->lastProperty(), id);
MOZ_ASSERT(objectFlags == obj->lastProperty()->objectFlags());
#endif
// Now that we've possibly preserved slot, check whether all members match.
// If so, this is a redundant "put" and we can return without more work.
if (shape->matchesParamsAfterId(obj->lastProperty()->base(),
obj->lastProperty()->objectFlags(), slot,
attrs, nullptr, nullptr)) {
return shape;
}
if (!maybeToDictionaryModeForPut(cx, obj, &shape)) {
return nullptr;
}
MOZ_ASSERT_IF(shape->isDataProperty(), shape->slot() == slot);
if (obj->inDictionaryMode()) {
// Updating some property in a dictionary-mode object. Create a new
// shape for the existing property, and also generate a new shape for
// the last property of the dictionary (unless the modified property
// is also the last property).
bool updateLast = (shape == obj->lastProperty());
shape = NativeObject::replaceWithNewEquivalentShape(
cx, obj, shape, nullptr,
/* accessorShape = */ false);
if (!shape) {
return nullptr;
}
if (!updateLast && !NativeObject::generateOwnShape(cx, obj)) {
return nullptr;
}
if (slot == SHAPE_INVALID_SLOT) {
if (!allocDictionarySlot(cx, obj, &slot)) {
return nullptr;
}
}
shape->setBase(obj->lastProperty()->base());
shape->setSlot(slot);
shape->attrs = uint8_t(attrs);
shape->immutableFlags &= ~Shape::ACCESSOR_SHAPE;
shape->immutableFlags |= Shape::IN_DICTIONARY;
} else {
// Updating the last property in a non-dictionary-mode object. Find an
// alternate shared child of the last property's previous shape.
MOZ_ASSERT(shape == obj->lastProperty());
// Find or create a property tree node labeled by our arguments.
Rooted<StackShape> child(
cx, StackShape(obj->lastProperty()->base(),
obj->lastProperty()->objectFlags(), id, slot, attrs));
RootedShape parent(cx, shape->parent);
shape = getChildDataProperty(cx, obj, parent, &child);
if (!shape) {
return nullptr;
}
}
MOZ_ASSERT(shape->isDataProperty());
return shape;
}
/* static */
Shape* NativeObject::putAccessorProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, GetterOp getter,
SetterOp setter, unsigned attrs) {
MOZ_ASSERT(!JSID_IS_VOID(id));
AutoCheckShapeConsistency check(obj);
AssertValidArrayIndex(obj, id);
AssertValidPropertyOp(obj, getter, setter, attrs);
AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
// Search for id in order to claim its entry if table has been allocated.
AutoKeepShapeCaches keep(cx);
RootedShape shape(cx);
{
ShapeTable* table;
ShapeTable::Entry* entry;
if (!Shape::search<MaybeAdding::Adding>(cx, obj->lastProperty(), id, keep,
shape.address(), &table, &entry)) {
return nullptr;
}
if (!shape) {
MOZ_ASSERT(
obj->isExtensible() ||
(JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))),
"Can't add new property to non-extensible object");
return addAccessorPropertyInternal(cx, obj, id, getter, setter, attrs,
table, entry, keep);
}
// Property exists: search must have returned a valid entry.
MOZ_ASSERT_IF(entry, !entry->isRemoved());
}
AssertCanChangeAttrs(shape, attrs);
bool hadSlot = shape->isDataProperty();
uint32_t oldSlot = shape->maybeSlot();
#ifdef DEBUG
// Property |id| already exists, so object flags should already be set.
ObjectFlags objectFlags = GetObjectFlagsForNewShape(obj->lastProperty(), id);
MOZ_ASSERT(objectFlags == obj->lastProperty()->objectFlags());
#endif
// Check whether all members match. If so, this is a redundant "put" and we
// can return without more work.
if (shape->matchesParamsAfterId(obj->lastProperty()->base(),
obj->lastProperty()->objectFlags(),
SHAPE_INVALID_SLOT, attrs, getter, setter)) {
return shape;
}
if (!maybeToDictionaryModeForPut(cx, obj, &shape)) {
return nullptr;
}
if (obj->inDictionaryMode()) {
// Updating some property in a dictionary-mode object. Create a new
// shape for the existing property, and also generate a new shape for
// the last property of the dictionary (unless the modified property
// is also the last property).
bool updateLast = (shape == obj->lastProperty());
shape =
NativeObject::replaceWithNewEquivalentShape(cx, obj, shape, nullptr,
/* accessorShape = */ true);
if (!shape) {
return nullptr;
}
if (!updateLast && !NativeObject::generateOwnShape(cx, obj)) {
return nullptr;
}
shape->setBase(obj->lastProperty()->base());
shape->setSlot(SHAPE_INVALID_SLOT);
shape->attrs = uint8_t(attrs);
shape->immutableFlags |= Shape::IN_DICTIONARY | Shape::ACCESSOR_SHAPE;
AccessorShape& accShape = shape->asAccessorShape();
accShape.rawGetter = getter;
accShape.rawSetter = setter;
GetterSetterPostWriteBarrier(&accShape);
} else {
// Updating the last property in a non-dictionary-mode object. Find an
// alternate shared child of the last property's previous shape.
MOZ_ASSERT(shape == obj->lastProperty());
// Find or create a property tree node labeled by our arguments.
Rooted<StackShape> child(cx, StackShape(obj->lastProperty()->base(),
obj->lastProperty()->objectFlags(),
id, SHAPE_INVALID_SLOT, attrs));
child.updateGetterSetter(getter, setter);
RootedShape parent(cx, shape->parent);
shape = getChildAccessorProperty(cx, obj, parent, &child);
if (!shape) {
return nullptr;
}
}
// Can't fail now, so free the previous incarnation's slot. But we do not
// need to free oldSlot (and must not, as trying to will botch an assertion
// in NativeObject::freeSlot) if the new last property (shape here) has a
// slotSpan that does not cover it.
if (hadSlot && oldSlot < obj->slotSpan()) {
obj->freeSlot(cx, oldSlot);
}
MOZ_ASSERT(!shape->isDataProperty());
return shape;
}
/* static */
Shape* NativeObject::changeProperty(JSContext* cx, HandleNativeObject obj,
HandleShape shape, unsigned attrs,
GetterOp getter, SetterOp setter) {
MOZ_ASSERT(obj->containsPure(shape));
AutoCheckShapeConsistency check(obj);
/* Allow only shared (slotless) => unshared (slotful) transition. */
#ifdef DEBUG
bool needSlot = Shape::isDataProperty(attrs, getter, setter);
MOZ_ASSERT_IF(shape->isDataProperty() != needSlot, needSlot);
#endif
AssertCanChangeAttrs(shape, attrs);
if (shape->attrs == attrs && shape->getter() == getter &&
shape->setter() == setter) {
return shape;
}
RootedId propid(cx, shape->propid());
return putAccessorProperty(cx, obj, propid, getter, setter, attrs);
}
/* static */
bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
jsid id_) {
RootedId id(cx, id_);
AutoKeepShapeCaches keep(cx);
ShapeTable* table;
ShapeTable::Entry* entry;
RootedShape shape(cx);
if (!Shape::search(cx, obj->lastProperty(), id, keep, shape.address(), &table,
&entry)) {
return false;
}
if (!shape) {
return true;
}
const bool removingLastProperty = (shape == obj->lastProperty());
/*
* If shape is not the last property added, or the last property cannot
* be removed, switch to dictionary mode.
*/
if (!obj->inDictionaryMode() &&
(!removingLastProperty || !obj->canRemoveLastProperty())) {
if (!toDictionaryMode(cx, obj)) {
return false;
}
table = obj->lastProperty()->maybeTable(keep);
MOZ_ASSERT(table);
entry = &table->search<MaybeAdding::NotAdding>(shape->propid(), keep);
shape = entry->shape();
}
/*
* If in dictionary mode, get a new shape for the last property after the
* removal. We need a fresh shape for all dictionary deletions, even of
* the last property. Otherwise, a shape could replay and caches might
* return deleted DictionaryShapes! See bug 595365. Do this before changing
* the object or table, so the remaining removal is infallible.
*/
RootedShape spare(cx);
if (obj->inDictionaryMode()) {
/* For simplicity, always allocate an accessor shape for now. */
spare = Allocate<AccessorShape>(cx);
if (!spare) {
return false;
}
new (spare) Shape(shape->base(), ObjectFlags(), 0);
}
/* If shape has a slot, free its slot number. */
if (shape->isDataProperty()) {
obj->freeSlot(cx, shape->slot());
}
/*
* A dictionary-mode object owns mutable, unique shapes on a non-circular
* doubly linked list, hashed by lastProperty()->table. So we can edit the
* list and hash in place.
*/
if (obj->inDictionaryMode()) {
MOZ_ASSERT(obj->lastProperty()->maybeTable(keep) == table);
if (entry->hadCollision()) {
entry->setRemoved();
table->decEntryCount();
table->incRemovedCount();
} else {
entry->setFree();
table->decEntryCount();
#ifdef DEBUG
/*
* Check the consistency of the table but limit the number of
* checks not to alter significantly the complexity of the
* delete in debug builds, see bug 534493.
*/
Shape* aprop = obj->lastProperty();
for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent) {
MOZ_ASSERT_IF(aprop != shape, obj->contains(cx, aprop));
}
#endif
}
{
// Remove shape from its non-circular doubly linked list.
MOZ_ASSERT(removingLastProperty == (shape == obj->lastProperty()));
shape->removeFromDictionary(obj);
// If we just removed the object's last property, move its ShapeTable,
// BaseShape and object flags to the new last property. Information in
// (Base)Shapes for non-last properties may be out of sync with the
// object's state. Updating the shape's BaseShape is sound because we
// generate a new shape for the object right after this.
if (removingLastProperty) {
MOZ_ASSERT(obj->lastProperty() != shape);
shape->handoffTableTo(obj->lastProperty());
obj->lastProperty()->setBase(shape->base());
obj->lastProperty()->setObjectFlags(shape->objectFlags());
}
}
/* Generate a new shape for the object, infallibly. */
MOZ_ALWAYS_TRUE(NativeObject::generateOwnShape(cx, obj, spare));
/* Consider shrinking table if its load factor is <= .25. */
uint32_t size = table->capacity();
if (size > ShapeTable::MIN_SIZE && table->entryCount() <= size >> 2) {
(void)table->change(cx, -1);
}
} else {
/*
* Non-dictionary-mode shape tables are shared immutables, so all we
* need do is retract the last property and we'll either get or else
* lazily make via a later hashify the exact table for the new property
* lineage.
*/
MOZ_ASSERT(shape == obj->lastProperty());
obj->removeLastProperty(cx);
}
obj->checkShapeConsistency();
return true;
}
/* static */
Shape* NativeObject::replaceWithNewEquivalentShape(JSContext* cx,
HandleNativeObject obj,
Shape* oldShape,
Shape* newShape,
bool accessorShape) {
MOZ_ASSERT(cx->isInsideCurrentZone(oldShape));
MOZ_ASSERT_IF(oldShape != obj->lastProperty(),
obj->inDictionaryMode() &&
obj->lookup(cx, oldShape->propidRef()) == oldShape);
if (!obj->inDictionaryMode()) {
RootedShape newRoot(cx, newShape);
if (!toDictionaryMode(cx, obj)) {
return nullptr;
}
oldShape = obj->lastProperty();
newShape = newRoot;
}
if (!newShape) {
RootedShape oldRoot(cx, oldShape);
newShape = (oldShape->isAccessorShape() || accessorShape)
? Allocate<AccessorShape>(cx)
: Allocate<Shape>(cx);
if (!newShape) {
return nullptr;
}
new (newShape) Shape(oldRoot->base(), ObjectFlags(), 0);
oldShape = oldRoot;
}
AutoCheckCannotGC nogc;
ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, nogc);
if (!table) {
return nullptr;
}
ShapeTable::Entry* entry =
oldShape->isEmptyShape()
? nullptr
: &table->search<MaybeAdding::NotAdding>(oldShape->propidRef(), nogc);
/*
* Splice the new shape into the same position as the old shape, preserving
* enumeration order (see bug 601399).
*/
StackShape nshape(oldShape);
newShape->initDictionaryShape(nshape, obj->numFixedSlots(),
oldShape->dictNext);
MOZ_ASSERT(newShape->parent == oldShape);
oldShape->removeFromDictionary(obj);
if (newShape == obj->lastProperty()) {
oldShape->handoffTableTo(newShape);
}
if (entry) {
entry->setPreservingCollision(newShape);
}
return newShape;
}
/* static */
bool JSObject::setFlag(JSContext* cx, HandleObject obj, ObjectFlag flag,
GenerateShape generateShape) {
MOZ_ASSERT(cx->compartment() == obj->compartment());
if (obj->hasFlag(flag)) {
return true;
}
if (obj->is<NativeObject>() && obj->as<NativeObject>().inDictionaryMode()) {
if (generateShape == GENERATE_SHAPE) {
if (!NativeObject::generateOwnShape(cx, obj.as<NativeObject>())) {
return false;
}
}
Shape* last = obj->as<NativeObject>().lastProperty();
ObjectFlags flags = last->objectFlags();
flags.setFlag(flag);
last->setObjectFlags(flags);
return true;
}
Shape* newShape = Shape::setObjectFlag(cx, flag, obj->shape());
if (!newShape) {
return false;
}
obj->setShape(newShape);
return true;
}
/* static */
bool JSObject::setProtoUnchecked(JSContext* cx, HandleObject obj,
Handle<TaggedProto> proto) {
MOZ_ASSERT(cx->compartment() == obj->compartment());
MOZ_ASSERT_IF(proto.isObject(), proto.toObject()->isDelegate());
if (obj->shape()->proto() == proto) {
return true;
}
if (obj->is<NativeObject>() && obj->as<NativeObject>().inDictionaryMode()) {
Rooted<StackBaseShape> base(
cx, StackBaseShape(obj->getClass(), obj->nonCCWRealm(), proto));
Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, base));
if (!nbase) {
return false;
}
if (!NativeObject::generateOwnShape(cx, obj.as<NativeObject>())) {
return false;
}
Shape* last = obj->as<NativeObject>().lastProperty();
last->setBase(nbase);
return true;
}
Shape* newShape = Shape::setProto(cx, proto, obj->shape());
if (!newShape) {
return false;
}
obj->setShape(newShape);
return true;
}
/* static */
bool NativeObject::clearFlag(JSContext* cx, HandleNativeObject obj,
ObjectFlag flag) {
MOZ_ASSERT(obj->lastProperty()->hasObjectFlag(flag));
if (!obj->inDictionaryMode()) {
if (!toDictionaryMode(cx, obj)) {
return false;
}
}
Shape* last = obj->lastProperty();
ObjectFlags flags = last->objectFlags();
flags.clearFlag(flag);
last->setObjectFlags(flags);
return true;
}
/* static */
Shape* Shape::setObjectFlag(JSContext* cx, ObjectFlag flag, Shape* last) {
MOZ_ASSERT(!last->inDictionary());
MOZ_ASSERT(!last->hasObjectFlag(flag));
ObjectFlags objectFlags = last->objectFlags();
objectFlags.setFlag(flag);
RootedShape lastRoot(cx, last);
return replaceLastProperty(cx, objectFlags, last->proto(), lastRoot);
}
/* static */
Shape* Shape::setProto(JSContext* cx, TaggedProto proto, Shape* last) {
MOZ_ASSERT(!last->inDictionary());
MOZ_ASSERT(last->proto() != proto);
RootedShape lastRoot(cx, last);
return replaceLastProperty(cx, last->objectFlags(), proto, lastRoot);
}
inline BaseShape::BaseShape(const StackBaseShape& base)
: TenuredCellWithNonGCPointer(base.clasp),
realm_(base.realm),
proto_(base.proto) {
MOZ_ASSERT(JS::StringIsASCII(clasp()->name));
MOZ_ASSERT_IF(proto().isObject(),
compartment() == proto().toObject()->compartment());
MOZ_ASSERT_IF(proto().isObject(), proto().toObject()->isDelegate());
// Windows may not appear on prototype chains.
MOZ_ASSERT_IF(proto().isObject(), !IsWindow(proto().toObject()));
#ifdef DEBUG
if (GlobalObject* global = realm()->unsafeUnbarrieredMaybeGlobal()) {
AssertTargetIsNotGray(global);
}
#endif
}
/* static */
BaseShape* BaseShape::get(JSContext* cx, Handle<StackBaseShape> base) {
auto& table = cx->zone()->baseShapes();
auto p = MakeDependentAddPtr(cx, table, base.get());
if (p) {
return *p;
}
BaseShape* nbase = Allocate<BaseShape>(cx);
if (!nbase) {
return nullptr;
}
new (nbase) BaseShape(base);
if (!p.add(cx, table, nbase, nbase)) {
return nullptr;
}
return nbase;
}
#ifdef DEBUG
bool Shape::canSkipMarkingShapeCache() {
// Check that every shape in the shape table will be marked by marking
// |lastShape|.
AutoCheckCannotGC nogc;
ShapeCachePtr cache = getCache(nogc);
if (!cache.isTable()) {
return true;
}
uint32_t count = 0;
for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront()) {
Shape* shape = &r.front();
ShapeTable::Entry& entry =
cache.getTablePointer()->search<MaybeAdding::NotAdding>(shape->propid(),
nogc);
if (entry.isLive()) {
count++;
}
}
return count == cache.getTablePointer()->entryCount();
}
#endif
#ifdef JSGC_HASH_TABLE_CHECKS
void Zone::checkBaseShapeTableAfterMovingGC() {
for (auto r = baseShapes().all(); !r.empty(); r.popFront()) {
BaseShape* base = r.front().unbarrieredGet();
CheckGCThingAfterMovingGC(base);
BaseShapeSet::Ptr ptr = baseShapes().lookup(base);
MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
}
}
#endif // JSGC_HASH_TABLE_CHECKS
inline InitialShapeEntry::InitialShapeEntry() : shape(nullptr) {}
inline InitialShapeEntry::InitialShapeEntry(Shape* shape) : shape(shape) {}
#ifdef JSGC_HASH_TABLE_CHECKS
void Zone::checkInitialShapesTableAfterMovingGC() {
/*
* Assert that the postbarriers have worked and that nothing is left in
* initialShapes that points into the nursery, and that the hash table
* entries are discoverable.
*/
for (auto r = initialShapes().all(); !r.empty(); r.popFront()) {
InitialShapeEntry entry = r.front();
Shape* shape = entry.shape.unbarrieredGet();
CheckGCThingAfterMovingGC(shape);
using Lookup = InitialShapeEntry::Lookup;
Lookup lookup(shape->getObjectClass(), shape->realm(), shape->proto(),
shape->numFixedSlots(), shape->objectFlags());
InitialShapeSet::Ptr ptr = initialShapes().lookup(lookup);
MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
}
}
#endif // JSGC_HASH_TABLE_CHECKS
Shape* EmptyShape::new_(JSContext* cx, Handle<BaseShape*> base,
ObjectFlags objectFlags, uint32_t nfixed) {
Shape* shape = Allocate<Shape>(cx);
if (!shape) {
ReportOutOfMemory(cx);
return nullptr;
}
new (shape) EmptyShape(base, objectFlags, nfixed);
return shape;
}
MOZ_ALWAYS_INLINE HashNumber ShapeHasher::hash(const Lookup& l) {
return l.hash();
}
MOZ_ALWAYS_INLINE bool ShapeHasher::match(const Key k, const Lookup& l) {
return k->matches(l);
}
static ShapeSet* MakeShapeSet(Shape* child1, Shape* child2) {
auto hash = MakeUnique<ShapeSet>();
if (!hash || !hash->reserve(2)) {
return nullptr;
}
hash->putNewInfallible(StackShape(child1), child1);
hash->putNewInfallible(StackShape(child2), child2);
return hash.release();
}
bool PropertyTree::insertChild(JSContext* cx, Shape* parent, Shape* child) {
MOZ_ASSERT(!parent->inDictionary());
MOZ_ASSERT(!child->parent);