blob: 54a9cc359384f312f1045fc33201c24d624d3df1 [file] [log] [blame]
/* -*- 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 "js/HashTable.h"
#include "jscntxtinlines.h"
#include "jscompartmentinlines.h"
#include "jsobjinlines.h"
#include "vm/NativeObject-inl.h"
#include "vm/Runtime-inl.h"
using namespace js;
using namespace js::gc;
using mozilla::CeilingLog2Size;
using mozilla::DebugOnly;
using mozilla::PodZero;
using mozilla::RotateLeft;
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;
/*
* Use rt->calloc for memory accounting and overpressure handling
* without OOM reporting. See ShapeTable::change.
*/
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 = search<true>(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;
}
void
Shape::insertIntoDictionary(HeapPtrShape* 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, compartment() == (*dictp)->compartment());
setParent(dictp->get());
if (parent)
parent->listp = &parent;
listp = (HeapPtrShape*) dictp;
*dictp = this;
}
bool
Shape::makeOwnBaseShape(ExclusiveContext* cx)
{
MOZ_ASSERT(!base()->isOwned());
assertSameCompartmentDebugOnly(cx, compartment());
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 <bool adding>
ShapeTable::Entry&
ShapeTable::search(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);
#ifdef DEBUG
bool collisionFlag = true;
#endif
/* Save the first removed entry pointer so we can recycle it if adding. */
Entry* firstRemoved;
if (entry->isRemoved()) {
firstRemoved = entry;
} else {
firstRemoved = nullptr;
if (adding && !entry->hadCollision())
entry->flagCollision();
#ifdef DEBUG
collisionFlag &= entry->hadCollision();
#endif
}
while (true) {
hash1 -= hash2;
hash1 &= sizeMask;
entry = &getEntry(hash1);
if (entry->isFree())
return (adding && firstRemoved) ? *firstRemoved : *entry;
shape = entry->shape();
if (shape && shape->propidRaw() == id) {
MOZ_ASSERT(collisionFlag);
return *entry;
}
if (entry->isRemoved()) {
if (!firstRemoved)
firstRemoved = entry;
} else {
if (adding && !entry->hadCollision())
entry->flagCollision();
#ifdef DEBUG
collisionFlag &= entry->hadCollision();
#endif
}
}
MOZ_CRASH("Shape::search failed to find an expected entry.");
}
template ShapeTable::Entry& ShapeTable::search<false>(jsid id);
template ShapeTable::Entry& ShapeTable::search<true>(jsid id);
bool
ShapeTable::change(int log2Delta, ExclusiveContext* cx)
{
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->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. */
for (Entry* oldEntry = oldTable; oldSize != 0; oldEntry++) {
if (Shape* shape = oldEntry->shape()) {
Entry& entry = search<true>(shape->propid());
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(delta, cx)) {
if (entryCount_ + removedCount_ == size - 1)
return false;
cx->recoverFromOutOfMemory();
}
return true;
}
/* 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->compartment()->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->compartment()->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());
/* We allocate the shapes from cx->compartment(), so make sure it's right. */
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;
}
HeapPtrShape* 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;
}
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;
}
ShapeTable::Entry* entry = nullptr;
if (obj->inDictionaryMode())
entry = &obj->lastProperty()->table().search<true>(id);
return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, flags, entry,
allowDictionary);
}
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)
{
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()->table();
entry = &table->search<true>(id);
}
} else {
table = &obj->lastProperty()->table();
if (table->needsToGrow()) {
if (!table->grow(cx))
return nullptr;
entry = &table->search<true>(id);
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->table() == 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->compartment()->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.
*/
ShapeTable::Entry* entry;
RootedShape shape(cx, Shape::search<true>(cx, obj->lastProperty(), id, &entry));
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);
}
/* 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;
entry = &obj->lastProperty()->table().search(shape->propid());
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(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);
ShapeTable::Entry* entry;
RootedShape shape(cx, Shape::search(cx, lastProperty(), id, &entry));
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;
entry = &self->lastProperty()->table().search(shape->propid());
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(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()->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(-1, cx);
} 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->isInsideCurrentCompartment(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;
}
ShapeTable& table = self->lastProperty()->table();
ShapeTable::Entry* entry = oldShape->isEmptyShape()
? nullptr
: &table.search(oldShape->propidRef());
/*
* 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->getTaggedProto(), existingShape);
if (!newShape)
return false;
self->setShapeMaybeNonNative(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),
compartment_(base.compartment),
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.compartment_ = src.compartment_;
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();
ShapeTable* table = &this->table();
BaseShape::copyFromUnowned(*this, *other);
setTable(table);
setSlotSpan(span);
assertConsistency();
}
/* static */ UnownedBaseShape*
BaseShape::getUnowned(ExclusiveContext* cx, StackBaseShape& base)
{
BaseShapeSet& table = cx->compartment()->baseShapes;
if (!table.initialized() && !table.init()) {
ReportOutOfMemory(cx);
return nullptr;
}
DependentAddPtr<BaseShapeSet> p(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)
{
assertConsistency();
if (trc->isMarkingTracer())
compartment()->mark();
if (isOwned())
TraceEdge(trc, &unowned_, "base");
JSObject* global = compartment()->unsafeUnbarrieredMaybeGlobal();
if (global)
TraceManuallyBarrieredEdge(trc, &global, "global");
}
void
JSCompartment::sweepBaseShapeTable()
{
if (!baseShapes.initialized())
return;
for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) {
UnownedBaseShape* base = e.front().unbarrieredGet();
if (IsAboutToBeFinalizedUnbarriered(&base)) {
e.removeFront();
} else if (base != e.front().unbarrieredGet()) {
ReadBarriered<UnownedBaseShape*> b(base);
e.rekeyFront(base, b);
}
}
}
#ifdef JSGC_HASH_TABLE_CHECKS
void
JSCompartment::checkBaseShapeTableAfterMovingGC()
{
if (!baseShapes.initialized())
return;
for (BaseShapeSet::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(nullptr)
{
}
inline
InitialShapeEntry::InitialShapeEntry(const ReadBarrieredShape& shape, TaggedProto proto)
: shape(shape), proto(proto)
{
}
inline InitialShapeEntry::Lookup
InitialShapeEntry::getLookup() const
{
return Lookup(shape->getObjectClass(), proto, shape->numFixedSlots(), shape->getObjectFlags());
}
/* static */ inline HashNumber
InitialShapeEntry::hash(const Lookup& lookup)
{
HashNumber hash = uintptr_t(lookup.clasp) >> 3;
hash = RotateLeft(hash, 4) ^
(uintptr_t(lookup.hashProto.toWord()) >> 3);
return hash + lookup.nfixed;
}
/* static */ inline bool
InitialShapeEntry::match(const InitialShapeEntry& key, const Lookup& lookup)
{
const Shape* shape = *key.shape.unsafeGet();
return lookup.clasp == shape->getObjectClass()
&& lookup.matchProto.toWord() == key.proto.toWord()
&& lookup.nfixed == shape->numFixedSlots()
&& lookup.baseFlags == shape->getObjectFlags();
}
/*
* This class is used to add a post barrier on the initialShapes set, as the key
* is calculated based on objects which may be moved by generational GC.
*/
class InitialShapeSetRef : public BufferableRef
{
InitialShapeSet* set;
const Class* clasp;
TaggedProto proto;
size_t nfixed;
uint32_t objectFlags;
public:
InitialShapeSetRef(InitialShapeSet* set,
const Class* clasp,
TaggedProto proto,
size_t nfixed,
uint32_t objectFlags)
: set(set),
clasp(clasp),
proto(proto),
nfixed(nfixed),
objectFlags(objectFlags)
{}
void trace(JSTracer* trc) override {
TaggedProto priorProto = proto;
if (proto.isObject()) {
TraceManuallyBarrieredEdge(trc, reinterpret_cast<JSObject**>(&proto),
"initialShapes set proto");
}
if (proto == priorProto)
return;
/* Find the original entry, which must still be present. */
InitialShapeEntry::Lookup lookup(clasp, priorProto, nfixed, objectFlags);
InitialShapeSet::Ptr p = set->lookup(lookup);
MOZ_ASSERT(p);
/* Update the entry's possibly-moved proto, and ensure lookup will still match. */
InitialShapeEntry& entry = const_cast<InitialShapeEntry&>(*p);
entry.proto = proto;
lookup.matchProto = proto;
/* Rekey the entry. */
set->rekeyAs(lookup,
InitialShapeEntry::Lookup(clasp, proto, nfixed, objectFlags),
*p);
}
};
#ifdef JSGC_HASH_TABLE_CHECKS
void
JSCompartment::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 (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
InitialShapeEntry entry = e.front();
TaggedProto proto = entry.proto;
Shape* shape = entry.shape.unbarrieredGet();
if (proto.isObject())
CheckGCThingAfterMovingGC(proto.toObject());
InitialShapeEntry::Lookup lookup(shape->getObjectClass(),
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 */ 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()));
InitialShapeSet& table = cx->compartment()->initialShapes;
if (!table.initialized() && !table.init()) {
ReportOutOfMemory(cx);
return nullptr;
}
typedef InitialShapeEntry::Lookup Lookup;
DependentAddPtr<InitialShapeSet>
p(cx, table, Lookup(clasp, proto, nfixed, objectFlags));
if (p)
return p->shape;
Rooted<TaggedProto> protoRoot(cx, proto);
StackBaseShape base(cx, clasp, objectFlags);
Rooted<UnownedBaseShape*> nbase(cx, BaseShape::getUnowned(cx, base));
if (!nbase)
return nullptr;
Shape* shape = EmptyShape::new_(cx, nbase, nfixed);
if (!shape)
return nullptr;
Lookup lookup(clasp, protoRoot, nfixed, objectFlags);
if (!p.add(cx, table, lookup, InitialShapeEntry(ReadBarrieredShape(shape), protoRoot)))
return nullptr;
// Post-barrier for the initial shape table update.
if (cx->isJSContext()) {
if (protoRoot.isObject() && IsInsideNursery(protoRoot.toObject())) {
InitialShapeSetRef ref(&table, clasp, protoRoot, nfixed, objectFlags);
cx->asJSContext()->runtime()->gc.storeBuffer.putGeneric(ref);
}
}
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);
Rooted<GlobalObject*> global(cx, shape->compartment()->unsafeUnbarrieredMaybeGlobal());
RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp, TaggedProto(proto)));
if (!group) {
purge();
cx->recoverFromOutOfMemory();
return;
}
EntryIndex entry;
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)
{
InitialShapeEntry::Lookup lookup(shape->getObjectClass(), TaggedProto(proto),
shape->numFixedSlots(), shape->getObjectFlags());
InitialShapeSet::Ptr p = cx->compartment()->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);
/*
* 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->runtime()->newObjectCache.invalidateEntriesForShape(ncx, shape, proto);
}
}
void
JSCompartment::sweepInitialShapeTable()
{
if (initialShapes.initialized()) {
for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
const InitialShapeEntry& entry = e.front();
Shape* shape = entry.shape.unbarrieredGet();
JSObject* proto = entry.proto.raw();
if (IsAboutToBeFinalizedUnbarriered(&shape) ||
(entry.proto.isObject() && IsAboutToBeFinalizedUnbarriered(&proto)))
{
e.removeFront();
} else {
if (shape != entry.shape.unbarrieredGet() || proto != entry.proto.raw()) {
ReadBarrieredShape readBarrieredShape(shape);
InitialShapeEntry newKey(readBarrieredShape, TaggedProto(proto));
e.rekeyFront(newKey.getLookup(), newKey);
}
}
}
}
}
void
JSCompartment::fixupInitialShapeTable()
{
if (!initialShapes.initialized())
return;
for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
InitialShapeEntry entry = e.front();
bool needRekey = false;
if (IsForwarded(entry.shape.unbarrieredGet())) {
entry.shape.set(Forwarded(entry.shape.unbarrieredGet()));
needRekey = true;
}
if (entry.proto.isObject() && IsForwarded(entry.proto.toObject())) {
entry.proto = TaggedProto(Forwarded(entry.proto.toObject()));
needRekey = true;
}
if (needRekey) {
InitialShapeEntry::Lookup relookup(entry.shape.unbarrieredGet()->getObjectClass(),
entry.proto,
entry.shape.unbarrieredGet()->numFixedSlots(),
entry.shape.unbarrieredGet()->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());
if (get().hasTable())
size += get().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());
}