diff options
Diffstat (limited to 'js/src/vm/Shape.cpp')
-rw-r--r-- | js/src/vm/Shape.cpp | 1782 |
1 files changed, 1782 insertions, 0 deletions
diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp new file mode 100644 index 000000000..a64dc529a --- /dev/null +++ b/js/src/vm/Shape.cpp @@ -0,0 +1,1782 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/DebugOnly.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/PodOperations.h" + +#include "jsatom.h" +#include "jscntxt.h" +#include "jshashutil.h" +#include "jsobj.h" + +#include "gc/Policy.h" +#include "js/HashTable.h" + +#include "jscntxtinlines.h" +#include "jscompartmentinlines.h" +#include "jsobjinlines.h" + +#include "vm/Caches-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; +using namespace js::gc; + +using mozilla::CeilingLog2Size; +using mozilla::DebugOnly; +using mozilla::PodZero; +using mozilla::RotateLeft; + +using JS::AutoCheckCannotGC; + +Shape* const ShapeTable::Entry::SHAPE_REMOVED = (Shape*)ShapeTable::Entry::SHAPE_COLLISION; + +bool +ShapeTable::init(ExclusiveContext* cx, Shape* lastProp) +{ + uint32_t sizeLog2 = CeilingLog2Size(entryCount_); + uint32_t size = JS_BIT(sizeLog2); + if (entryCount_ >= size - (size >> 2)) + sizeLog2++; + if (sizeLog2 < MIN_SIZE_LOG2) + sizeLog2 = MIN_SIZE_LOG2; + + size = JS_BIT(sizeLog2); + entries_ = 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(listp); + + MOZ_ASSERT(obj->shape_->inDictionary()); + MOZ_ASSERT(obj->shape_->listp == &obj->shape_); + + if (parent) + parent->listp = listp; + *listp = parent; + listp = nullptr; + + obj->shape_->clearCachedBigEnoughForShapeTable(); +} + +void +Shape::insertIntoDictionary(GCPtrShape* dictp) +{ + // Don't assert inDictionaryMode() here because we may be called from + // JSObject::toDictionaryMode via JSObject::newDictionaryShape. + MOZ_ASSERT(inDictionary()); + MOZ_ASSERT(!listp); + + MOZ_ASSERT_IF(*dictp, (*dictp)->inDictionary()); + MOZ_ASSERT_IF(*dictp, (*dictp)->listp == dictp); + MOZ_ASSERT_IF(*dictp, zone() == (*dictp)->zone()); + + setParent(dictp->get()); + if (parent) + parent->listp = &parent; + listp = (GCPtrShape*) dictp; + *dictp = this; +} + +bool +Shape::makeOwnBaseShape(ExclusiveContext* cx) +{ + MOZ_ASSERT(!base()->isOwned()); + MOZ_ASSERT(cx->zone() == zone()); + + BaseShape* nbase = Allocate<BaseShape, NoGC>(cx); + if (!nbase) + return false; + + new (nbase) BaseShape(StackBaseShape(this)); + nbase->setOwned(base()->toUnowned()); + + this->base_ = nbase; + + return true; +} + +void +Shape::handoffTableTo(Shape* shape) +{ + MOZ_ASSERT(inDictionary() && shape->inDictionary()); + + if (this == shape) + return; + + MOZ_ASSERT(base()->isOwned() && !shape->base()->isOwned()); + + BaseShape* nbase = base(); + + MOZ_ASSERT_IF(shape->hasSlot(), nbase->slotSpan() > shape->slot()); + + this->base_ = nbase->baseUnowned(); + nbase->adoptUnowned(shape->base()->toUnowned()); + + shape->base_ = nbase; +} + +/* static */ bool +Shape::hashify(ExclusiveContext* cx, Shape* shape) +{ + MOZ_ASSERT(!shape->hasTable()); + + if (!shape->ensureOwnBaseShape(cx)) + return false; + + ShapeTable* table = cx->new_<ShapeTable>(shape->entryCount()); + if (!table) + return false; + + if (!table->init(cx, shape)) { + js_free(table); + return false; + } + + shape->base()->setTable(table); + return true; +} + +/* + * Double hashing needs the second hash code to be relatively prime to table + * size, so we simply make hash2 odd. + */ +static HashNumber +Hash1(HashNumber hash0, uint32_t shift) +{ + return hash0 >> shift; +} + +static HashNumber +Hash2(HashNumber hash0, uint32_t log2, uint32_t shift) +{ + return ((hash0 << log2) >> shift) | 1; +} + +template<MaybeAdding Adding> +ShapeTable::Entry& +ShapeTable::searchUnchecked(jsid id) +{ + MOZ_ASSERT(entries_); + MOZ_ASSERT(!JSID_IS_EMPTY(id)); + + /* Compute the primary hash address. */ + HashNumber hash0 = HashId(id); + HashNumber hash1 = Hash1(hash0, hashShift_); + Entry* entry = &getEntry(hash1); + + /* Miss: return space for a new entry. */ + if (entry->isFree()) + return *entry; + + /* Hit: return entry. */ + Shape* shape = entry->shape(); + if (shape && shape->propidRaw() == id) + return *entry; + + /* Collision: double hash. */ + uint32_t sizeLog2 = HASH_BITS - hashShift_; + HashNumber hash2 = Hash2(hash0, sizeLog2, hashShift_); + uint32_t sizeMask = JS_BITMASK(sizeLog2); + + /* Save the first removed entry pointer so we can recycle it if adding. */ + Entry* firstRemoved; + if (Adding == MaybeAdding::Adding) { + if (entry->isRemoved()) { + firstRemoved = entry; + } else { + firstRemoved = nullptr; + if (!entry->hadCollision()) + entry->flagCollision(); + } + } + +#ifdef DEBUG + bool collisionFlag = true; + if (!entry->isRemoved()) + collisionFlag = entry->hadCollision(); +#endif + + while (true) { + hash1 -= hash2; + hash1 &= sizeMask; + entry = &getEntry(hash1); + + if (entry->isFree()) + return (Adding == MaybeAdding::Adding && firstRemoved) ? *firstRemoved : *entry; + + shape = entry->shape(); + if (shape && shape->propidRaw() == id) { + MOZ_ASSERT(collisionFlag); + return *entry; + } + + if (Adding == MaybeAdding::Adding) { + if (entry->isRemoved()) { + if (!firstRemoved) + firstRemoved = entry; + } else { + if (!entry->hadCollision()) + entry->flagCollision(); + } + } + +#ifdef DEBUG + if (!entry->isRemoved()) + collisionFlag &= entry->hadCollision(); +#endif + } + + MOZ_CRASH("Shape::search failed to find an expected entry."); +} + +template ShapeTable::Entry& ShapeTable::searchUnchecked<MaybeAdding::Adding>(jsid id); +template ShapeTable::Entry& ShapeTable::searchUnchecked<MaybeAdding::NotAdding>(jsid id); + +bool +ShapeTable::change(ExclusiveContext* 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 = JS_BIT(oldLog2); + uint32_t newSize = JS_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_; + entries_ = 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(ExclusiveContext* 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 +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); + } + } +} + +#ifdef JSGC_HASH_TABLE_CHECKS + +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(ExclusiveContext* cx, StackBaseShape& base, + TaggedProto proto, HandleShape shape) +{ + MOZ_ASSERT(!shape->inDictionary()); + + if (!shape->parent) { + /* Treat as resetting the initial property of the shape hierarchy. */ + AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); + return EmptyShape::getInitialShape(cx, base.clasp, proto, kind, + base.flags & BaseShape::OBJECT_FLAG_MASK); + } + + UnownedBaseShape* nbase = BaseShape::getUnowned(cx, base); + if (!nbase) + return nullptr; + + Rooted<StackShape> child(cx, StackShape(shape)); + 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 */ Shape* +NativeObject::getChildPropertyOnDictionary(ExclusiveContext* cx, HandleNativeObject obj, + HandleShape parent, MutableHandle<StackShape> child) +{ + /* + * Shared properties have no slot, but slot_ will reflect that of parent. + * Unshared properties allocate a slot here but may lose it due to a + * JS_ClearScope call. + */ + if (!child.hasSlot()) { + child.setSlot(parent->maybeSlot()); + } else { + if (child.hasMissingSlot()) { + uint32_t slot; + if (!allocSlot(cx, obj, &slot)) + return nullptr; + 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()))); + } + } + + RootedShape shape(cx); + + if (obj->inDictionaryMode()) { + MOZ_ASSERT(parent == obj->lastProperty()); + shape = child.isAccessorShape() ? Allocate<AccessorShape>(cx) : Allocate<Shape>(cx); + if (!shape) + return nullptr; + if (child.hasSlot() && child.slot() >= obj->lastProperty()->base()->slotSpan()) { + if (!obj->setSlotSpan(cx, child.slot() + 1)) { + new (shape) Shape(obj->lastProperty()->base()->unowned(), 0); + return nullptr; + } + } + shape->initDictionaryShape(child, obj->numFixedSlots(), &obj->shape_); + } + + return shape; +} + +/* static */ Shape* +NativeObject::getChildProperty(ExclusiveContext* cx, + HandleNativeObject obj, HandleShape parent, + MutableHandle<StackShape> child) +{ + Shape* shape = getChildPropertyOnDictionary(cx, obj, parent, child); + + if (!obj->inDictionaryMode()) { + shape = cx->zone()->propertyTree.getChild(cx, parent, child); + if (!shape) + return nullptr; + //MOZ_ASSERT(shape->parent == parent); + //MOZ_ASSERT_IF(parent != lastProperty(), parent == lastProperty()->parent); + if (!obj->setLastProperty(cx, shape)) + return nullptr; + } + + return shape; +} + +bool +js::NativeObject::toDictionaryMode(ExclusiveContext* cx) +{ + MOZ_ASSERT(!inDictionaryMode()); + MOZ_ASSERT(cx->isInsideCurrentCompartment(this)); + + uint32_t span = slotSpan(); + + Rooted<NativeObject*> self(cx, this); + + // 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, lastProperty()); + while (shape) { + MOZ_ASSERT(!shape->inDictionary()); + + Shape* dprop = shape->isAccessorShape() ? Allocate<AccessorShape>(cx) : Allocate<Shape>(cx); + if (!dprop) { + ReportOutOfMemory(cx); + return false; + } + + GCPtrShape* listp = dictionaryShape ? &dictionaryShape->parent : nullptr; + StackShape child(shape); + dprop->initDictionaryShape(child, self->numFixedSlots(), listp); + + if (!dictionaryShape) + root = dprop; + + MOZ_ASSERT(!dprop->hasTable()); + dictionaryShape = dprop; + shape = shape->previous(); + } + + if (!Shape::hashify(cx, root)) { + ReportOutOfMemory(cx); + return false; + } + + if (IsInsideNursery(self) && + !cx->asJSContext()->gc.nursery.queueDictionaryModeObjectToSweep(self)) + { + ReportOutOfMemory(cx); + return false; + } + + MOZ_ASSERT(root->listp == nullptr); + root->listp = &self->shape_; + self->shape_ = root; + + MOZ_ASSERT(self->inDictionaryMode()); + root->base()->setSlotSpan(span); + + return true; +} + +/* static */ Shape* +NativeObject::addProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, + GetterOp getter, SetterOp setter, uint32_t slot, unsigned attrs, + unsigned flags, bool allowDictionary) +{ + MOZ_ASSERT(!JSID_IS_VOID(id)); + MOZ_ASSERT(getter != JS_PropertyStub); + MOZ_ASSERT(setter != JS_StrictPropertyStub); + + bool extensible; + if (!IsExtensible(cx, obj, &extensible)) + return nullptr; + if (!extensible) { + if (cx->isJSContext()) + obj->reportNotExtensible(cx->asJSContext()); + return nullptr; + } + + AutoKeepShapeTables keep(cx); + ShapeTable::Entry* entry = nullptr; + if (obj->inDictionaryMode()) { + ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, keep); + if (!table) + return nullptr; + entry = &table->search<MaybeAdding::Adding>(id, keep); + } + + return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, flags, entry, + allowDictionary, keep); +} + +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 */ Shape* +NativeObject::addPropertyInternal(ExclusiveContext* cx, + HandleNativeObject obj, HandleId id, + GetterOp getter, SetterOp setter, + uint32_t slot, unsigned attrs, + unsigned flags, ShapeTable::Entry* entry, + bool allowDictionary, const AutoKeepShapeTables& keep) +{ + MOZ_ASSERT_IF(!allowDictionary, !obj->inDictionaryMode()); + MOZ_ASSERT(getter != JS_PropertyStub); + MOZ_ASSERT(setter != JS_StrictPropertyStub); + + AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); + + /* + * The code below deals with either converting obj to dictionary mode or + * growing an object that's already in dictionary mode. Either way, + * dictionray operations are safe if thread local. + */ + ShapeTable* table = nullptr; + if (!obj->inDictionaryMode()) { + bool stableSlot = + (slot == SHAPE_INVALID_SLOT) || + obj->lastProperty()->hasMissingSlot() || + (slot == obj->lastProperty()->maybeSlot() + 1); + MOZ_ASSERT_IF(!allowDictionary, stableSlot); + if (allowDictionary && + (!stableSlot || ShouldConvertToDictionary(obj))) + { + if (!obj->toDictionaryMode(cx)) + 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. */ + RootedShape shape(cx); + { + RootedShape last(cx, obj->lastProperty()); + + uint32_t index; + bool indexed = IdIsIndex(id, &index); + + Rooted<UnownedBaseShape*> nbase(cx); + if (!indexed) { + nbase = last->base()->unowned(); + } else { + StackBaseShape base(last->base()); + base.flags |= BaseShape::INDEXED; + nbase = BaseShape::getUnowned(cx, base); + if (!nbase) + return nullptr; + } + + Rooted<StackShape> child(cx, StackShape(nbase, id, slot, attrs, flags)); + child.updateGetterSetter(getter, setter); + shape = getChildProperty(cx, obj, last, &child); + } + + if (shape) { + MOZ_ASSERT(shape == obj->lastProperty()); + + if (table) { + /* Store the tree node pointer in the table entry for id. */ + entry->setPreservingCollision(shape); + table->incEntryCount(); + + /* Pass the table along to the new last property, namely shape. */ + MOZ_ASSERT(shape->parent->maybeTable(keep) == table); + shape->parent->handoffTableTo(shape); + } + + obj->checkShapeConsistency(); + return shape; + } + + obj->checkShapeConsistency(); + return nullptr; +} + +Shape* +js::ReshapeForAllocKind(JSContext* cx, Shape* shape, TaggedProto proto, + gc::AllocKind allocKind) +{ + // Compute the number of fixed slots with the new allocation kind. + size_t nfixed = gc::GetGCKindSlots(allocKind, shape->getObjectClass()); + + // Get all the ids in the shape, in order. + js::AutoIdVector ids(cx); + { + for (unsigned i = 0; i < shape->slotSpan(); i++) { + if (!ids.append(JSID_VOID)) + return nullptr; + } + Shape* nshape = shape; + while (!nshape->isEmptyShape()) { + ids[nshape->slot()].set(nshape->propid()); + nshape = nshape->previous(); + } + } + + // Construct the new shape, without updating type information. + RootedId id(cx); + RootedShape newShape(cx, EmptyShape::getInitialShape(cx, shape->getObjectClass(), + proto, nfixed, shape->getObjectFlags())); + if (!newShape) + return nullptr; + + for (unsigned i = 0; i < ids.length(); i++) { + id = ids[i]; + + uint32_t index; + bool indexed = IdIsIndex(id, &index); + + Rooted<UnownedBaseShape*> nbase(cx, newShape->base()->unowned()); + if (indexed) { + StackBaseShape base(nbase); + base.flags |= BaseShape::INDEXED; + nbase = BaseShape::getUnowned(cx, base); + if (!nbase) + return nullptr; + } + + Rooted<StackShape> child(cx, StackShape(nbase, id, i, JSPROP_ENUMERATE, 0)); + newShape = cx->zone()->propertyTree.getChild(cx, newShape, child); + if (!newShape) + return nullptr; + } + + return newShape; +} + +/* + * Check and adjust the new attributes for the shape to make sure that our + * slot access optimizations are sound. It is responsibility of the callers to + * enforce all restrictions from ECMA-262 v5 8.12.9 [[DefineOwnProperty]]. + */ +static inline bool +CheckCanChangeAttrs(ExclusiveContext* cx, JSObject* obj, Shape* shape, unsigned* attrsp) +{ + if (shape->configurable()) + return true; + + /* A permanent property must stay permanent. */ + *attrsp |= JSPROP_PERMANENT; + + /* Reject attempts to remove a slot from the permanent data property. */ + if (shape->isDataDescriptor() && shape->hasSlot() && + (*attrsp & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED))) + { + if (cx->isJSContext()) + obj->reportNotConfigurable(cx->asJSContext(), shape->propid()); + return false; + } + + return true; +} + +/* static */ Shape* +NativeObject::putProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, + GetterOp getter, SetterOp setter, uint32_t slot, unsigned attrs, + unsigned flags) +{ + MOZ_ASSERT(!JSID_IS_VOID(id)); + MOZ_ASSERT(getter != JS_PropertyStub); + MOZ_ASSERT(setter != JS_StrictPropertyStub); + +#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 + + AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); + + /* + * Search for id in order to claim its entry if table has been allocated. + * + * Note that we can only try to claim an entry in a table that is thread + * local. An object may be thread local *without* its shape being thread + * local. The only thread local objects that *also* have thread local + * shapes are dictionaries that were allocated/converted thread + * locally. Only for those objects we can try to claim an entry in its + * shape table. + */ + AutoKeepShapeTables keep(cx); + ShapeTable::Entry* entry; + RootedShape shape(cx); + if (!Shape::search<MaybeAdding::Adding>(cx, obj->lastProperty(), id, keep, + shape.address(), &entry)) + { + return nullptr; + } + + if (!shape) { + /* + * You can't add properties to a non-extensible object, but you can change + * attributes of properties in such objects. + */ + bool extensible; + + if (!IsExtensible(cx, obj, &extensible)) + return nullptr; + + if (!extensible) { + if (cx->isJSContext()) + obj->reportNotExtensible(cx->asJSContext()); + return nullptr; + } + + return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, flags, + entry, true, keep); + } + + /* Property exists: search must have returned a valid entry. */ + MOZ_ASSERT_IF(entry, !entry->isRemoved()); + + if (!CheckCanChangeAttrs(cx, obj, shape, &attrs)) + return nullptr; + + /* + * 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->hasSlot(); + uint32_t oldSlot = shape->maybeSlot(); + if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot) + slot = oldSlot; + + Rooted<UnownedBaseShape*> nbase(cx); + { + uint32_t index; + bool indexed = IdIsIndex(id, &index); + StackBaseShape base(obj->lastProperty()->base()); + if (indexed) + base.flags |= BaseShape::INDEXED; + nbase = BaseShape::getUnowned(cx, base); + if (!nbase) + return nullptr; + } + + /* + * 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(nbase, slot, attrs, flags, getter, setter)) + return shape; + + /* + * Overwriting a non-last property requires switching to dictionary mode. + * The shape tree is shared immutable, and we can't removeProperty and then + * addPropertyInternal because a failure under add would lose data. + */ + if (shape != obj->lastProperty() && !obj->inDictionaryMode()) { + if (!obj->toDictionaryMode(cx)) + return nullptr; + ShapeTable* table = obj->lastProperty()->maybeTable(keep); + MOZ_ASSERT(table); + entry = &table->search<MaybeAdding::NotAdding>(shape->propid(), keep); + shape = entry->shape(); + } + + MOZ_ASSERT_IF(shape->hasSlot() && !(attrs & JSPROP_SHARED), 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()); + bool accessorShape = getter || setter || (attrs & (JSPROP_GETTER | JSPROP_SETTER)); + shape = obj->replaceWithNewEquivalentShape(cx, shape, nullptr, accessorShape); + if (!shape) + return nullptr; + if (!updateLast && !obj->generateOwnShape(cx)) + return nullptr; + + /* + * FIXME bug 593129 -- slot allocation and NativeObject *this must move + * out of here! + */ + if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED)) { + if (!allocSlot(cx, obj, &slot)) + return nullptr; + } + + if (updateLast) + shape->base()->adoptUnowned(nbase); + else + shape->base_ = nbase; + + MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); + + shape->setSlot(slot); + shape->attrs = uint8_t(attrs); + shape->flags = flags | Shape::IN_DICTIONARY | (accessorShape ? Shape::ACCESSOR_SHAPE : 0); + if (shape->isAccessorShape()) { + AccessorShape& accShape = shape->asAccessorShape(); + accShape.rawGetter = getter; + accShape.rawSetter = setter; + GetterSetterWriteBarrierPost(&accShape); + } else { + MOZ_ASSERT(!getter); + MOZ_ASSERT(!setter); + } + } else { + /* + * Updating the last property in a non-dictionary-mode object. Find an + * alternate shared child of the last property's previous shape. + */ + StackBaseShape base(obj->lastProperty()->base()); + + UnownedBaseShape* nbase = BaseShape::getUnowned(cx, base); + if (!nbase) + return nullptr; + + MOZ_ASSERT(shape == obj->lastProperty()); + + /* Find or create a property tree node labeled by our arguments. */ + Rooted<StackShape> child(cx, StackShape(nbase, id, slot, attrs, flags)); + child.updateGetterSetter(getter, setter); + RootedShape parent(cx, shape->parent); + Shape* newShape = getChildProperty(cx, obj, parent, &child); + + if (!newShape) { + obj->checkShapeConsistency(); + return nullptr; + } + + shape = newShape; + } + + /* + * Can't fail now, so free the previous incarnation's slot if the new shape + * has no slot. But we do not need to free oldSlot (and must not, as trying + * to will botch an assertion in JSObject::freeSlot) if the new last + * property (shape here) has a slotSpan that does not cover it. + */ + if (hadSlot && !shape->hasSlot()) { + if (oldSlot < obj->slotSpan()) + obj->freeSlot(cx, oldSlot); + /* Note: The optimization based on propertyRemovals is only relevant to the main thread. */ + if (cx->isJSContext()) + ++cx->asJSContext()->runtime()->propertyRemovals; + } + + obj->checkShapeConsistency(); + + return shape; +} + +/* static */ Shape* +NativeObject::changeProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape, + unsigned attrs, GetterOp getter, SetterOp setter) +{ + MOZ_ASSERT(obj->containsPure(shape)); + MOZ_ASSERT(getter != JS_PropertyStub); + MOZ_ASSERT(setter != JS_StrictPropertyStub); + MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); + + /* Allow only shared (slotless) => unshared (slotful) transition. */ + MOZ_ASSERT(!((attrs ^ shape->attrs) & JSPROP_SHARED) || + !(attrs & JSPROP_SHARED)); + + MarkTypePropertyNonData(cx, obj, shape->propid()); + + if (!CheckCanChangeAttrs(cx, obj, shape, &attrs)) + return nullptr; + + if (shape->attrs == attrs && shape->getter() == getter && shape->setter() == setter) + return shape; + + /* + * Let JSObject::putProperty handle this |overwriting| case, including + * the conservation of shape->slot (if it's valid). We must not call + * removeProperty because it will free an allocated shape->slot, and + * putProperty won't re-allocate it. + */ + RootedId propid(cx, shape->propid()); + Shape* newShape = putProperty(cx, obj, propid, getter, setter, + shape->maybeSlot(), attrs, shape->flags); + + obj->checkShapeConsistency(); + return newShape; +} + +bool +NativeObject::removeProperty(ExclusiveContext* cx, jsid id_) +{ + RootedId id(cx, id_); + RootedNativeObject self(cx, this); + + AutoKeepShapeTables keep(cx); + ShapeTable::Entry* entry; + RootedShape shape(cx); + if (!Shape::search(cx, lastProperty(), id, keep, shape.address(), &entry)) + return false; + + if (!shape) + return true; + + /* + * If shape is not the last property added, or the last property cannot + * be removed, switch to dictionary mode. + */ + if (!self->inDictionaryMode() && (shape != self->lastProperty() || !self->canRemoveLastProperty())) { + if (!self->toDictionaryMode(cx)) + return false; + ShapeTable* table = self->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 (self->inDictionaryMode()) { + /* For simplicity, always allocate an accessor shape for now. */ + spare = Allocate<AccessorShape>(cx); + if (!spare) + return false; + new (spare) Shape(shape->base()->unowned(), 0); + if (shape == self->lastProperty()) { + /* + * Get an up to date unowned base shape for the new last property + * when removing the dictionary's last property. Information in + * base shapes for non-last properties may be out of sync with the + * object's state. + */ + RootedShape previous(cx, self->lastProperty()->parent); + StackBaseShape base(self->lastProperty()->base()); + BaseShape* nbase = BaseShape::getUnowned(cx, base); + if (!nbase) + return false; + previous->base_ = nbase; + } + } + + /* If shape has a slot, free its slot number. */ + if (shape->hasSlot()) { + self->freeSlot(cx, shape->slot()); + if (cx->isJSContext()) + ++cx->asJSContext()->runtime()->propertyRemovals; + } + + /* + * 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 (self->inDictionaryMode()) { + ShapeTable* table = self->lastProperty()->maybeTable(keep); + MOZ_ASSERT(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 = self->lastProperty(); + for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent) + MOZ_ASSERT_IF(aprop != shape, self->contains(cx, aprop)); +#endif + } + + { + /* Remove shape from its non-circular doubly linked list. */ + Shape* oldLastProp = self->lastProperty(); + shape->removeFromDictionary(self); + + /* Hand off table from the old to new last property. */ + oldLastProp->handoffTableTo(self->lastProperty()); + } + + /* Generate a new shape for the object, infallibly. */ + JS_ALWAYS_TRUE(self->generateOwnShape(cx, 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 == self->lastProperty()); + self->removeLastProperty(cx); + } + + self->checkShapeConsistency(); + return true; +} + +/* static */ void +NativeObject::clear(ExclusiveContext* cx, HandleNativeObject obj) +{ + Shape* shape = obj->lastProperty(); + MOZ_ASSERT(obj->inDictionaryMode() == shape->inDictionary()); + + while (shape->parent) { + shape = shape->parent; + MOZ_ASSERT(obj->inDictionaryMode() == shape->inDictionary()); + } + MOZ_ASSERT(shape->isEmptyShape()); + + if (obj->inDictionaryMode()) + shape->listp = &obj->shape_; + + JS_ALWAYS_TRUE(obj->setLastProperty(cx, shape)); + + if (cx->isJSContext()) + ++cx->asJSContext()->runtime()->propertyRemovals; + obj->checkShapeConsistency(); +} + +/* static */ bool +NativeObject::rollbackProperties(ExclusiveContext* cx, HandleNativeObject obj, uint32_t slotSpan) +{ + /* + * Remove properties from this object until it has a matching slot span. + * The object cannot have escaped in a way which would prevent safe + * removal of the last properties. + */ + MOZ_ASSERT(!obj->inDictionaryMode() && slotSpan <= obj->slotSpan()); + while (true) { + if (obj->lastProperty()->isEmptyShape()) { + MOZ_ASSERT(slotSpan == 0); + break; + } else { + uint32_t slot = obj->lastProperty()->slot(); + if (slot < slotSpan) + break; + } + if (!obj->removeProperty(cx, obj->lastProperty()->propid())) + return false; + } + + return true; +} + +Shape* +NativeObject::replaceWithNewEquivalentShape(ExclusiveContext* cx, Shape* oldShape, Shape* newShape, + bool accessorShape) +{ + MOZ_ASSERT(cx->isInsideCurrentZone(oldShape)); + MOZ_ASSERT_IF(oldShape != lastProperty(), + inDictionaryMode() && lookup(cx, oldShape->propidRef()) == oldShape); + + NativeObject* self = this; + + if (!inDictionaryMode()) { + RootedNativeObject selfRoot(cx, self); + RootedShape newRoot(cx, newShape); + if (!toDictionaryMode(cx)) + return nullptr; + oldShape = selfRoot->lastProperty(); + self = selfRoot; + newShape = newRoot; + } + + if (!newShape) { + RootedNativeObject selfRoot(cx, self); + RootedShape oldRoot(cx, oldShape); + newShape = (oldShape->isAccessorShape() || accessorShape) + ? Allocate<AccessorShape>(cx) + : Allocate<Shape>(cx); + if (!newShape) + return nullptr; + new (newShape) Shape(oldRoot->base()->unowned(), 0); + self = selfRoot; + oldShape = oldRoot; + } + + AutoCheckCannotGC nogc; + ShapeTable* table = self->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, self->numFixedSlots(), oldShape->listp); + + MOZ_ASSERT(newShape->parent == oldShape); + oldShape->removeFromDictionary(self); + + if (newShape == self->lastProperty()) + oldShape->handoffTableTo(newShape); + + if (entry) + entry->setPreservingCollision(newShape); + return newShape; +} + +bool +NativeObject::shadowingShapeChange(ExclusiveContext* cx, const Shape& shape) +{ + return generateOwnShape(cx); +} + +bool +JSObject::setFlags(ExclusiveContext* cx, BaseShape::Flag flags, GenerateShape generateShape) +{ + if (hasAllFlags(flags)) + return true; + + RootedObject self(cx, this); + + if (isNative() && as<NativeObject>().inDictionaryMode()) { + if (generateShape == GENERATE_SHAPE && !as<NativeObject>().generateOwnShape(cx)) + return false; + StackBaseShape base(self->as<NativeObject>().lastProperty()); + base.flags |= flags; + UnownedBaseShape* nbase = BaseShape::getUnowned(cx, base); + if (!nbase) + return false; + + self->as<NativeObject>().lastProperty()->base()->adoptUnowned(nbase); + return true; + } + + Shape* existingShape = self->ensureShape(cx); + if (!existingShape) + return false; + + Shape* newShape = Shape::setObjectFlags(cx, flags, self->taggedProto(), existingShape); + if (!newShape) + return false; + + // The success of the |JSObject::ensureShape| call above means that |self| + // can be assumed to have a shape. + self->as<ShapedObject>().setShape(newShape); + + return true; +} + +bool +NativeObject::clearFlag(ExclusiveContext* cx, BaseShape::Flag flag) +{ + MOZ_ASSERT(inDictionaryMode()); + + RootedNativeObject self(cx, &as<NativeObject>()); + MOZ_ASSERT(self->lastProperty()->getObjectFlags() & flag); + + StackBaseShape base(self->lastProperty()); + base.flags &= ~flag; + UnownedBaseShape* nbase = BaseShape::getUnowned(cx, base); + if (!nbase) + return false; + + self->lastProperty()->base()->adoptUnowned(nbase); + return true; +} + +/* static */ Shape* +Shape::setObjectFlags(ExclusiveContext* cx, BaseShape::Flag flags, TaggedProto proto, Shape* last) +{ + if ((last->getObjectFlags() & flags) == flags) + return last; + + StackBaseShape base(last); + base.flags |= flags; + + RootedShape lastRoot(cx, last); + return replaceLastProperty(cx, base, proto, lastRoot); +} + +/* static */ inline HashNumber +StackBaseShape::hash(const Lookup& lookup) +{ + HashNumber hash = lookup.flags; + hash = RotateLeft(hash, 4) ^ (uintptr_t(lookup.clasp) >> 3); + return hash; +} + +/* static */ inline bool +StackBaseShape::match(ReadBarriered<UnownedBaseShape*> key, const Lookup& lookup) +{ + return key.unbarrieredGet()->flags == lookup.flags && + key.unbarrieredGet()->clasp_ == lookup.clasp; +} + +inline +BaseShape::BaseShape(const StackBaseShape& base) + : clasp_(base.clasp), + flags(base.flags), + slotSpan_(0), + unowned_(nullptr), + table_(nullptr) +{ +} + +/* static */ void +BaseShape::copyFromUnowned(BaseShape& dest, UnownedBaseShape& src) +{ + dest.clasp_ = src.clasp_; + dest.slotSpan_ = src.slotSpan_; + dest.unowned_ = &src; + dest.flags = src.flags | OWNED_SHAPE; +} + +inline void +BaseShape::adoptUnowned(UnownedBaseShape* other) +{ + // This is a base shape owned by a dictionary object, update it to reflect the + // unowned base shape of a new last property. + MOZ_ASSERT(isOwned()); + + uint32_t span = slotSpan(); + + BaseShape::copyFromUnowned(*this, *other); + setSlotSpan(span); + + assertConsistency(); +} + +/* static */ UnownedBaseShape* +BaseShape::getUnowned(ExclusiveContext* cx, StackBaseShape& base) +{ + auto& table = cx->zone()->baseShapes; + + if (!table.initialized() && !table.init()) { + ReportOutOfMemory(cx); + return nullptr; + } + + auto p = MakeDependentAddPtr(cx, table, base); + if (p) + return *p; + + BaseShape* nbase_ = Allocate<BaseShape>(cx); + if (!nbase_) + return nullptr; + + new (nbase_) BaseShape(base); + + UnownedBaseShape* nbase = static_cast<UnownedBaseShape*>(nbase_); + + if (!p.add(cx, table, base, nbase)) + return nullptr; + + return nbase; +} + +void +BaseShape::assertConsistency() +{ +#ifdef DEBUG + if (isOwned()) { + UnownedBaseShape* unowned = baseUnowned(); + MOZ_ASSERT(getObjectFlags() == unowned->getObjectFlags()); + } +#endif +} + +void +BaseShape::traceChildren(JSTracer* trc) +{ + traceChildrenSkipShapeTable(trc); + traceShapeTable(trc); +} + +void +BaseShape::traceChildrenSkipShapeTable(JSTracer* trc) +{ + if (isOwned()) + TraceEdge(trc, &unowned_, "base"); + + assertConsistency(); +} + +void +BaseShape::traceShapeTable(JSTracer* trc) +{ + AutoCheckCannotGC nogc; + if (ShapeTable* table = maybeTable(nogc)) + table->trace(trc); +} + +#ifdef DEBUG +bool +BaseShape::canSkipMarkingShapeTable(Shape* lastShape) +{ + // Check that every shape in the shape table will be marked by marking + // |lastShape|. + + AutoCheckCannotGC nogc; + ShapeTable* table = maybeTable(nogc); + if (!table) + return true; + + uint32_t count = 0; + for (Shape::Range<NoGC> r(lastShape); !r.empty(); r.popFront()) { + Shape* shape = &r.front(); + ShapeTable::Entry& entry = table->search<MaybeAdding::NotAdding>(shape->propid(), nogc); + if (entry.isLive()) + count++; + } + + return count == table->entryCount(); +} +#endif + +#ifdef JSGC_HASH_TABLE_CHECKS + +void +Zone::checkBaseShapeTableAfterMovingGC() +{ + if (!baseShapes.initialized()) + return; + + for (decltype(baseShapes)::Enum e(baseShapes); !e.empty(); e.popFront()) { + UnownedBaseShape* base = e.front().unbarrieredGet(); + CheckGCThingAfterMovingGC(base); + + BaseShapeSet::Ptr ptr = baseShapes.lookup(base); + MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front()); + } +} + +#endif // JSGC_HASH_TABLE_CHECKS + +void +BaseShape::finalize(FreeOp* fop) +{ + if (table_) { + fop->delete_(table_); + table_ = nullptr; + } +} + +inline +InitialShapeEntry::InitialShapeEntry() : shape(nullptr), proto() +{ +} + +inline +InitialShapeEntry::InitialShapeEntry(Shape* shape, const Lookup::ShapeProto& proto) + : shape(shape), proto(proto) +{ +} + +/* static */ inline HashNumber +InitialShapeEntry::hash(const Lookup& lookup) +{ + return (RotateLeft(uintptr_t(lookup.clasp) >> 3, 4) ^ lookup.proto.hashCode()) + + lookup.nfixed; +} + +/* static */ inline bool +InitialShapeEntry::match(const InitialShapeEntry& key, const Lookup& lookup) +{ + const Shape* shape = key.shape.unbarrieredGet(); + return lookup.clasp == shape->getObjectClass() + && lookup.nfixed == shape->numFixedSlots() + && lookup.baseFlags == shape->getObjectFlags() + && lookup.proto.match(key.proto); +} + +#ifdef JSGC_HASH_TABLE_CHECKS + +void +Zone::checkInitialShapesTableAfterMovingGC() +{ + if (!initialShapes.initialized()) + return; + + /* + * 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 (decltype(initialShapes)::Enum e(initialShapes); !e.empty(); e.popFront()) { + InitialShapeEntry entry = e.front(); + JSProtoKey protoKey = entry.proto.key(); + TaggedProto proto = entry.proto.proto().unbarrieredGet(); + Shape* shape = entry.shape.unbarrieredGet(); + + CheckGCThingAfterMovingGC(shape); + if (proto.isObject()) + CheckGCThingAfterMovingGC(proto.toObject()); + + using Lookup = InitialShapeEntry::Lookup; + Lookup lookup(shape->getObjectClass(), + Lookup::ShapeProto(protoKey, proto), + shape->numFixedSlots(), + shape->getObjectFlags()); + InitialShapeSet::Ptr ptr = initialShapes.lookup(lookup); + MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front()); + } +} + +#endif // JSGC_HASH_TABLE_CHECKS + +Shape* +EmptyShape::new_(ExclusiveContext* cx, Handle<UnownedBaseShape*> base, uint32_t nfixed) +{ + Shape* shape = Allocate<Shape>(cx); + if (!shape) { + ReportOutOfMemory(cx); + return nullptr; + } + + new (shape) EmptyShape(base, nfixed); + return shape; +} + +static bool +IsOriginalProto(GlobalObject* global, JSProtoKey key, JSObject& proto) +{ + if (global->getPrototype(key) != ObjectValue(proto)) + return false; + + if (key == JSProto_Object) { + MOZ_ASSERT(proto.staticPrototypeIsImmutable(), + "proto should be Object.prototype, whose prototype is " + "immutable"); + MOZ_ASSERT(proto.staticPrototype() == nullptr, + "Object.prototype must have null prototype"); + return true; + } + + // Check that other prototypes still have Object.prototype as proto. + JSObject* protoProto = proto.staticPrototype(); + if (!protoProto || global->getPrototype(JSProto_Object) != ObjectValue(*protoProto)) + return false; + + MOZ_ASSERT(protoProto->staticPrototypeIsImmutable(), + "protoProto should be Object.prototype, whose prototype is " + "immutable"); + MOZ_ASSERT(protoProto->staticPrototype() == nullptr, + "Object.prototype must have null prototype"); + return true; +} + +static JSProtoKey +GetInitialShapeProtoKey(TaggedProto proto, ExclusiveContext* cx) +{ + if (proto.isObject() && proto.toObject()->hasStaticPrototype()) { + GlobalObject* global = cx->global(); + JSObject& obj = *proto.toObject(); + MOZ_ASSERT(global == &obj.global()); + + if (IsOriginalProto(global, JSProto_Object, obj)) + return JSProto_Object; + if (IsOriginalProto(global, JSProto_Function, obj)) + return JSProto_Function; + if (IsOriginalProto(global, JSProto_Array, obj)) + return JSProto_Array; + if (IsOriginalProto(global, JSProto_RegExp, obj)) + return JSProto_RegExp; + } + return JSProto_LIMIT; +} + +/* static */ Shape* +EmptyShape::getInitialShape(ExclusiveContext* cx, const Class* clasp, TaggedProto proto, + size_t nfixed, uint32_t objectFlags) +{ + MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); + + auto& table = cx->zone()->initialShapes; + + if (!table.initialized() && !table.init()) { + ReportOutOfMemory(cx); + return nullptr; + } + + using Lookup = InitialShapeEntry::Lookup; + auto protoPointer = MakeDependentAddPtr(cx, table, + Lookup(clasp, Lookup::ShapeProto(proto), + nfixed, objectFlags)); + if (protoPointer) + return protoPointer->shape; + + // No entry for this proto. If the proto is one of a few common builtin + // prototypes, try to do a lookup based on the JSProtoKey, so we can share + // shapes across globals. + Rooted<TaggedProto> protoRoot(cx, proto); + Shape* shape = nullptr; + bool insertKey = false; + mozilla::Maybe<DependentAddPtr<decltype(cx->zone()->initialShapes)>> keyPointer; + + JSProtoKey key = GetInitialShapeProtoKey(protoRoot, cx); + if (key != JSProto_LIMIT) { + keyPointer.emplace(MakeDependentAddPtr(cx, table, + Lookup(clasp, Lookup::ShapeProto(key), + nfixed, objectFlags))); + if (keyPointer.ref()) { + shape = keyPointer.ref()->shape; + MOZ_ASSERT(shape); + } else { + insertKey = true; + } + } + + if (!shape) { + StackBaseShape base(cx, clasp, objectFlags); + Rooted<UnownedBaseShape*> nbase(cx, BaseShape::getUnowned(cx, base)); + if (!nbase) + return nullptr; + + shape = EmptyShape::new_(cx, nbase, nfixed); + if (!shape) + return nullptr; + } + + Lookup::ShapeProto shapeProto(protoRoot); + Lookup lookup(clasp, shapeProto, nfixed, objectFlags); + if (!protoPointer.add(cx, table, lookup, InitialShapeEntry(shape, shapeProto))) + return nullptr; + + // Also add an entry based on the JSProtoKey, if needed. + if (insertKey) { + Lookup::ShapeProto shapeProto(key); + Lookup lookup(clasp, shapeProto, nfixed, objectFlags); + if (!keyPointer->add(cx, table, lookup, InitialShapeEntry(shape, shapeProto))) + return nullptr; + } + + return shape; +} + +/* static */ Shape* +EmptyShape::getInitialShape(ExclusiveContext* cx, const Class* clasp, TaggedProto proto, + AllocKind kind, uint32_t objectFlags) +{ + return getInitialShape(cx, clasp, proto, GetGCKindSlots(kind, clasp), objectFlags); +} + +void +NewObjectCache::invalidateEntriesForShape(JSContext* cx, HandleShape shape, HandleObject proto) +{ + const Class* clasp = shape->getObjectClass(); + + gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); + if (CanBeFinalizedInBackground(kind, clasp)) + kind = GetBackgroundAllocKind(kind); + + RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp, TaggedProto(proto))); + if (!group) { + purge(); + cx->recoverFromOutOfMemory(); + return; + } + + EntryIndex entry; + for (CompartmentsInZoneIter comp(shape->zone()); !comp.done(); comp.next()) { + if (GlobalObject* global = comp->unsafeUnbarrieredMaybeGlobal()) { + if (lookupGlobal(clasp, global, kind, &entry)) + PodZero(&entries[entry]); + } + } + if (!proto->is<GlobalObject>() && lookupProto(clasp, proto, kind, &entry)) + PodZero(&entries[entry]); + if (lookupGroup(group, kind, &entry)) + PodZero(&entries[entry]); +} + +/* static */ void +EmptyShape::insertInitialShape(ExclusiveContext* cx, HandleShape shape, HandleObject proto) +{ + using Lookup = InitialShapeEntry::Lookup; + Lookup lookup(shape->getObjectClass(), Lookup::ShapeProto(TaggedProto(proto)), + shape->numFixedSlots(), shape->getObjectFlags()); + + InitialShapeSet::Ptr p = cx->zone()->initialShapes.lookup(lookup); + MOZ_ASSERT(p); + + InitialShapeEntry& entry = const_cast<InitialShapeEntry&>(*p); + + // The metadata callback can end up causing redundant changes of the initial shape. + if (entry.shape == shape) + return; + + // The new shape had better be rooted at the old one. +#ifdef DEBUG + Shape* nshape = shape; + while (!nshape->isEmptyShape()) + nshape = nshape->previous(); + MOZ_ASSERT(nshape == entry.shape); +#endif + + entry.shape = ReadBarrieredShape(shape); + + // For certain prototypes -- namely, those of various builtin classes, + // keyed by JSProtoKey |key| -- there are two entries: one for a lookup + // via |proto|, and one for a lookup via |key|. If this is such a + // prototype, also update the alternate |key|-keyed shape. + JSProtoKey key = GetInitialShapeProtoKey(TaggedProto(proto), cx); + if (key != JSProto_LIMIT) { + Lookup lookup(shape->getObjectClass(), Lookup::ShapeProto(key), + shape->numFixedSlots(), shape->getObjectFlags()); + if (InitialShapeSet::Ptr p = cx->zone()->initialShapes.lookup(lookup)) { + InitialShapeEntry& entry = const_cast<InitialShapeEntry&>(*p); + if (entry.shape != shape) + entry.shape = ReadBarrieredShape(shape); + } + } + + /* + * This affects the shape that will be produced by the various NewObject + * methods, so clear any cache entry referring to the old shape. This is + * not required for correctness: the NewObject must always check for a + * nativeEmpty() result and generate the appropriate properties if found. + * Clearing the cache entry avoids this duplicate regeneration. + * + * Clearing is not necessary when this context is running off the main + * thread, as it will not use the new object cache for allocations. + */ + if (cx->isJSContext()) { + JSContext* ncx = cx->asJSContext(); + ncx->caches.newObjectCache.invalidateEntriesForShape(ncx, shape, proto); + } +} + +void +Zone::fixupInitialShapeTable() +{ + if (!initialShapes.initialized()) + return; + + for (decltype(initialShapes)::Enum e(initialShapes); !e.empty(); e.popFront()) { + // The shape may have been moved, but we can update that in place. + Shape* shape = e.front().shape.unbarrieredGet(); + if (IsForwarded(shape)) { + shape = Forwarded(shape); + e.mutableFront().shape.set(shape); + } + shape->updateBaseShapeAfterMovingGC(); + + // If the prototype has moved we have to rekey the entry. + InitialShapeEntry entry = e.front(); + if (entry.proto.proto().isObject() && IsForwarded(entry.proto.proto().toObject())) { + entry.proto.setProto(TaggedProto(Forwarded(entry.proto.proto().toObject()))); + using Lookup = InitialShapeEntry::Lookup; + Lookup relookup(shape->getObjectClass(), + Lookup::ShapeProto(entry.proto), + shape->numFixedSlots(), + shape->getObjectFlags()); + e.rekeyFront(relookup, entry); + } + } +} + +void +AutoRooterGetterSetter::Inner::trace(JSTracer* trc) +{ + if ((attrs & JSPROP_GETTER) && *pgetter) + TraceRoot(trc, (JSObject**) pgetter, "AutoRooterGetterSetter getter"); + if ((attrs & JSPROP_SETTER) && *psetter) + TraceRoot(trc, (JSObject**) psetter, "AutoRooterGetterSetter setter"); +} + +JS::ubi::Node::Size +JS::ubi::Concrete<js::Shape>::size(mozilla::MallocSizeOf mallocSizeOf) const +{ + Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind()); + + AutoCheckCannotGC nogc; + if (ShapeTable* table = get().maybeTable(nogc)) + size += table->sizeOfIncludingThis(mallocSizeOf); + + if (!get().inDictionary() && get().kids.isHash()) + size += get().kids.toHash()->sizeOfIncludingThis(mallocSizeOf); + + return size; +} + +JS::ubi::Node::Size +JS::ubi::Concrete<js::BaseShape>::size(mozilla::MallocSizeOf mallocSizeOf) const +{ + return js::gc::Arena::thingSize(get().asTenured().getAllocKind()); +} |