michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* JS symbol tables. */ michael@0: michael@0: #include "vm/Shape-inl.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jshashutil.h" michael@0: #include "jsobj.h" michael@0: michael@0: #include "js/HashTable.h" michael@0: michael@0: #include "jscntxtinlines.h" michael@0: #include "jsobjinlines.h" michael@0: michael@0: #include "vm/ObjectImpl-inl.h" michael@0: #include "vm/Runtime-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: michael@0: using mozilla::CeilingLog2Size; michael@0: using mozilla::DebugOnly; michael@0: using mozilla::PodZero; michael@0: using mozilla::RotateLeft; michael@0: michael@0: bool michael@0: ShapeTable::init(ThreadSafeContext *cx, Shape *lastProp) michael@0: { michael@0: /* michael@0: * Either we're creating a table for a large scope that was populated michael@0: * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or michael@0: * JSOP_SETPROP; or else calloc failed at least once already. In any michael@0: * event, let's try to grow, overallocating to hold at least twice the michael@0: * current population. michael@0: */ michael@0: uint32_t sizeLog2 = CeilingLog2Size(2 * entryCount); michael@0: if (sizeLog2 < MIN_SIZE_LOG2) michael@0: sizeLog2 = MIN_SIZE_LOG2; michael@0: michael@0: /* michael@0: * Use rt->calloc_ for memory accounting and overpressure handling michael@0: * without OOM reporting. See ShapeTable::change. michael@0: */ michael@0: entries = (Shape **) cx->calloc_(sizeOfEntries(JS_BIT(sizeLog2))); michael@0: if (!entries) michael@0: return false; michael@0: michael@0: hashShift = HASH_BITS - sizeLog2; michael@0: for (Shape::Range r(lastProp); !r.empty(); r.popFront()) { michael@0: Shape &shape = r.front(); michael@0: JS_ASSERT(cx->isThreadLocal(&shape)); michael@0: Shape **spp = search(shape.propid(), true); michael@0: michael@0: /* michael@0: * Beware duplicate args and arg vs. var conflicts: the youngest shape michael@0: * (nearest to lastProp) must win. See bug 600067. michael@0: */ michael@0: if (!SHAPE_FETCH(spp)) michael@0: SHAPE_STORE_PRESERVING_COLLISION(spp, &shape); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: Shape::removeFromDictionary(ObjectImpl *obj) michael@0: { michael@0: JS_ASSERT(inDictionary()); michael@0: JS_ASSERT(obj->inDictionaryMode()); michael@0: JS_ASSERT(listp); michael@0: michael@0: JS_ASSERT(obj->shape_->inDictionary()); michael@0: JS_ASSERT(obj->shape_->listp == &obj->shape_); michael@0: michael@0: if (parent) michael@0: parent->listp = listp; michael@0: *listp = parent; michael@0: listp = nullptr; michael@0: } michael@0: michael@0: void michael@0: Shape::insertIntoDictionary(HeapPtrShape *dictp) michael@0: { michael@0: // Don't assert inDictionaryMode() here because we may be called from michael@0: // JSObject::toDictionaryMode via JSObject::newDictionaryShape. michael@0: JS_ASSERT(inDictionary()); michael@0: JS_ASSERT(!listp); michael@0: michael@0: JS_ASSERT_IF(*dictp, (*dictp)->inDictionary()); michael@0: JS_ASSERT_IF(*dictp, (*dictp)->listp == dictp); michael@0: JS_ASSERT_IF(*dictp, compartment() == (*dictp)->compartment()); michael@0: michael@0: setParent(dictp->get()); michael@0: if (parent) michael@0: parent->listp = &parent; michael@0: listp = (HeapPtrShape *) dictp; michael@0: *dictp = this; michael@0: } michael@0: michael@0: bool michael@0: Shape::makeOwnBaseShape(ThreadSafeContext *cx) michael@0: { michael@0: JS_ASSERT(!base()->isOwned()); michael@0: JS_ASSERT(cx->isThreadLocal(this)); michael@0: assertSameCompartmentDebugOnly(cx, compartment()); michael@0: michael@0: BaseShape *nbase = js_NewGCBaseShape(cx); michael@0: if (!nbase) michael@0: return false; michael@0: michael@0: new (nbase) BaseShape(StackBaseShape(this)); michael@0: nbase->setOwned(base()->toUnowned()); michael@0: michael@0: this->base_ = nbase; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: Shape::handoffTableTo(Shape *shape) michael@0: { michael@0: JS_ASSERT(inDictionary() && shape->inDictionary()); michael@0: michael@0: if (this == shape) michael@0: return; michael@0: michael@0: JS_ASSERT(base()->isOwned() && !shape->base()->isOwned()); michael@0: michael@0: BaseShape *nbase = base(); michael@0: michael@0: JS_ASSERT_IF(shape->hasSlot(), nbase->slotSpan() > shape->slot()); michael@0: michael@0: this->base_ = nbase->baseUnowned(); michael@0: nbase->adoptUnowned(shape->base()->toUnowned()); michael@0: michael@0: shape->base_ = nbase; michael@0: } michael@0: michael@0: /* static */ bool michael@0: Shape::hashify(ThreadSafeContext *cx, Shape *shape) michael@0: { michael@0: JS_ASSERT(!shape->hasTable()); michael@0: michael@0: if (!shape->ensureOwnBaseShape(cx)) michael@0: return false; michael@0: michael@0: ShapeTable *table = cx->new_(shape->entryCount()); michael@0: if (!table) michael@0: return false; michael@0: michael@0: if (!table->init(cx, shape)) { michael@0: js_free(table); michael@0: return false; michael@0: } michael@0: michael@0: shape->base()->setTable(table); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Double hashing needs the second hash code to be relatively prime to table michael@0: * size, so we simply make hash2 odd. michael@0: */ michael@0: #define HASH1(hash0,shift) ((hash0) >> (shift)) michael@0: #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1) michael@0: michael@0: Shape ** michael@0: ShapeTable::search(jsid id, bool adding) michael@0: { michael@0: js::HashNumber hash0, hash1, hash2; michael@0: int sizeLog2; michael@0: Shape *stored, *shape, **spp, **firstRemoved; michael@0: uint32_t sizeMask; michael@0: michael@0: JS_ASSERT(entries); michael@0: JS_ASSERT(!JSID_IS_EMPTY(id)); michael@0: michael@0: /* Compute the primary hash address. */ michael@0: hash0 = HashId(id); michael@0: hash1 = HASH1(hash0, hashShift); michael@0: spp = entries + hash1; michael@0: michael@0: /* Miss: return space for a new entry. */ michael@0: stored = *spp; michael@0: if (SHAPE_IS_FREE(stored)) michael@0: return spp; michael@0: michael@0: /* Hit: return entry. */ michael@0: shape = SHAPE_CLEAR_COLLISION(stored); michael@0: if (shape && shape->propidRaw() == id) michael@0: return spp; michael@0: michael@0: /* Collision: double hash. */ michael@0: sizeLog2 = HASH_BITS - hashShift; michael@0: hash2 = HASH2(hash0, sizeLog2, hashShift); michael@0: sizeMask = JS_BITMASK(sizeLog2); michael@0: michael@0: #ifdef DEBUG michael@0: uintptr_t collision_flag = SHAPE_COLLISION; michael@0: #endif michael@0: michael@0: /* Save the first removed entry pointer so we can recycle it if adding. */ michael@0: if (SHAPE_IS_REMOVED(stored)) { michael@0: firstRemoved = spp; michael@0: } else { michael@0: firstRemoved = nullptr; michael@0: if (adding && !SHAPE_HAD_COLLISION(stored)) michael@0: SHAPE_FLAG_COLLISION(spp, shape); michael@0: #ifdef DEBUG michael@0: collision_flag &= uintptr_t(*spp) & SHAPE_COLLISION; michael@0: #endif michael@0: } michael@0: michael@0: for (;;) { michael@0: hash1 -= hash2; michael@0: hash1 &= sizeMask; michael@0: spp = entries + hash1; michael@0: michael@0: stored = *spp; michael@0: if (SHAPE_IS_FREE(stored)) michael@0: return (adding && firstRemoved) ? firstRemoved : spp; michael@0: michael@0: shape = SHAPE_CLEAR_COLLISION(stored); michael@0: if (shape && shape->propidRaw() == id) { michael@0: JS_ASSERT(collision_flag); michael@0: return spp; michael@0: } michael@0: michael@0: if (SHAPE_IS_REMOVED(stored)) { michael@0: if (!firstRemoved) michael@0: firstRemoved = spp; michael@0: } else { michael@0: if (adding && !SHAPE_HAD_COLLISION(stored)) michael@0: SHAPE_FLAG_COLLISION(spp, shape); michael@0: #ifdef DEBUG michael@0: collision_flag &= uintptr_t(*spp) & SHAPE_COLLISION; michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: /* NOTREACHED */ michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: ShapeTable::change(int log2Delta, ThreadSafeContext *cx) michael@0: { michael@0: JS_ASSERT(entries); michael@0: michael@0: /* michael@0: * Grow, shrink, or compress by changing this->entries. michael@0: */ michael@0: int oldlog2 = HASH_BITS - hashShift; michael@0: int newlog2 = oldlog2 + log2Delta; michael@0: uint32_t oldsize = JS_BIT(oldlog2); michael@0: uint32_t newsize = JS_BIT(newlog2); michael@0: Shape **newTable = (Shape **) cx->calloc_(sizeOfEntries(newsize)); michael@0: if (!newTable) michael@0: return false; michael@0: michael@0: /* Now that we have newTable allocated, update members. */ michael@0: hashShift = HASH_BITS - newlog2; michael@0: removedCount = 0; michael@0: Shape **oldTable = entries; michael@0: entries = newTable; michael@0: michael@0: /* Copy only live entries, leaving removed and free ones behind. */ michael@0: for (Shape **oldspp = oldTable; oldsize != 0; oldspp++) { michael@0: Shape *shape = SHAPE_FETCH(oldspp); michael@0: JS_ASSERT(cx->isThreadLocal(shape)); michael@0: if (shape) { michael@0: Shape **spp = search(shape->propid(), true); michael@0: JS_ASSERT(SHAPE_IS_FREE(*spp)); michael@0: *spp = shape; michael@0: } michael@0: oldsize--; michael@0: } michael@0: michael@0: /* Finally, free the old entries storage. */ michael@0: js_free(oldTable); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ShapeTable::grow(ThreadSafeContext *cx) michael@0: { michael@0: JS_ASSERT(needsToGrow()); michael@0: michael@0: uint32_t size = capacity(); michael@0: int delta = removedCount < size >> 2; michael@0: michael@0: if (!change(delta, cx) && entryCount + removedCount == size - 1) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: Shape::replaceLastProperty(ExclusiveContext *cx, StackBaseShape &base, michael@0: TaggedProto proto, HandleShape shape) michael@0: { michael@0: JS_ASSERT(!shape->inDictionary()); michael@0: michael@0: if (!shape->parent) { michael@0: /* Treat as resetting the initial property of the shape hierarchy. */ michael@0: AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); michael@0: return EmptyShape::getInitialShape(cx, base.clasp, proto, michael@0: base.parent, base.metadata, kind, michael@0: base.flags & BaseShape::OBJECT_FLAG_MASK); michael@0: } michael@0: michael@0: UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); michael@0: if (!nbase) michael@0: return nullptr; michael@0: michael@0: StackShape child(shape); michael@0: child.base = nbase; michael@0: michael@0: return cx->compartment()->propertyTree.getChild(cx, shape->parent, child); michael@0: } michael@0: michael@0: /* michael@0: * Get or create a property-tree or dictionary child property of |parent|, michael@0: * which must be lastProperty() if inDictionaryMode(), else parent must be michael@0: * one of lastProperty() or lastProperty()->parent. michael@0: */ michael@0: /* static */ Shape * michael@0: JSObject::getChildPropertyOnDictionary(ThreadSafeContext *cx, JS::HandleObject obj, michael@0: HandleShape parent, js::StackShape &child) michael@0: { michael@0: /* michael@0: * Shared properties have no slot, but slot_ will reflect that of parent. michael@0: * Unshared properties allocate a slot here but may lose it due to a michael@0: * JS_ClearScope call. michael@0: */ michael@0: if (!child.hasSlot()) { michael@0: child.setSlot(parent->maybeSlot()); michael@0: } else { michael@0: if (child.hasMissingSlot()) { michael@0: uint32_t slot; michael@0: if (!allocSlot(cx, obj, &slot)) michael@0: return nullptr; michael@0: child.setSlot(slot); michael@0: } else { michael@0: /* michael@0: * Slots can only be allocated out of order on objects in michael@0: * dictionary mode. Otherwise the child's slot must be after the michael@0: * parent's slot (if it has one), because slot number determines michael@0: * slot span for objects with that shape. Usually child slot michael@0: * *immediately* follows parent slot, but there may be a slot gap michael@0: * when the object uses some -- but not all -- of its reserved michael@0: * slots to store properties. michael@0: */ michael@0: JS_ASSERT(obj->inDictionaryMode() || michael@0: parent->hasMissingSlot() || michael@0: child.slot() == parent->maybeSlot() + 1 || michael@0: (parent->maybeSlot() + 1 < JSSLOT_FREE(obj->getClass()) && michael@0: child.slot() == JSSLOT_FREE(obj->getClass()))); michael@0: } michael@0: } michael@0: michael@0: RootedShape shape(cx); michael@0: michael@0: if (obj->inDictionaryMode()) { michael@0: JS_ASSERT(parent == obj->lastProperty()); michael@0: RootedGeneric childRoot(cx, &child); michael@0: shape = js_NewGCShape(cx); michael@0: if (!shape) michael@0: return nullptr; michael@0: if (childRoot->hasSlot() && childRoot->slot() >= obj->lastProperty()->base()->slotSpan()) { michael@0: if (!JSObject::setSlotSpan(cx, obj, childRoot->slot() + 1)) michael@0: return nullptr; michael@0: } michael@0: shape->initDictionaryShape(*childRoot, obj->numFixedSlots(), &obj->shape_); michael@0: } michael@0: michael@0: return shape; michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: JSObject::getChildProperty(ExclusiveContext *cx, michael@0: HandleObject obj, HandleShape parent, StackShape &unrootedChild) michael@0: { michael@0: RootedGeneric child(cx, &unrootedChild); michael@0: RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, *child)); michael@0: michael@0: if (!obj->inDictionaryMode()) { michael@0: shape = cx->compartment()->propertyTree.getChild(cx, parent, *child); michael@0: if (!shape) michael@0: return nullptr; michael@0: //JS_ASSERT(shape->parent == parent); michael@0: //JS_ASSERT_IF(parent != lastProperty(), parent == lastProperty()->parent); michael@0: if (!JSObject::setLastProperty(cx, obj, shape)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return shape; michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: JSObject::lookupChildProperty(ThreadSafeContext *cx, michael@0: HandleObject obj, HandleShape parent, StackShape &unrootedChild) michael@0: { michael@0: RootedGeneric child(cx, &unrootedChild); michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: michael@0: RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, *child)); michael@0: michael@0: if (!obj->inDictionaryMode()) { michael@0: shape = cx->compartment_->propertyTree.lookupChild(cx, parent, *child); michael@0: if (!shape) michael@0: return nullptr; michael@0: if (!JSObject::setLastProperty(cx, obj, shape)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return shape; michael@0: } michael@0: michael@0: bool michael@0: js::ObjectImpl::toDictionaryMode(ThreadSafeContext *cx) michael@0: { michael@0: JS_ASSERT(!inDictionaryMode()); michael@0: michael@0: /* We allocate the shapes from cx->compartment(), so make sure it's right. */ michael@0: JS_ASSERT(cx->isInsideCurrentCompartment(this)); michael@0: michael@0: /* michael@0: * This function is thread safe as long as the object is thread local. It michael@0: * does not modify the shared shapes, and only allocates newly allocated michael@0: * (and thus also thread local) shapes. michael@0: */ michael@0: JS_ASSERT(cx->isThreadLocal(this)); michael@0: michael@0: uint32_t span = slotSpan(); michael@0: michael@0: Rooted self(cx, this); michael@0: michael@0: /* michael@0: * Clone the shapes into a new dictionary list. Don't update the michael@0: * last property of this object until done, otherwise a GC michael@0: * triggered while creating the dictionary will get the wrong michael@0: * slot span for this object. michael@0: */ michael@0: RootedShape root(cx); michael@0: RootedShape dictionaryShape(cx); michael@0: michael@0: RootedShape shape(cx, lastProperty()); michael@0: while (shape) { michael@0: JS_ASSERT(!shape->inDictionary()); michael@0: michael@0: Shape *dprop = js_NewGCShape(cx); michael@0: if (!dprop) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: HeapPtrShape *listp = dictionaryShape michael@0: ? &dictionaryShape->parent michael@0: : (HeapPtrShape *) root.address(); michael@0: michael@0: StackShape child(shape); michael@0: dprop->initDictionaryShape(child, self->numFixedSlots(), listp); michael@0: michael@0: JS_ASSERT(!dprop->hasTable()); michael@0: dictionaryShape = dprop; michael@0: shape = shape->previous(); michael@0: } michael@0: michael@0: if (!Shape::hashify(cx, root)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: JS_ASSERT((Shape **) root->listp == root.address()); michael@0: root->listp = &self->shape_; michael@0: self->shape_ = root; michael@0: michael@0: JS_ASSERT(self->inDictionaryMode()); michael@0: root->base()->setSlotSpan(span); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Normalize stub getter and setter values for faster is-stub testing in the michael@0: * SHAPE_CALL_[GS]ETTER macros. michael@0: */ michael@0: static inline bool michael@0: NormalizeGetterAndSetter(JSObject *obj, michael@0: jsid id, unsigned attrs, unsigned flags, michael@0: PropertyOp &getter, michael@0: StrictPropertyOp &setter) michael@0: { michael@0: if (setter == JS_StrictPropertyStub) { michael@0: JS_ASSERT(!(attrs & JSPROP_SETTER)); michael@0: setter = nullptr; michael@0: } michael@0: if (getter == JS_PropertyStub) { michael@0: JS_ASSERT(!(attrs & JSPROP_GETTER)); michael@0: getter = nullptr; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: JSObject::addProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, michael@0: PropertyOp getter, StrictPropertyOp setter, michael@0: uint32_t slot, unsigned attrs, michael@0: unsigned flags, bool allowDictionary) michael@0: { michael@0: JS_ASSERT(!JSID_IS_VOID(id)); michael@0: michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return nullptr; michael@0: if (!extensible) { michael@0: if (cx->isJSContext()) michael@0: obj->reportNotExtensible(cx->asJSContext()); michael@0: return nullptr; michael@0: } michael@0: michael@0: NormalizeGetterAndSetter(obj, id, attrs, flags, getter, setter); michael@0: michael@0: Shape **spp = nullptr; michael@0: if (obj->inDictionaryMode()) michael@0: spp = obj->lastProperty()->table().search(id, true); michael@0: michael@0: return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, michael@0: flags, spp, allowDictionary); michael@0: } michael@0: michael@0: static bool michael@0: ShouldConvertToDictionary(JSObject *obj) michael@0: { michael@0: /* michael@0: * Use a lower limit if this object is likely a hashmap (SETELEM was used michael@0: * to set properties). michael@0: */ michael@0: if (obj->hadElementsAccess()) michael@0: return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS; michael@0: return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT; michael@0: } michael@0: michael@0: template michael@0: static inline UnownedBaseShape * michael@0: GetOrLookupUnownedBaseShape(typename ExecutionModeTraits::ExclusiveContextType cx, michael@0: StackBaseShape &base) michael@0: { michael@0: if (mode == ParallelExecution) michael@0: return BaseShape::lookupUnowned(cx, base); michael@0: return BaseShape::getUnowned(cx->asExclusiveContext(), base); michael@0: } michael@0: michael@0: template michael@0: /* static */ Shape * michael@0: JSObject::addPropertyInternal(typename ExecutionModeTraits::ExclusiveContextType cx, michael@0: HandleObject obj, HandleId id, michael@0: PropertyOp getter, StrictPropertyOp setter, michael@0: uint32_t slot, unsigned attrs, michael@0: unsigned flags, Shape **spp, michael@0: bool allowDictionary) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: JS_ASSERT_IF(!allowDictionary, !obj->inDictionaryMode()); michael@0: michael@0: AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); michael@0: michael@0: /* michael@0: * The code below deals with either converting obj to dictionary mode or michael@0: * growing an object that's already in dictionary mode. Either way, michael@0: * dictionray operations are safe if thread local. michael@0: */ michael@0: ShapeTable *table = nullptr; michael@0: if (!obj->inDictionaryMode()) { michael@0: bool stableSlot = michael@0: (slot == SHAPE_INVALID_SLOT) || michael@0: obj->lastProperty()->hasMissingSlot() || michael@0: (slot == obj->lastProperty()->maybeSlot() + 1); michael@0: JS_ASSERT_IF(!allowDictionary, stableSlot); michael@0: if (allowDictionary && michael@0: (!stableSlot || ShouldConvertToDictionary(obj))) michael@0: { michael@0: if (!obj->toDictionaryMode(cx)) michael@0: return nullptr; michael@0: table = &obj->lastProperty()->table(); michael@0: spp = table->search(id, true); michael@0: } michael@0: } else { michael@0: table = &obj->lastProperty()->table(); michael@0: if (table->needsToGrow()) { michael@0: if (!table->grow(cx)) michael@0: return nullptr; michael@0: spp = table->search(id, true); michael@0: JS_ASSERT(!SHAPE_FETCH(spp)); michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(!!table == !!spp); michael@0: michael@0: /* Find or create a property tree node labeled by our arguments. */ michael@0: RootedShape shape(cx); michael@0: { michael@0: RootedShape last(cx, obj->lastProperty()); michael@0: michael@0: uint32_t index; michael@0: bool indexed = js_IdIsIndex(id, &index); michael@0: michael@0: Rooted nbase(cx); michael@0: if (last->base()->matchesGetterSetter(getter, setter) && !indexed) { michael@0: nbase = last->base()->unowned(); michael@0: } else { michael@0: StackBaseShape base(last->base()); michael@0: base.updateGetterSetter(attrs, getter, setter); michael@0: if (indexed) michael@0: base.flags |= BaseShape::INDEXED; michael@0: nbase = GetOrLookupUnownedBaseShape(cx, base); michael@0: if (!nbase) michael@0: return nullptr; michael@0: } michael@0: michael@0: StackShape child(nbase, id, slot, attrs, flags); michael@0: shape = getOrLookupChildProperty(cx, obj, last, child); michael@0: } michael@0: michael@0: if (shape) { michael@0: JS_ASSERT(shape == obj->lastProperty()); michael@0: michael@0: if (table) { michael@0: /* Store the tree node pointer in the table entry for id. */ michael@0: SHAPE_STORE_PRESERVING_COLLISION(spp, static_cast(shape)); michael@0: ++table->entryCount; michael@0: michael@0: /* Pass the table along to the new last property, namely shape. */ michael@0: JS_ASSERT(&shape->parent->table() == table); michael@0: shape->parent->handoffTableTo(shape); michael@0: } michael@0: michael@0: obj->checkShapeConsistency(); michael@0: return shape; michael@0: } michael@0: michael@0: obj->checkShapeConsistency(); michael@0: return nullptr; michael@0: } michael@0: michael@0: template /* static */ Shape * michael@0: JSObject::addPropertyInternal(ExclusiveContext *cx, michael@0: HandleObject obj, HandleId id, michael@0: PropertyOp getter, StrictPropertyOp setter, michael@0: uint32_t slot, unsigned attrs, michael@0: unsigned flags, Shape **spp, michael@0: bool allowDictionary); michael@0: template /* static */ Shape * michael@0: JSObject::addPropertyInternal(ForkJoinContext *cx, michael@0: HandleObject obj, HandleId id, michael@0: PropertyOp getter, StrictPropertyOp setter, michael@0: uint32_t slot, unsigned attrs, michael@0: unsigned flags, Shape **spp, michael@0: bool allowDictionary); michael@0: michael@0: JSObject * michael@0: js::NewReshapedObject(JSContext *cx, HandleTypeObject type, JSObject *parent, michael@0: gc::AllocKind allocKind, HandleShape shape, NewObjectKind newKind) michael@0: { michael@0: RootedObject res(cx, NewObjectWithType(cx, type, parent, allocKind, newKind)); michael@0: if (!res) michael@0: return nullptr; michael@0: michael@0: if (shape->isEmptyShape()) michael@0: return res; michael@0: michael@0: /* Get all the ids in the object, in order. */ michael@0: js::AutoIdVector ids(cx); michael@0: { michael@0: for (unsigned i = 0; i <= shape->slot(); i++) { michael@0: if (!ids.append(JSID_VOID)) michael@0: return nullptr; michael@0: } michael@0: Shape *nshape = shape; michael@0: while (!nshape->isEmptyShape()) { michael@0: ids[nshape->slot()] = nshape->propid(); michael@0: nshape = nshape->previous(); michael@0: } michael@0: } michael@0: michael@0: /* Construct the new shape, without updating type information. */ michael@0: RootedId id(cx); michael@0: RootedShape newShape(cx, res->lastProperty()); michael@0: for (unsigned i = 0; i < ids.length(); i++) { michael@0: id = ids[i]; michael@0: JS_ASSERT(!res->nativeContains(cx, id)); michael@0: michael@0: uint32_t index; michael@0: bool indexed = js_IdIsIndex(id, &index); michael@0: michael@0: Rooted nbase(cx, newShape->base()->unowned()); michael@0: if (indexed) { michael@0: StackBaseShape base(nbase); michael@0: base.flags |= BaseShape::INDEXED; michael@0: nbase = GetOrLookupUnownedBaseShape(cx, base); michael@0: if (!nbase) michael@0: return nullptr; michael@0: } michael@0: michael@0: StackShape child(nbase, id, i, JSPROP_ENUMERATE, 0); michael@0: newShape = cx->compartment()->propertyTree.getChild(cx, newShape, child); michael@0: if (!newShape) michael@0: return nullptr; michael@0: if (!JSObject::setLastProperty(cx, res, newShape)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: /* michael@0: * Check and adjust the new attributes for the shape to make sure that our michael@0: * slot access optimizations are sound. It is responsibility of the callers to michael@0: * enforce all restrictions from ECMA-262 v5 8.12.9 [[DefineOwnProperty]]. michael@0: */ michael@0: static inline bool michael@0: CheckCanChangeAttrs(ThreadSafeContext *cx, JSObject *obj, Shape *shape, unsigned *attrsp) michael@0: { michael@0: if (shape->configurable()) michael@0: return true; michael@0: michael@0: /* A permanent property must stay permanent. */ michael@0: *attrsp |= JSPROP_PERMANENT; michael@0: michael@0: /* Reject attempts to remove a slot from the permanent data property. */ michael@0: if (shape->isDataDescriptor() && shape->hasSlot() && michael@0: (*attrsp & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED))) michael@0: { michael@0: if (cx->isJSContext()) michael@0: obj->reportNotConfigurable(cx->asJSContext(), shape->propid()); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: /* static */ Shape * michael@0: JSObject::putProperty(typename ExecutionModeTraits::ExclusiveContextType cx, michael@0: HandleObject obj, HandleId id, michael@0: PropertyOp getter, StrictPropertyOp setter, michael@0: uint32_t slot, unsigned attrs, unsigned flags) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: JS_ASSERT(!JSID_IS_VOID(id)); michael@0: michael@0: #ifdef DEBUG michael@0: if (obj->is()) { michael@0: ArrayObject *arr = &obj->as(); michael@0: uint32_t index; michael@0: if (js_IdIsIndex(id, &index)) michael@0: JS_ASSERT(index < arr->length() || arr->lengthIsWritable()); michael@0: } michael@0: #endif michael@0: michael@0: NormalizeGetterAndSetter(obj, id, attrs, flags, getter, setter); michael@0: michael@0: AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); michael@0: michael@0: /* michael@0: * Search for id in order to claim its entry if table has been allocated. michael@0: * michael@0: * Note that we can only try to claim an entry in a table that is thread michael@0: * local. An object may be thread local *without* its shape being thread michael@0: * local. The only thread local objects that *also* have thread local michael@0: * shapes are dictionaries that were allocated/converted thread michael@0: * locally. Only for those objects we can try to claim an entry in its michael@0: * shape table. michael@0: */ michael@0: Shape **spp; michael@0: RootedShape shape(cx, (mode == ParallelExecution michael@0: ? Shape::searchThreadLocal(cx, obj->lastProperty(), id, &spp, michael@0: cx->isThreadLocal(obj->lastProperty())) michael@0: : Shape::search(cx->asExclusiveContext(), obj->lastProperty(), id, michael@0: &spp, true))); michael@0: if (!shape) { michael@0: /* michael@0: * You can't add properties to a non-extensible object, but you can change michael@0: * attributes of properties in such objects. michael@0: */ michael@0: bool extensible; michael@0: michael@0: if (mode == ParallelExecution) { michael@0: if (obj->is()) michael@0: return nullptr; michael@0: extensible = obj->nonProxyIsExtensible(); michael@0: } else { michael@0: if (!JSObject::isExtensible(cx->asExclusiveContext(), obj, &extensible)) michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!extensible) { michael@0: if (cx->isJSContext()) michael@0: obj->reportNotExtensible(cx->asJSContext()); michael@0: return nullptr; michael@0: } michael@0: michael@0: return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, flags, michael@0: spp, true); michael@0: } michael@0: michael@0: /* Property exists: search must have returned a valid *spp. */ michael@0: JS_ASSERT_IF(spp, !SHAPE_IS_REMOVED(*spp)); michael@0: michael@0: if (!CheckCanChangeAttrs(cx, obj, shape, &attrs)) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * If the caller wants to allocate a slot, but doesn't care which slot, michael@0: * copy the existing shape's slot into slot so we can match shape, if all michael@0: * other members match. michael@0: */ michael@0: bool hadSlot = shape->hasSlot(); michael@0: uint32_t oldSlot = shape->maybeSlot(); michael@0: if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot) michael@0: slot = oldSlot; michael@0: michael@0: Rooted nbase(cx); michael@0: { michael@0: uint32_t index; michael@0: bool indexed = js_IdIsIndex(id, &index); michael@0: StackBaseShape base(obj->lastProperty()->base()); michael@0: base.updateGetterSetter(attrs, getter, setter); michael@0: if (indexed) michael@0: base.flags |= BaseShape::INDEXED; michael@0: nbase = GetOrLookupUnownedBaseShape(cx, base); michael@0: if (!nbase) michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Now that we've possibly preserved slot, check whether all members match. michael@0: * If so, this is a redundant "put" and we can return without more work. michael@0: */ michael@0: if (shape->matchesParamsAfterId(nbase, slot, attrs, flags)) michael@0: return shape; michael@0: michael@0: /* michael@0: * Overwriting a non-last property requires switching to dictionary mode. michael@0: * The shape tree is shared immutable, and we can't removeProperty and then michael@0: * addPropertyInternal because a failure under add would lose data. michael@0: */ michael@0: if (shape != obj->lastProperty() && !obj->inDictionaryMode()) { michael@0: if (!obj->toDictionaryMode(cx)) michael@0: return nullptr; michael@0: spp = obj->lastProperty()->table().search(shape->propid(), false); michael@0: shape = SHAPE_FETCH(spp); michael@0: } michael@0: michael@0: JS_ASSERT_IF(shape->hasSlot() && !(attrs & JSPROP_SHARED), shape->slot() == slot); michael@0: michael@0: if (obj->inDictionaryMode()) { michael@0: /* michael@0: * Updating some property in a dictionary-mode object. Create a new michael@0: * shape for the existing property, and also generate a new shape for michael@0: * the last property of the dictionary (unless the modified property michael@0: * is also the last property). michael@0: */ michael@0: bool updateLast = (shape == obj->lastProperty()); michael@0: shape = obj->replaceWithNewEquivalentShape(cx, shape); michael@0: if (!shape) michael@0: return nullptr; michael@0: if (!updateLast && !obj->generateOwnShape(cx)) michael@0: return nullptr; michael@0: michael@0: /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */ michael@0: if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED)) { michael@0: if (!allocSlot(cx, obj, &slot)) michael@0: return nullptr; michael@0: } michael@0: michael@0: if (updateLast) michael@0: shape->base()->adoptUnowned(nbase); michael@0: else michael@0: shape->base_ = nbase; michael@0: michael@0: JS_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); michael@0: michael@0: shape->setSlot(slot); michael@0: shape->attrs = uint8_t(attrs); michael@0: shape->flags = flags | Shape::IN_DICTIONARY; michael@0: } else { michael@0: /* michael@0: * Updating the last property in a non-dictionary-mode object. Find an michael@0: * alternate shared child of the last property's previous shape. michael@0: */ michael@0: StackBaseShape base(obj->lastProperty()->base()); michael@0: base.updateGetterSetter(attrs, getter, setter); michael@0: michael@0: UnownedBaseShape *nbase = GetOrLookupUnownedBaseShape(cx, base); michael@0: if (!nbase) michael@0: return nullptr; michael@0: michael@0: JS_ASSERT(shape == obj->lastProperty()); michael@0: michael@0: /* Find or create a property tree node labeled by our arguments. */ michael@0: StackShape child(nbase, id, slot, attrs, flags); michael@0: RootedShape parent(cx, shape->parent); michael@0: Shape *newShape = getOrLookupChildProperty(cx, obj, parent, child); michael@0: michael@0: if (!newShape) { michael@0: obj->checkShapeConsistency(); michael@0: return nullptr; michael@0: } michael@0: michael@0: shape = newShape; michael@0: } michael@0: michael@0: /* michael@0: * Can't fail now, so free the previous incarnation's slot if the new shape michael@0: * has no slot. But we do not need to free oldSlot (and must not, as trying michael@0: * to will botch an assertion in JSObject::freeSlot) if the new last michael@0: * property (shape here) has a slotSpan that does not cover it. michael@0: */ michael@0: if (hadSlot && !shape->hasSlot()) { michael@0: if (oldSlot < obj->slotSpan()) michael@0: obj->freeSlot(oldSlot); michael@0: /* Note: The optimization based on propertyRemovals is only relevant to the main thread. */ michael@0: if (cx->isJSContext()) michael@0: ++cx->asJSContext()->runtime()->propertyRemovals; michael@0: } michael@0: michael@0: obj->checkShapeConsistency(); michael@0: michael@0: return shape; michael@0: } michael@0: michael@0: template /* static */ Shape * michael@0: JSObject::putProperty(ExclusiveContext *cx, michael@0: HandleObject obj, HandleId id, michael@0: PropertyOp getter, StrictPropertyOp setter, michael@0: uint32_t slot, unsigned attrs, michael@0: unsigned flags); michael@0: template /* static */ Shape * michael@0: JSObject::putProperty(ForkJoinContext *cx, michael@0: HandleObject obj, HandleId id, michael@0: PropertyOp getter, StrictPropertyOp setter, michael@0: uint32_t slot, unsigned attrs, michael@0: unsigned flags); michael@0: michael@0: template michael@0: /* static */ Shape * michael@0: JSObject::changeProperty(typename ExecutionModeTraits::ExclusiveContextType cx, michael@0: HandleObject obj, HandleShape shape, unsigned attrs, michael@0: unsigned mask, PropertyOp getter, StrictPropertyOp setter) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: JS_ASSERT(obj->nativeContainsPure(shape)); michael@0: michael@0: attrs |= shape->attrs & mask; michael@0: JS_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); michael@0: michael@0: /* Allow only shared (slotless) => unshared (slotful) transition. */ michael@0: JS_ASSERT(!((attrs ^ shape->attrs) & JSPROP_SHARED) || michael@0: !(attrs & JSPROP_SHARED)); michael@0: michael@0: if (mode == ParallelExecution) { michael@0: if (!types::IsTypePropertyIdMarkedNonData(obj, shape->propid())) michael@0: return nullptr; michael@0: } else { michael@0: types::MarkTypePropertyNonData(cx->asExclusiveContext(), obj, shape->propid()); michael@0: } michael@0: michael@0: if (getter == JS_PropertyStub) michael@0: getter = nullptr; michael@0: if (setter == JS_StrictPropertyStub) michael@0: setter = nullptr; michael@0: michael@0: if (!CheckCanChangeAttrs(cx, obj, shape, &attrs)) michael@0: return nullptr; michael@0: michael@0: if (shape->attrs == attrs && shape->getter() == getter && shape->setter() == setter) michael@0: return shape; michael@0: michael@0: /* michael@0: * Let JSObject::putProperty handle this |overwriting| case, including michael@0: * the conservation of shape->slot (if it's valid). We must not call michael@0: * removeProperty because it will free an allocated shape->slot, and michael@0: * putProperty won't re-allocate it. michael@0: */ michael@0: RootedId propid(cx, shape->propid()); michael@0: Shape *newShape = putProperty(cx, obj, propid, getter, setter, michael@0: shape->maybeSlot(), attrs, shape->flags); michael@0: michael@0: obj->checkShapeConsistency(); michael@0: return newShape; michael@0: } michael@0: michael@0: template /* static */ Shape * michael@0: JSObject::changeProperty(ExclusiveContext *cx, michael@0: HandleObject obj, HandleShape shape, michael@0: unsigned attrs, unsigned mask, michael@0: PropertyOp getter, StrictPropertyOp setter); michael@0: template /* static */ Shape * michael@0: JSObject::changeProperty(ForkJoinContext *cx, michael@0: HandleObject obj, HandleShape shape, michael@0: unsigned attrs, unsigned mask, michael@0: PropertyOp getter, StrictPropertyOp setter); michael@0: michael@0: bool michael@0: JSObject::removeProperty(ExclusiveContext *cx, jsid id_) michael@0: { michael@0: RootedId id(cx, id_); michael@0: RootedObject self(cx, this); michael@0: michael@0: Shape **spp; michael@0: RootedShape shape(cx, Shape::search(cx, lastProperty(), id, &spp)); michael@0: if (!shape) michael@0: return true; michael@0: michael@0: /* michael@0: * If shape is not the last property added, or the last property cannot michael@0: * be removed, switch to dictionary mode. michael@0: */ michael@0: if (!self->inDictionaryMode() && (shape != self->lastProperty() || !self->canRemoveLastProperty())) { michael@0: if (!self->toDictionaryMode(cx)) michael@0: return false; michael@0: spp = self->lastProperty()->table().search(shape->propid(), false); michael@0: shape = SHAPE_FETCH(spp); michael@0: } michael@0: michael@0: /* michael@0: * If in dictionary mode, get a new shape for the last property after the michael@0: * removal. We need a fresh shape for all dictionary deletions, even of michael@0: * the last property. Otherwise, a shape could replay and caches might michael@0: * return deleted DictionaryShapes! See bug 595365. Do this before changing michael@0: * the object or table, so the remaining removal is infallible. michael@0: */ michael@0: RootedShape spare(cx); michael@0: if (self->inDictionaryMode()) { michael@0: spare = js_NewGCShape(cx); michael@0: if (!spare) michael@0: return false; michael@0: new (spare) Shape(shape->base()->unowned(), 0); michael@0: if (shape == self->lastProperty()) { michael@0: /* michael@0: * Get an up to date unowned base shape for the new last property michael@0: * when removing the dictionary's last property. Information in michael@0: * base shapes for non-last properties may be out of sync with the michael@0: * object's state. michael@0: */ michael@0: RootedShape previous(cx, self->lastProperty()->parent); michael@0: StackBaseShape base(self->lastProperty()->base()); michael@0: base.updateGetterSetter(previous->attrs, previous->getter(), previous->setter()); michael@0: BaseShape *nbase = BaseShape::getUnowned(cx, base); michael@0: if (!nbase) michael@0: return false; michael@0: previous->base_ = nbase; michael@0: } michael@0: } michael@0: michael@0: /* If shape has a slot, free its slot number. */ michael@0: if (shape->hasSlot()) { michael@0: self->freeSlot(shape->slot()); michael@0: if (cx->isJSContext()) michael@0: ++cx->asJSContext()->runtime()->propertyRemovals; michael@0: } michael@0: michael@0: /* michael@0: * A dictionary-mode object owns mutable, unique shapes on a non-circular michael@0: * doubly linked list, hashed by lastProperty()->table. So we can edit the michael@0: * list and hash in place. michael@0: */ michael@0: if (self->inDictionaryMode()) { michael@0: ShapeTable &table = self->lastProperty()->table(); michael@0: michael@0: if (SHAPE_HAD_COLLISION(*spp)) { michael@0: *spp = SHAPE_REMOVED; michael@0: ++table.removedCount; michael@0: --table.entryCount; michael@0: } else { michael@0: *spp = nullptr; michael@0: --table.entryCount; michael@0: michael@0: #ifdef DEBUG michael@0: /* michael@0: * Check the consistency of the table but limit the number of michael@0: * checks not to alter significantly the complexity of the michael@0: * delete in debug builds, see bug 534493. michael@0: */ michael@0: Shape *aprop = self->lastProperty(); michael@0: for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent) michael@0: JS_ASSERT_IF(aprop != shape, self->nativeContains(cx, aprop)); michael@0: #endif michael@0: } michael@0: michael@0: { michael@0: /* Remove shape from its non-circular doubly linked list. */ michael@0: Shape *oldLastProp = self->lastProperty(); michael@0: shape->removeFromDictionary(self); michael@0: michael@0: /* Hand off table from the old to new last property. */ michael@0: oldLastProp->handoffTableTo(self->lastProperty()); michael@0: } michael@0: michael@0: /* Generate a new shape for the object, infallibly. */ michael@0: JS_ALWAYS_TRUE(self->generateOwnShape(cx, spare)); michael@0: michael@0: /* Consider shrinking table if its load factor is <= .25. */ michael@0: uint32_t size = table.capacity(); michael@0: if (size > ShapeTable::MIN_SIZE && table.entryCount <= size >> 2) michael@0: (void) table.change(-1, cx); michael@0: } else { michael@0: /* michael@0: * Non-dictionary-mode shape tables are shared immutables, so all we michael@0: * need do is retract the last property and we'll either get or else michael@0: * lazily make via a later hashify the exact table for the new property michael@0: * lineage. michael@0: */ michael@0: JS_ASSERT(shape == self->lastProperty()); michael@0: self->removeLastProperty(cx); michael@0: } michael@0: michael@0: self->checkShapeConsistency(); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ void michael@0: JSObject::clear(JSContext *cx, HandleObject obj) michael@0: { michael@0: RootedShape shape(cx, obj->lastProperty()); michael@0: JS_ASSERT(obj->inDictionaryMode() == shape->inDictionary()); michael@0: michael@0: while (shape->parent) { michael@0: shape = shape->parent; michael@0: JS_ASSERT(obj->inDictionaryMode() == shape->inDictionary()); michael@0: } michael@0: JS_ASSERT(shape->isEmptyShape()); michael@0: michael@0: if (obj->inDictionaryMode()) michael@0: shape->listp = &obj->shape_; michael@0: michael@0: JS_ALWAYS_TRUE(JSObject::setLastProperty(cx, obj, shape)); michael@0: michael@0: ++cx->runtime()->propertyRemovals; michael@0: obj->checkShapeConsistency(); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::rollbackProperties(ExclusiveContext *cx, HandleObject obj, uint32_t slotSpan) michael@0: { michael@0: /* michael@0: * Remove properties from this object until it has a matching slot span. michael@0: * The object cannot have escaped in a way which would prevent safe michael@0: * removal of the last properties. michael@0: */ michael@0: JS_ASSERT(!obj->inDictionaryMode() && slotSpan <= obj->slotSpan()); michael@0: while (true) { michael@0: if (obj->lastProperty()->isEmptyShape()) { michael@0: JS_ASSERT(slotSpan == 0); michael@0: break; michael@0: } else { michael@0: uint32_t slot = obj->lastProperty()->slot(); michael@0: if (slot < slotSpan) michael@0: break; michael@0: JS_ASSERT(obj->getSlot(slot).isUndefined()); michael@0: } michael@0: if (!obj->removeProperty(cx, obj->lastProperty()->propid())) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: Shape * michael@0: ObjectImpl::replaceWithNewEquivalentShape(ThreadSafeContext *cx, Shape *oldShape, Shape *newShape) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(this)); michael@0: JS_ASSERT(cx->isThreadLocal(oldShape)); michael@0: JS_ASSERT(cx->isInsideCurrentCompartment(oldShape)); michael@0: JS_ASSERT_IF(oldShape != lastProperty(), michael@0: inDictionaryMode() && michael@0: ((cx->isExclusiveContext() michael@0: ? nativeLookup(cx->asExclusiveContext(), oldShape->propidRef()) michael@0: : nativeLookupPure(oldShape->propidRef())) == oldShape)); michael@0: michael@0: ObjectImpl *self = this; michael@0: michael@0: if (!inDictionaryMode()) { michael@0: Rooted selfRoot(cx, self); michael@0: RootedShape newRoot(cx, newShape); michael@0: if (!toDictionaryMode(cx)) michael@0: return nullptr; michael@0: oldShape = selfRoot->lastProperty(); michael@0: self = selfRoot; michael@0: newShape = newRoot; michael@0: } michael@0: michael@0: if (!newShape) { michael@0: Rooted selfRoot(cx, self); michael@0: RootedShape oldRoot(cx, oldShape); michael@0: newShape = js_NewGCShape(cx); michael@0: if (!newShape) michael@0: return nullptr; michael@0: new (newShape) Shape(oldRoot->base()->unowned(), 0); michael@0: self = selfRoot; michael@0: oldShape = oldRoot; michael@0: } michael@0: michael@0: ShapeTable &table = self->lastProperty()->table(); michael@0: Shape **spp = oldShape->isEmptyShape() michael@0: ? nullptr michael@0: : table.search(oldShape->propidRef(), false); michael@0: michael@0: /* michael@0: * Splice the new shape into the same position as the old shape, preserving michael@0: * enumeration order (see bug 601399). michael@0: */ michael@0: StackShape nshape(oldShape); michael@0: newShape->initDictionaryShape(nshape, self->numFixedSlots(), oldShape->listp); michael@0: michael@0: JS_ASSERT(newShape->parent == oldShape); michael@0: oldShape->removeFromDictionary(self); michael@0: michael@0: if (newShape == self->lastProperty()) michael@0: oldShape->handoffTableTo(newShape); michael@0: michael@0: if (spp) michael@0: SHAPE_STORE_PRESERVING_COLLISION(spp, newShape); michael@0: return newShape; michael@0: } michael@0: michael@0: bool michael@0: JSObject::shadowingShapeChange(ExclusiveContext *cx, const Shape &shape) michael@0: { michael@0: return generateOwnShape(cx); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::clearParent(JSContext *cx, HandleObject obj) michael@0: { michael@0: return setParent(cx, obj, NullPtr()); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::setParent(JSContext *cx, HandleObject obj, HandleObject parent) michael@0: { michael@0: if (parent && !parent->setDelegate(cx)) michael@0: return false; michael@0: michael@0: if (obj->inDictionaryMode()) { michael@0: StackBaseShape base(obj->lastProperty()); michael@0: base.parent = parent; michael@0: UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); michael@0: if (!nbase) michael@0: return false; michael@0: michael@0: obj->lastProperty()->base()->adoptUnowned(nbase); michael@0: return true; michael@0: } michael@0: michael@0: Shape *newShape = Shape::setObjectParent(cx, parent, obj->getTaggedProto(), obj->shape_); michael@0: if (!newShape) michael@0: return false; michael@0: michael@0: obj->shape_ = newShape; michael@0: return true; michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: Shape::setObjectParent(ExclusiveContext *cx, JSObject *parent, TaggedProto proto, Shape *last) michael@0: { michael@0: if (last->getObjectParent() == parent) michael@0: return last; michael@0: michael@0: StackBaseShape base(last); michael@0: base.parent = parent; michael@0: michael@0: RootedShape lastRoot(cx, last); michael@0: return replaceLastProperty(cx, base, proto, lastRoot); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::setMetadata(JSContext *cx, HandleObject obj, HandleObject metadata) michael@0: { michael@0: if (obj->inDictionaryMode()) { michael@0: StackBaseShape base(obj->lastProperty()); michael@0: base.metadata = metadata; michael@0: UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); michael@0: if (!nbase) michael@0: return false; michael@0: michael@0: obj->lastProperty()->base()->adoptUnowned(nbase); michael@0: return true; michael@0: } michael@0: michael@0: Shape *newShape = Shape::setObjectMetadata(cx, metadata, obj->getTaggedProto(), obj->shape_); michael@0: if (!newShape) michael@0: return false; michael@0: michael@0: obj->shape_ = newShape; michael@0: return true; michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: Shape::setObjectMetadata(JSContext *cx, JSObject *metadata, TaggedProto proto, Shape *last) michael@0: { michael@0: if (last->getObjectMetadata() == metadata) michael@0: return last; michael@0: michael@0: StackBaseShape base(last); michael@0: base.metadata = metadata; michael@0: michael@0: RootedShape lastRoot(cx, last); michael@0: return replaceLastProperty(cx, base, proto, lastRoot); michael@0: } michael@0: michael@0: /* static */ bool michael@0: js::ObjectImpl::preventExtensions(JSContext *cx, Handle obj) michael@0: { michael@0: #ifdef DEBUG michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: MOZ_ASSERT(extensible, michael@0: "Callers must ensure |obj| is extensible before calling " michael@0: "preventExtensions"); michael@0: #endif michael@0: michael@0: if (Downcast(obj)->is()) { michael@0: RootedObject object(cx, obj->asObjectPtr()); michael@0: return js::Proxy::preventExtensions(cx, object); michael@0: } michael@0: michael@0: RootedObject self(cx, obj->asObjectPtr()); michael@0: michael@0: /* michael@0: * Force lazy properties to be resolved by iterating over the objects' own michael@0: * properties. michael@0: */ michael@0: AutoIdVector props(cx); michael@0: if (!js::GetPropertyNames(cx, self, JSITER_HIDDEN | JSITER_OWNONLY, &props)) michael@0: return false; michael@0: michael@0: /* michael@0: * Convert all dense elements to sparse properties. This will shrink the michael@0: * initialized length and capacity of the object to zero and ensure that no michael@0: * new dense elements can be added without calling growElements(), which michael@0: * checks isExtensible(). michael@0: */ michael@0: if (self->isNative() && !JSObject::sparsifyDenseElements(cx, self)) michael@0: return false; michael@0: michael@0: return self->setFlag(cx, BaseShape::NOT_EXTENSIBLE, GENERATE_SHAPE); michael@0: } michael@0: michael@0: bool michael@0: js::ObjectImpl::setFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag_, michael@0: GenerateShape generateShape) michael@0: { michael@0: BaseShape::Flag flag = (BaseShape::Flag) flag_; michael@0: michael@0: if (lastProperty()->getObjectFlags() & flag) michael@0: return true; michael@0: michael@0: Rooted self(cx, this); michael@0: michael@0: if (inDictionaryMode()) { michael@0: if (generateShape == GENERATE_SHAPE && !generateOwnShape(cx)) michael@0: return false; michael@0: StackBaseShape base(self->lastProperty()); michael@0: base.flags |= flag; michael@0: UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); michael@0: if (!nbase) michael@0: return false; michael@0: michael@0: self->lastProperty()->base()->adoptUnowned(nbase); michael@0: return true; michael@0: } michael@0: michael@0: Shape *newShape = michael@0: Shape::setObjectFlag(cx, flag, self->getTaggedProto(), self->lastProperty()); michael@0: if (!newShape) michael@0: return false; michael@0: michael@0: self->shape_ = newShape; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::ObjectImpl::clearFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag) michael@0: { michael@0: JS_ASSERT(inDictionaryMode()); michael@0: JS_ASSERT(lastProperty()->getObjectFlags() & flag); michael@0: michael@0: RootedObject self(cx, this->asObjectPtr()); michael@0: michael@0: StackBaseShape base(self->lastProperty()); michael@0: base.flags &= ~flag; michael@0: UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); michael@0: if (!nbase) michael@0: return false; michael@0: michael@0: self->lastProperty()->base()->adoptUnowned(nbase); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: Shape::setObjectFlag(ExclusiveContext *cx, BaseShape::Flag flag, TaggedProto proto, Shape *last) michael@0: { michael@0: if (last->getObjectFlags() & flag) michael@0: return last; michael@0: michael@0: StackBaseShape base(last); michael@0: base.flags |= flag; michael@0: michael@0: RootedShape lastRoot(cx, last); michael@0: return replaceLastProperty(cx, base, proto, lastRoot); michael@0: } michael@0: michael@0: /* static */ inline HashNumber michael@0: StackBaseShape::hash(const StackBaseShape *base) michael@0: { michael@0: HashNumber hash = base->flags; michael@0: hash = RotateLeft(hash, 4) ^ (uintptr_t(base->clasp) >> 3); michael@0: hash = RotateLeft(hash, 4) ^ (uintptr_t(base->parent) >> 3); michael@0: hash = RotateLeft(hash, 4) ^ (uintptr_t(base->metadata) >> 3); michael@0: hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawGetter); michael@0: hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawSetter); michael@0: return hash; michael@0: } michael@0: michael@0: /* static */ inline bool michael@0: StackBaseShape::match(UnownedBaseShape *key, const StackBaseShape *lookup) michael@0: { michael@0: return key->flags == lookup->flags michael@0: && key->clasp_ == lookup->clasp michael@0: && key->parent == lookup->parent michael@0: && key->metadata == lookup->metadata michael@0: && key->rawGetter == lookup->rawGetter michael@0: && key->rawSetter == lookup->rawSetter; michael@0: } michael@0: michael@0: void michael@0: StackBaseShape::trace(JSTracer *trc) michael@0: { michael@0: if (parent) { michael@0: gc::MarkObjectRoot(trc, (JSObject**)&parent, michael@0: "StackBaseShape parent"); michael@0: } michael@0: if (metadata) { michael@0: gc::MarkObjectRoot(trc, (JSObject**)&metadata, michael@0: "StackBaseShape metadata"); michael@0: } michael@0: if ((flags & BaseShape::HAS_GETTER_OBJECT) && rawGetter) { michael@0: gc::MarkObjectRoot(trc, (JSObject**)&rawGetter, michael@0: "StackBaseShape getter"); michael@0: } michael@0: if ((flags & BaseShape::HAS_SETTER_OBJECT) && rawSetter) { michael@0: gc::MarkObjectRoot(trc, (JSObject**)&rawSetter, michael@0: "StackBaseShape setter"); michael@0: } michael@0: } michael@0: michael@0: /* static */ UnownedBaseShape* michael@0: BaseShape::getUnowned(ExclusiveContext *cx, StackBaseShape &base) michael@0: { michael@0: BaseShapeSet &table = cx->compartment()->baseShapes; michael@0: michael@0: if (!table.initialized() && !table.init()) michael@0: return nullptr; michael@0: michael@0: DependentAddPtr p(cx, table, &base); michael@0: if (p) michael@0: return *p; michael@0: michael@0: RootedGeneric root(cx, &base); michael@0: michael@0: BaseShape *nbase_ = js_NewGCBaseShape(cx); michael@0: if (!nbase_) michael@0: return nullptr; michael@0: michael@0: new (nbase_) BaseShape(*root); michael@0: michael@0: UnownedBaseShape *nbase = static_cast(nbase_); michael@0: michael@0: if (!p.add(cx, table, root, nbase)) michael@0: return nullptr; michael@0: michael@0: return nbase; michael@0: } michael@0: michael@0: /* static */ UnownedBaseShape * michael@0: BaseShape::lookupUnowned(ThreadSafeContext *cx, const StackBaseShape &base) michael@0: { michael@0: BaseShapeSet &table = cx->compartment_->baseShapes; michael@0: michael@0: if (!table.initialized()) michael@0: return nullptr; michael@0: michael@0: BaseShapeSet::Ptr p = table.readonlyThreadsafeLookup(&base); michael@0: return *p; michael@0: } michael@0: michael@0: void michael@0: BaseShape::assertConsistency() michael@0: { michael@0: #ifdef DEBUG michael@0: if (isOwned()) { michael@0: UnownedBaseShape *unowned = baseUnowned(); michael@0: JS_ASSERT(hasGetterObject() == unowned->hasGetterObject()); michael@0: JS_ASSERT(hasSetterObject() == unowned->hasSetterObject()); michael@0: JS_ASSERT_IF(hasGetterObject(), getterObject() == unowned->getterObject()); michael@0: JS_ASSERT_IF(hasSetterObject(), setterObject() == unowned->setterObject()); michael@0: JS_ASSERT(getObjectParent() == unowned->getObjectParent()); michael@0: JS_ASSERT(getObjectMetadata() == unowned->getObjectMetadata()); michael@0: JS_ASSERT(getObjectFlags() == unowned->getObjectFlags()); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: JSCompartment::sweepBaseShapeTable() michael@0: { michael@0: gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats, michael@0: gcstats::PHASE_SWEEP_TABLES_BASE_SHAPE); michael@0: michael@0: if (baseShapes.initialized()) { michael@0: for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) { michael@0: UnownedBaseShape *base = e.front(); michael@0: if (IsBaseShapeAboutToBeFinalized(&base)) michael@0: e.removeFront(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: BaseShape::finalize(FreeOp *fop) michael@0: { michael@0: if (table_) { michael@0: fop->delete_(table_); michael@0: table_ = nullptr; michael@0: } michael@0: } michael@0: michael@0: inline michael@0: InitialShapeEntry::InitialShapeEntry() : shape(nullptr), proto(nullptr) michael@0: { michael@0: } michael@0: michael@0: inline michael@0: InitialShapeEntry::InitialShapeEntry(const ReadBarriered &shape, TaggedProto proto) michael@0: : shape(shape), proto(proto) michael@0: { michael@0: } michael@0: michael@0: inline InitialShapeEntry::Lookup michael@0: InitialShapeEntry::getLookup() const michael@0: { michael@0: return Lookup(shape->getObjectClass(), proto, shape->getObjectParent(), shape->getObjectMetadata(), michael@0: shape->numFixedSlots(), shape->getObjectFlags()); michael@0: } michael@0: michael@0: /* static */ inline HashNumber michael@0: InitialShapeEntry::hash(const Lookup &lookup) michael@0: { michael@0: HashNumber hash = uintptr_t(lookup.clasp) >> 3; michael@0: hash = RotateLeft(hash, 4) ^ michael@0: (uintptr_t(lookup.hashProto.toWord()) >> 3); michael@0: hash = RotateLeft(hash, 4) ^ michael@0: (uintptr_t(lookup.hashParent) >> 3) ^ michael@0: (uintptr_t(lookup.hashMetadata) >> 3); michael@0: return hash + lookup.nfixed; michael@0: } michael@0: michael@0: /* static */ inline bool michael@0: InitialShapeEntry::match(const InitialShapeEntry &key, const Lookup &lookup) michael@0: { michael@0: const Shape *shape = *key.shape.unsafeGet(); michael@0: return lookup.clasp == shape->getObjectClass() michael@0: && lookup.matchProto.toWord() == key.proto.toWord() michael@0: && lookup.matchParent == shape->getObjectParent() michael@0: && lookup.matchMetadata == shape->getObjectMetadata() michael@0: && lookup.nfixed == shape->numFixedSlots() michael@0: && lookup.baseFlags == shape->getObjectFlags(); michael@0: } michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: michael@0: /* michael@0: * This class is used to add a post barrier on the initialShapes set, as the key michael@0: * is calculated based on several objects which may be moved by generational GC. michael@0: */ michael@0: class InitialShapeSetRef : public BufferableRef michael@0: { michael@0: InitialShapeSet *set; michael@0: const Class *clasp; michael@0: TaggedProto proto; michael@0: JSObject *parent; michael@0: JSObject *metadata; michael@0: size_t nfixed; michael@0: uint32_t objectFlags; michael@0: michael@0: public: michael@0: InitialShapeSetRef(InitialShapeSet *set, michael@0: const Class *clasp, michael@0: TaggedProto proto, michael@0: JSObject *parent, michael@0: JSObject *metadata, michael@0: size_t nfixed, michael@0: uint32_t objectFlags) michael@0: : set(set), michael@0: clasp(clasp), michael@0: proto(proto), michael@0: parent(parent), michael@0: metadata(metadata), michael@0: nfixed(nfixed), michael@0: objectFlags(objectFlags) michael@0: {} michael@0: michael@0: void mark(JSTracer *trc) { michael@0: TaggedProto priorProto = proto; michael@0: JSObject *priorParent = parent; michael@0: JSObject *priorMetadata = metadata; michael@0: if (proto.isObject()) michael@0: Mark(trc, reinterpret_cast(&proto), "initialShapes set proto"); michael@0: if (parent) michael@0: Mark(trc, &parent, "initialShapes set parent"); michael@0: if (metadata) michael@0: Mark(trc, &metadata, "initialShapes set metadata"); michael@0: if (proto == priorProto && parent == priorParent && metadata == priorMetadata) michael@0: return; michael@0: michael@0: /* Find the original entry, which must still be present. */ michael@0: InitialShapeEntry::Lookup lookup(clasp, priorProto, michael@0: priorParent, parent, michael@0: priorMetadata, metadata, michael@0: nfixed, objectFlags); michael@0: InitialShapeSet::Ptr p = set->lookup(lookup); michael@0: JS_ASSERT(p); michael@0: michael@0: /* Update the entry's possibly-moved proto, and ensure lookup will still match. */ michael@0: InitialShapeEntry &entry = const_cast(*p); michael@0: entry.proto = proto; michael@0: lookup.matchProto = proto; michael@0: michael@0: /* Rekey the entry. */ michael@0: set->rekeyAs(lookup, michael@0: InitialShapeEntry::Lookup(clasp, proto, parent, metadata, nfixed, objectFlags), michael@0: *p); michael@0: } michael@0: }; michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: void michael@0: JSCompartment::checkInitialShapesTableAfterMovingGC() michael@0: { michael@0: if (!initialShapes.initialized()) michael@0: return; michael@0: michael@0: /* michael@0: * Assert that the postbarriers have worked and that nothing is left in michael@0: * initialShapes that points into the nursery, and that the hash table michael@0: * entries are discoverable. michael@0: */ michael@0: JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtimeFromMainThread()); michael@0: for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) { michael@0: InitialShapeEntry entry = e.front(); michael@0: TaggedProto proto = entry.proto; michael@0: Shape *shape = entry.shape.get(); michael@0: michael@0: JS_ASSERT_IF(proto.isObject(), !IsInsideNursery(rt, proto.toObject())); michael@0: JS_ASSERT(!IsInsideNursery(rt, shape->getObjectParent())); michael@0: JS_ASSERT(!IsInsideNursery(rt, shape->getObjectMetadata())); michael@0: michael@0: InitialShapeEntry::Lookup lookup(shape->getObjectClass(), michael@0: proto, michael@0: shape->getObjectParent(), michael@0: shape->getObjectMetadata(), michael@0: shape->numFixedSlots(), michael@0: shape->getObjectFlags()); michael@0: InitialShapeSet::Ptr ptr = initialShapes.lookup(lookup); michael@0: JS_ASSERT(ptr.found() && &*ptr == &e.front()); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: #endif michael@0: michael@0: /* static */ Shape * michael@0: EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto, michael@0: JSObject *parent, JSObject *metadata, michael@0: size_t nfixed, uint32_t objectFlags) michael@0: { michael@0: JS_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); michael@0: JS_ASSERT_IF(parent, cx->isInsideCurrentCompartment(parent)); michael@0: michael@0: InitialShapeSet &table = cx->compartment()->initialShapes; michael@0: michael@0: if (!table.initialized() && !table.init()) michael@0: return nullptr; michael@0: michael@0: typedef InitialShapeEntry::Lookup Lookup; michael@0: DependentAddPtr michael@0: p(cx, table, Lookup(clasp, proto, parent, metadata, nfixed, objectFlags)); michael@0: if (p) michael@0: return p->shape; michael@0: michael@0: Rooted protoRoot(cx, proto); michael@0: RootedObject parentRoot(cx, parent); michael@0: RootedObject metadataRoot(cx, metadata); michael@0: michael@0: StackBaseShape base(cx, clasp, parent, metadata, objectFlags); michael@0: Rooted nbase(cx, BaseShape::getUnowned(cx, base)); michael@0: if (!nbase) michael@0: return nullptr; michael@0: michael@0: Shape *shape = cx->compartment()->propertyTree.newShape(cx); michael@0: if (!shape) michael@0: return nullptr; michael@0: new (shape) EmptyShape(nbase, nfixed); michael@0: michael@0: Lookup lookup(clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags); michael@0: if (!p.add(cx, table, lookup, InitialShapeEntry(shape, protoRoot))) michael@0: return nullptr; michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (cx->hasNursery()) { michael@0: if ((protoRoot.isObject() && cx->nursery().isInside(protoRoot.toObject())) || michael@0: cx->nursery().isInside(parentRoot.get()) || michael@0: cx->nursery().isInside(metadataRoot.get())) michael@0: { michael@0: InitialShapeSetRef ref( michael@0: &table, clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags); michael@0: cx->asJSContext()->runtime()->gcStoreBuffer.putGeneric(ref); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: return shape; michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto, michael@0: JSObject *parent, JSObject *metadata, michael@0: AllocKind kind, uint32_t objectFlags) michael@0: { michael@0: return getInitialShape(cx, clasp, proto, parent, metadata, GetGCKindSlots(kind, clasp), objectFlags); michael@0: } michael@0: michael@0: void michael@0: NewObjectCache::invalidateEntriesForShape(JSContext *cx, HandleShape shape, HandleObject proto) michael@0: { michael@0: const Class *clasp = shape->getObjectClass(); michael@0: michael@0: gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); michael@0: if (CanBeFinalizedInBackground(kind, clasp)) michael@0: kind = GetBackgroundAllocKind(kind); michael@0: michael@0: Rooted global(cx, &shape->getObjectParent()->global()); michael@0: Rooted type(cx, cx->getNewType(clasp, proto.get())); michael@0: michael@0: EntryIndex entry; michael@0: if (lookupGlobal(clasp, global, kind, &entry)) michael@0: PodZero(&entries[entry]); michael@0: if (!proto->is() && lookupProto(clasp, proto, kind, &entry)) michael@0: PodZero(&entries[entry]); michael@0: if (lookupType(type, kind, &entry)) michael@0: PodZero(&entries[entry]); michael@0: } michael@0: michael@0: /* static */ void michael@0: EmptyShape::insertInitialShape(ExclusiveContext *cx, HandleShape shape, HandleObject proto) michael@0: { michael@0: InitialShapeEntry::Lookup lookup(shape->getObjectClass(), TaggedProto(proto), michael@0: shape->getObjectParent(), shape->getObjectMetadata(), michael@0: shape->numFixedSlots(), shape->getObjectFlags()); michael@0: michael@0: InitialShapeSet::Ptr p = cx->compartment()->initialShapes.lookup(lookup); michael@0: JS_ASSERT(p); michael@0: michael@0: InitialShapeEntry &entry = const_cast(*p); michael@0: michael@0: /* The new shape had better be rooted at the old one. */ michael@0: #ifdef DEBUG michael@0: Shape *nshape = shape; michael@0: while (!nshape->isEmptyShape()) michael@0: nshape = nshape->previous(); michael@0: JS_ASSERT(nshape == entry.shape); michael@0: #endif michael@0: michael@0: entry.shape = shape.get(); michael@0: michael@0: /* michael@0: * This affects the shape that will be produced by the various NewObject michael@0: * methods, so clear any cache entry referring to the old shape. This is michael@0: * not required for correctness: the NewObject must always check for a michael@0: * nativeEmpty() result and generate the appropriate properties if found. michael@0: * Clearing the cache entry avoids this duplicate regeneration. michael@0: * michael@0: * Clearing is not necessary when this context is running off the main michael@0: * thread, as it will not use the new object cache for allocations. michael@0: */ michael@0: if (cx->isJSContext()) { michael@0: JSContext *ncx = cx->asJSContext(); michael@0: ncx->runtime()->newObjectCache.invalidateEntriesForShape(ncx, shape, proto); michael@0: } michael@0: } michael@0: michael@0: void michael@0: JSCompartment::sweepInitialShapeTable() michael@0: { michael@0: gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats, michael@0: gcstats::PHASE_SWEEP_TABLES_INITIAL_SHAPE); michael@0: michael@0: if (initialShapes.initialized()) { michael@0: for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) { michael@0: const InitialShapeEntry &entry = e.front(); michael@0: Shape *shape = entry.shape; michael@0: JSObject *proto = entry.proto.raw(); michael@0: if (IsShapeAboutToBeFinalized(&shape) || (entry.proto.isObject() && IsObjectAboutToBeFinalized(&proto))) { michael@0: e.removeFront(); michael@0: } else { michael@0: #ifdef DEBUG michael@0: DebugOnly parent = shape->getObjectParent(); michael@0: JS_ASSERT(!parent || !IsObjectAboutToBeFinalized(&parent)); michael@0: JS_ASSERT(parent == shape->getObjectParent()); michael@0: #endif michael@0: if (shape != entry.shape || proto != entry.proto.raw()) { michael@0: InitialShapeEntry newKey(shape, proto); michael@0: e.rekeyFront(newKey.getLookup(), newKey); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: AutoRooterGetterSetter::Inner::trace(JSTracer *trc) michael@0: { michael@0: if ((attrs & JSPROP_GETTER) && *pgetter) michael@0: gc::MarkObjectRoot(trc, (JSObject**) pgetter, "AutoRooterGetterSetter getter"); michael@0: if ((attrs & JSPROP_SETTER) && *psetter) michael@0: gc::MarkObjectRoot(trc, (JSObject**) psetter, "AutoRooterGetterSetter setter"); michael@0: }