blob: d1f5ed9fb9a021f3616c152986ee38d4924fcdb8 [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/. */
#ifndef vm_Shape_h
#define vm_Shape_h
#include "mozilla/Attributes.h"
#include "mozilla/GuardObjects.h"
#include "jsobj.h"
#include "jspropertytree.h"
#include "jstypes.h"
#include "gc/Heap.h"
#include "js/HashTable.h"
#include "js/RootingAPI.h"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4800)
#pragma warning(push)
#pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
#endif
/*
* In isolation, a Shape represents a property that exists in one or more
* objects; it has an id, flags, etc. (But it doesn't represent the property's
* value.) However, Shapes are always stored in linked linear sequence of
* Shapes, called "shape lineages". Each shape lineage represents the layout of
* an entire object.
*
* Every JSObject has a pointer, |shape_|, accessible via lastProperty(), to
* the last Shape in a shape lineage, which identifies the property most
* recently added to the object. This pointer permits fast object layout
* tests. The shape lineage order also dictates the enumeration order for the
* object; ECMA requires no particular order but this implementation has
* promised and delivered property definition order.
*
* Shape lineages occur in two kinds of data structure.
*
* 1. N-ary property trees. Each path from a non-root node to the root node in
* a property tree is a shape lineage. Property trees permit full (or
* partial) sharing of Shapes between objects that have fully (or partly)
* identical layouts. The root is an EmptyShape whose identity is determined
* by the object's class, compartment and prototype. These Shapes are shared
* and immutable.
*
* 2. Dictionary mode lists. Shapes in such lists are said to be "in
* dictionary mode", as are objects that point to such Shapes. These Shapes
* are unshared, private to a single object, and immutable except for their
* links in the dictionary list.
*
* All shape lineages are bi-directionally linked, via the |parent| and
* |kids|/|listp| members.
*
* Shape lineages start out life in the property tree. They can be converted
* (by copying) to dictionary mode lists in the following circumstances.
*
* 1. The shape lineage's size reaches MAX_HEIGHT. This reasonable limit avoids
* potential worst cases involving shape lineage mutations.
*
* 2. A property represented by a non-last Shape in a shape lineage is removed
* from an object. (In the last Shape case, obj->shape_ can be easily
* adjusted to point to obj->shape_->parent.) We originally tried lazy
* forking of the property tree, but this blows up for delete/add
* repetitions.
*
* 3. A property represented by a non-last Shape in a shape lineage has its
* attributes modified.
*
* To find the Shape for a particular property of an object initially requires
* a linear search. But if the number of searches starting at any particular
* Shape in the property tree exceeds MAX_LINEAR_SEARCHES and the Shape's
* lineage has (excluding the EmptyShape) at least MIN_ENTRIES, we create an
* auxiliary hash table -- the ShapeTable -- that allows faster lookup.
* Furthermore, a ShapeTable is always created for dictionary mode lists,
* and it is attached to the last Shape in the lineage. Shape tables for
* property tree Shapes never change, but shape tables for dictionary mode
* Shapes can grow and shrink.
*
* There used to be a long, math-heavy comment here explaining why property
* trees are more space-efficient than alternatives. This was removed in bug
* 631138; see that bug for the full details.
*
* Because many Shapes have similar data, there is actually a secondary type
* called a BaseShape that holds some of a Shape's data. Many shapes can share
* a single BaseShape.
*/
class JSObject;
namespace js {
class Bindings;
class Nursery;
class StaticBlockObject;
/* Limit on the number of slotful properties in an object. */
static const uint32_t SHAPE_INVALID_SLOT = JS_BIT(24) - 1;
static const uint32_t SHAPE_MAXIMUM_SLOT = JS_BIT(24) - 2;
/*
* Shapes use multiplicative hashing, but specialized to
* minimize footprint.
*/
struct ShapeTable {
static const uint32_t HASH_BITS = tl::BitSize<HashNumber>::result;
static const uint32_t MIN_ENTRIES = 7;
static const uint32_t MIN_SIZE_LOG2 = 4;
static const uint32_t MIN_SIZE = JS_BIT(MIN_SIZE_LOG2);
int hashShift; /* multiplicative hash shift */
uint32_t entryCount; /* number of entries in table */
uint32_t removedCount; /* removed entry sentinels in table */
uint32_t freelist; /* SHAPE_INVALID_SLOT or head of slot
freelist in owning dictionary-mode
object */
js::Shape **entries; /* table of ptrs to shared tree nodes */
ShapeTable(uint32_t nentries)
: hashShift(HASH_BITS - MIN_SIZE_LOG2),
entryCount(nentries),
removedCount(0),
freelist(SHAPE_INVALID_SLOT)
{
/* NB: entries is set by init, which must be called. */
}
~ShapeTable() {
js_free(entries);
}
/* By definition, hashShift = HASH_BITS - log2(capacity). */
uint32_t capacity() const { return JS_BIT(HASH_BITS - hashShift); }
/* Computes the size of the entries array for a given capacity. */
static size_t sizeOfEntries(size_t cap) { return cap * sizeof(Shape *); }
/*
* This counts the ShapeTable object itself (which must be
* heap-allocated) and its |entries| array.
*/
size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const {
return mallocSizeOf(this) + mallocSizeOf(entries);
}
/* Whether we need to grow. We want to do this if the load factor is >= 0.75 */
bool needsToGrow() const {
uint32_t size = capacity();
return entryCount + removedCount >= size - (size >> 2);
}
/*
* Try to grow the table. On failure, reports out of memory on cx
* and returns false. This will make any extant pointers into the
* table invalid. Don't call this unless needsToGrow() is true.
*/
bool grow(JSContext *cx);
/*
* NB: init and change are fallible but do not report OOM, so callers can
* cope or ignore. They do however use JSRuntime's calloc_ method in order
* to update the malloc counter on success.
*/
bool init(JSRuntime *rt, Shape *lastProp);
bool change(int log2Delta, JSContext *cx);
Shape **search(jsid id, bool adding);
};
/*
* Reuse the API-only JSPROP_INDEX attribute to mean shadowability.
*/
#define JSPROP_SHADOWABLE JSPROP_INDEX
/*
* Shapes encode information about both a property lineage *and* a particular
* property. This information is split across the Shape and the BaseShape
* at shape->base(). Both Shape and BaseShape can be either owned or unowned
* by, respectively, the Object or Shape referring to them.
*
* Owned Shapes are used in dictionary objects, and form a doubly linked list
* whose entries are all owned by that dictionary. Unowned Shapes are all in
* the property tree.
*
* Owned BaseShapes are used for shapes which have shape tables, including
* the last properties in all dictionaries. Unowned BaseShapes compactly store
* information common to many shapes. In a given compartment there is a single
* BaseShape for each combination of BaseShape information. This information
* is cloned in owned BaseShapes so that information can be quickly looked up
* for a given object or shape without regard to whether the base shape is
* owned or not.
*
* All combinations of owned/unowned Shapes/BaseShapes are possible:
*
* Owned Shape, Owned BaseShape:
*
* Last property in a dictionary object. The BaseShape is transferred from
* property to property as the object's last property changes.
*
* Owned Shape, Unowned BaseShape:
*
* Property in a dictionary object other than the last one.
*
* Unowned Shape, Owned BaseShape:
*
* Property in the property tree which has a shape table.
*
* Unowned Shape, Unowned BaseShape:
*
* Property in the property tree which does not have a shape table.
*
* BaseShapes additionally encode some information about the referring object
* itself. This includes the object's class, parent and various flags that may
* be set for the object. Except for the class, this information is mutable and
* may change when the object has an established property lineage. On such
* changes the entire property lineage is not updated, but rather only the
* last property (and its base shape). This works because only the object's
* last property is used to query information about the object. Care must be
* taken to call JSObject::canRemoveLastProperty when unwinding an object to
* an earlier property, however.
*/
class Shape;
class UnownedBaseShape;
struct StackBaseShape;
class BaseShape : public js::gc::Cell
{
public:
friend class Shape;
friend struct StackBaseShape;
friend struct StackShape;
enum Flag {
/* Owned by the referring shape. */
OWNED_SHAPE = 0x1,
/* getterObj/setterObj are active in unions below. */
HAS_GETTER_OBJECT = 0x2,
HAS_SETTER_OBJECT = 0x4,
/*
* Flags set which describe the referring object. Once set these cannot
* be unset (except during object densification of sparse indexes), and
* are transferred from shape to shape as the object's last property
* changes.
*/
DELEGATE = 0x8,
NOT_EXTENSIBLE = 0x10,
INDEXED = 0x20,
BOUND_FUNCTION = 0x40,
VAROBJ = 0x80,
WATCHED = 0x100,
ITERATED_SINGLETON = 0x200,
NEW_TYPE_UNKNOWN = 0x400,
UNCACHEABLE_PROTO = 0x800,
HAD_ELEMENTS_ACCESS = 0x1000,
OBJECT_FLAG_MASK = 0x1ff8
};
private:
Class *clasp; /* Class of referring object. */
HeapPtrObject parent; /* Parent of referring object. */
HeapPtrObject metadata; /* Optional holder of metadata about
* the referring object. */
JSCompartment *compartment_; /* Compartment shape belongs to. */
uint32_t flags; /* Vector of above flags. */
uint32_t slotSpan_; /* Object slot span for BaseShapes at
* dictionary last properties. */
union {
PropertyOp rawGetter; /* getter hook for shape */
JSObject *getterObj; /* user-defined callable "get" object or
null if shape->hasGetterValue() */
};
union {
StrictPropertyOp rawSetter; /* setter hook for shape */
JSObject *setterObj; /* user-defined callable "set" object or
null if shape->hasSetterValue() */
};
/* For owned BaseShapes, the canonical unowned BaseShape. */
HeapPtr<UnownedBaseShape> unowned_;
/* For owned BaseShapes, the shape's shape table. */
ShapeTable *table_;
BaseShape(const BaseShape &base) MOZ_DELETE;
public:
void finalize(FreeOp *fop);
inline BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, JSObject *metadata,
uint32_t objectFlags);
inline BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, JSObject *metadata,
uint32_t objectFlags, uint8_t attrs,
PropertyOp rawGetter, StrictPropertyOp rawSetter);
inline BaseShape(const StackBaseShape &base);
/* Not defined: BaseShapes must not be stack allocated. */
~BaseShape();
inline BaseShape &operator=(const BaseShape &other);
bool isOwned() const { return !!(flags & OWNED_SHAPE); }
inline bool matchesGetterSetter(PropertyOp rawGetter,
StrictPropertyOp rawSetter) const;
inline void adoptUnowned(UnownedBaseShape *other);
inline void setOwned(UnownedBaseShape *unowned);
JSObject *getObjectParent() const { return parent; }
JSObject *getObjectMetadata() const { return metadata; }
uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; }
bool hasGetterObject() const { return !!(flags & HAS_GETTER_OBJECT); }
JSObject *getterObject() const { JS_ASSERT(hasGetterObject()); return getterObj; }
bool hasSetterObject() const { return !!(flags & HAS_SETTER_OBJECT); }
JSObject *setterObject() const { JS_ASSERT(hasSetterObject()); return setterObj; }
bool hasTable() const { JS_ASSERT_IF(table_, isOwned()); return table_ != NULL; }
ShapeTable &table() const { JS_ASSERT(table_ && isOwned()); return *table_; }
void setTable(ShapeTable *table) { JS_ASSERT(isOwned()); table_ = table; }
uint32_t slotSpan() const { JS_ASSERT(isOwned()); return slotSpan_; }
void setSlotSpan(uint32_t slotSpan) { JS_ASSERT(isOwned()); slotSpan_ = slotSpan; }
JSCompartment *compartment() const { return compartment_; }
JS::Zone *zone() const { return tenuredZone(); }
/* Lookup base shapes from the compartment's baseShapes table. */
static UnownedBaseShape* getUnowned(JSContext *cx, const StackBaseShape &base);
/* Get the canonical base shape. */
inline UnownedBaseShape* unowned();
/* Get the canonical base shape for an owned one. */
inline UnownedBaseShape* baseUnowned();
/* Get the canonical base shape for an unowned one (i.e. identity). */
inline UnownedBaseShape* toUnowned();
/* Check that an owned base shape is consistent with its unowned base. */
inline void assertConsistency();
/* For JIT usage */
static inline size_t offsetOfParent() { return offsetof(BaseShape, parent); }
static inline size_t offsetOfFlags() { return offsetof(BaseShape, flags); }
static inline void writeBarrierPre(BaseShape *shape);
static inline void writeBarrierPost(BaseShape *shape, void *addr);
static inline void readBarrier(BaseShape *shape);
static inline ThingRootKind rootKind() { return THING_ROOT_BASE_SHAPE; }
inline void markChildren(JSTracer *trc);
private:
static void staticAsserts() {
JS_STATIC_ASSERT(offsetof(BaseShape, clasp) == offsetof(js::shadow::BaseShape, clasp));
}
};
class UnownedBaseShape : public BaseShape {};
UnownedBaseShape *
BaseShape::unowned()
{
return isOwned() ? baseUnowned() : toUnowned();
}
UnownedBaseShape *
BaseShape::toUnowned()
{
JS_ASSERT(!isOwned() && !unowned_); return static_cast<UnownedBaseShape *>(this);
}
UnownedBaseShape*
BaseShape::baseUnowned()
{
JS_ASSERT(isOwned() && unowned_); return unowned_;
}
/* Entries for the per-compartment baseShapes set of unowned base shapes. */
struct StackBaseShape
{
typedef const StackBaseShape *Lookup;
uint32_t flags;
Class *clasp;
JSObject *parent;
JSObject *metadata;
PropertyOp rawGetter;
StrictPropertyOp rawSetter;
JSCompartment *compartment;
explicit StackBaseShape(BaseShape *base)
: flags(base->flags & BaseShape::OBJECT_FLAG_MASK),
clasp(base->clasp),
parent(base->parent),
metadata(base->metadata),
rawGetter(NULL),
rawSetter(NULL),
compartment(base->compartment())
{}
StackBaseShape(JSCompartment *comp, Class *clasp,
JSObject *parent, JSObject *metadata, uint32_t objectFlags)
: flags(objectFlags),
clasp(clasp),
parent(parent),
metadata(metadata),
rawGetter(NULL),
rawSetter(NULL),
compartment(comp)
{}
inline StackBaseShape(Shape *shape);
inline void updateGetterSetter(uint8_t attrs,
PropertyOp rawGetter,
StrictPropertyOp rawSetter);
static inline HashNumber hash(const StackBaseShape *lookup);
static inline bool match(UnownedBaseShape *key, const StackBaseShape *lookup);
class AutoRooter : private JS::CustomAutoRooter
{
public:
explicit AutoRooter(JSContext *cx, const StackBaseShape *base_
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: CustomAutoRooter(cx), base(base_), skip(cx, base_)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
private:
virtual void trace(JSTracer *trc);
const StackBaseShape *base;
SkipRoot skip;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
};
typedef HashSet<ReadBarriered<UnownedBaseShape>,
StackBaseShape,
SystemAllocPolicy> BaseShapeSet;
class Shape : public js::gc::Cell
{
friend class ::JSObject;
friend class ::JSFunction;
friend class js::Bindings;
friend class js::Nursery;
friend class js::ObjectImpl;
friend class js::PropertyTree;
friend class js::StaticBlockObject;
friend struct js::StackShape;
friend struct js::StackBaseShape;
protected:
HeapPtrBaseShape base_;
EncapsulatedId propid_;
JS_ENUM_HEADER(SlotInfo, uint32_t)
{
/* Number of fixed slots in objects with this shape. */
FIXED_SLOTS_MAX = 0x1f,
FIXED_SLOTS_SHIFT = 27,
FIXED_SLOTS_MASK = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT),
/*
* numLinearSearches starts at zero and is incremented initially on
* search() calls. Once numLinearSearches reaches LINEAR_SEARCHES_MAX,
* the table is created on the next search() call. The table can also
* be created when hashifying for dictionary mode.
*/
LINEAR_SEARCHES_MAX = 0x7,
LINEAR_SEARCHES_SHIFT = 24,
LINEAR_SEARCHES_MASK = LINEAR_SEARCHES_MAX << LINEAR_SEARCHES_SHIFT,
/*
* Mask to get the index in object slots for shapes which hasSlot().
* For !hasSlot() shapes in the property tree with a parent, stores the
* parent's slot index (which may be invalid), and invalid for all
* other shapes.
*/
SLOT_MASK = JS_BIT(24) - 1
} JS_ENUM_FOOTER(SlotInfo);
uint32_t slotInfo; /* mask of above info */
uint8_t attrs; /* attributes, see jsapi.h JSPROP_* */
uint8_t flags; /* flags, see below for defines */
int16_t shortid_; /* tinyid, or local arg/var index */
HeapPtrShape parent; /* parent node, reverse for..in order */
/* kids is valid when !inDictionary(), listp is valid when inDictionary(). */
union {
KidsPointer kids; /* null, single child, or a tagged ptr
to many-kids data structure */
HeapPtrShape *listp; /* dictionary list starting at shape_
has a double-indirect back pointer,
either to the next shape's parent if not
last, else to obj->shape_ */
};
static inline Shape *search(JSContext *cx, Shape *start, jsid id,
Shape ***pspp, bool adding = false);
static inline Shape *searchNoHashify(Shape *start, jsid id);
inline void removeFromDictionary(ObjectImpl *obj);
inline void insertIntoDictionary(HeapPtrShape *dictp);
inline void initDictionaryShape(const StackShape &child, uint32_t nfixed,
HeapPtrShape *dictp);
Shape *getChildBinding(JSContext *cx, const StackShape &child);
/* Replace the base shape of the last shape in a non-dictionary lineage with base. */
static Shape *replaceLastProperty(JSContext *cx, const StackBaseShape &base,
TaggedProto proto, HandleShape shape);
static bool hashify(JSContext *cx, Shape *shape);
void handoffTableTo(Shape *newShape);
inline void setParent(Shape *p);
bool ensureOwnBaseShape(JSContext *cx) {
if (base()->isOwned())
return true;
return makeOwnBaseShape(cx);
}
bool makeOwnBaseShape(JSContext *cx);
public:
bool hasTable() const { return base()->hasTable(); }
ShapeTable &table() const { return base()->table(); }
void sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf,
size_t *propTableSize, size_t *kidsSize) const {
*propTableSize = hasTable() ? table().sizeOfIncludingThis(mallocSizeOf) : 0;
*kidsSize = !inDictionary() && kids.isHash()
? kids.toHash()->sizeOfIncludingThis(mallocSizeOf)
: 0;
}
bool isNative() const {
JS_ASSERT(!(flags & NON_NATIVE) == getObjectClass()->isNative());
return !(flags & NON_NATIVE);
}
const HeapPtrShape &previous() const { return parent; }
JSCompartment *compartment() const { return base()->compartment(); }
template <AllowGC allowGC>
class Range {
protected:
friend class Shape;
typename MaybeRooted<Shape*, allowGC>::RootType cursor;
public:
Range(JSContext *cx, Shape *shape) : cursor(cx, shape) {
JS_STATIC_ASSERT(allowGC == CanGC);
}
Range(Shape *shape) : cursor(NULL, shape) {
JS_STATIC_ASSERT(allowGC == NoGC);
}
bool empty() const {
return !cursor || cursor->isEmptyShape();
}
Shape &front() const {
JS_ASSERT(!empty());
return *cursor;
}
void popFront() {
JS_ASSERT(!empty());
cursor = cursor->parent;
}
};
Class *getObjectClass() const { return base()->clasp; }
JSObject *getObjectParent() const { return base()->parent; }
JSObject *getObjectMetadata() const { return base()->metadata; }
static Shape *setObjectParent(JSContext *cx, JSObject *obj, TaggedProto proto, Shape *last);
static Shape *setObjectMetadata(JSContext *cx, JSObject *metadata, TaggedProto proto, Shape *last);
static Shape *setObjectFlag(JSContext *cx, BaseShape::Flag flag, TaggedProto proto, Shape *last);
uint32_t getObjectFlags() const { return base()->getObjectFlags(); }
bool hasObjectFlag(BaseShape::Flag flag) const {
JS_ASSERT(!(flag & ~BaseShape::OBJECT_FLAG_MASK));
return !!(base()->flags & flag);
}
protected:
/*
* Implementation-private bits stored in shape->flags. See public: enum {}
* flags further below, which were allocated FCFS over time, so interleave
* with these bits.
*/
enum {
/* Property is placeholder for a non-native class. */
NON_NATIVE = 0x01,
/* Property stored in per-object dictionary, not shared property tree. */
IN_DICTIONARY = 0x02,
UNUSED_BITS = 0x3C
};
/* Get a shape identical to this one, without parent/kids information. */
Shape(const StackShape &other, uint32_t nfixed);
/* Used by EmptyShape (see jsscopeinlines.h). */
Shape(UnownedBaseShape *base, uint32_t nfixed);
/* Copy constructor disabled, to avoid misuse of the above form. */
Shape(const Shape &other) MOZ_DELETE;
/*
* Whether this shape has a valid slot value. This may be true even if
* !hasSlot() (see SlotInfo comment above), and may be false even if
* hasSlot() if the shape is being constructed and has not had a slot
* assigned yet. After construction, hasSlot() implies !hasMissingSlot().
*/
bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
public:
/* Public bits stored in shape->flags. */
enum {
HAS_SHORTID = 0x40,
PUBLIC_FLAGS = HAS_SHORTID
};
bool inDictionary() const { return (flags & IN_DICTIONARY) != 0; }
unsigned getFlags() const { return flags & PUBLIC_FLAGS; }
bool hasShortID() const { return (flags & HAS_SHORTID) != 0; }
PropertyOp getter() const { return base()->rawGetter; }
bool hasDefaultGetter() const { return !base()->rawGetter; }
PropertyOp getterOp() const { JS_ASSERT(!hasGetterValue()); return base()->rawGetter; }
JSObject *getterObject() const { JS_ASSERT(hasGetterValue()); return base()->getterObj; }
// Per ES5, decode null getterObj as the undefined value, which encodes as null.
Value getterValue() const {
JS_ASSERT(hasGetterValue());
return base()->getterObj ? ObjectValue(*base()->getterObj) : UndefinedValue();
}
Value getterOrUndefined() const {
return (hasGetterValue() && base()->getterObj)
? ObjectValue(*base()->getterObj)
: UndefinedValue();
}
StrictPropertyOp setter() const { return base()->rawSetter; }
bool hasDefaultSetter() const { return !base()->rawSetter; }
StrictPropertyOp setterOp() const { JS_ASSERT(!hasSetterValue()); return base()->rawSetter; }
JSObject *setterObject() const { JS_ASSERT(hasSetterValue()); return base()->setterObj; }
// Per ES5, decode null setterObj as the undefined value, which encodes as null.
Value setterValue() const {
JS_ASSERT(hasSetterValue());
return base()->setterObj ? ObjectValue(*base()->setterObj) : UndefinedValue();
}
Value setterOrUndefined() const {
return (hasSetterValue() && base()->setterObj)
? ObjectValue(*base()->setterObj)
: UndefinedValue();
}
void update(PropertyOp getter, StrictPropertyOp setter, uint8_t attrs);
inline bool matches(const Shape *other) const;
inline bool matches(const StackShape &other) const;
inline bool matchesParamsAfterId(BaseShape *base,
uint32_t aslot, unsigned aattrs, unsigned aflags,
int ashortid) const;
bool get(JSContext* cx, HandleObject receiver, JSObject *obj, JSObject *pobj, MutableHandleValue vp);
bool set(JSContext* cx, HandleObject obj, HandleObject receiver, bool strict, MutableHandleValue vp);
BaseShape *base() const { return base_.get(); }
bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; }
uint32_t slot() const { JS_ASSERT(hasSlot() && !hasMissingSlot()); return maybeSlot(); }
uint32_t maybeSlot() const { return slotInfo & SLOT_MASK; }
bool isEmptyShape() const {
JS_ASSERT_IF(JSID_IS_EMPTY(propid_), hasMissingSlot());
return JSID_IS_EMPTY(propid_);
}
uint32_t slotSpan() const {
JS_ASSERT(!inDictionary());
uint32_t free = JSSLOT_FREE(getObjectClass());
return hasMissingSlot() ? free : Max(free, maybeSlot() + 1);
}
void setSlot(uint32_t slot) {
JS_ASSERT(slot <= SHAPE_INVALID_SLOT);
slotInfo = slotInfo & ~Shape::SLOT_MASK;
slotInfo = slotInfo | slot;
}
uint32_t numFixedSlots() const {
return (slotInfo >> FIXED_SLOTS_SHIFT);
}
void setNumFixedSlots(uint32_t nfixed) {
JS_ASSERT(nfixed < FIXED_SLOTS_MAX);
slotInfo = slotInfo & ~FIXED_SLOTS_MASK;
slotInfo = slotInfo | (nfixed << FIXED_SLOTS_SHIFT);
}
uint32_t numLinearSearches() const {
return (slotInfo & LINEAR_SEARCHES_MASK) >> LINEAR_SEARCHES_SHIFT;
}
void incrementNumLinearSearches() {
uint32_t count = numLinearSearches();
JS_ASSERT(count < LINEAR_SEARCHES_MAX);
slotInfo = slotInfo & ~LINEAR_SEARCHES_MASK;
slotInfo = slotInfo | ((count + 1) << LINEAR_SEARCHES_SHIFT);
}
const EncapsulatedId &propid() const {
JS_ASSERT(!isEmptyShape());
JS_ASSERT(!JSID_IS_VOID(propid_));
return propid_;
}
EncapsulatedId &propidRef() { JS_ASSERT(!JSID_IS_VOID(propid_)); return propid_; }
int16_t shortid() const { JS_ASSERT(hasShortID()); return maybeShortid(); }
int16_t maybeShortid() const { return shortid_; }
/*
* If SHORTID is set in shape->flags, we use shape->shortid rather
* than id when calling shape's getter or setter.
*/
inline bool getUserId(JSContext *cx, MutableHandleId idp) const;
uint8_t attributes() const { return attrs; }
bool configurable() const { return (attrs & JSPROP_PERMANENT) == 0; }
bool enumerable() const { return (attrs & JSPROP_ENUMERATE) != 0; }
bool writable() const {
// JS_ASSERT(isDataDescriptor());
return (attrs & JSPROP_READONLY) == 0;
}
bool hasGetterValue() const { return attrs & JSPROP_GETTER; }
bool hasSetterValue() const { return attrs & JSPROP_SETTER; }
bool isDataDescriptor() const {
return (attrs & (JSPROP_SETTER | JSPROP_GETTER)) == 0;
}
bool isAccessorDescriptor() const {
return (attrs & (JSPROP_SETTER | JSPROP_GETTER)) != 0;
}
PropDesc::Writability writability() const {
return (attrs & JSPROP_READONLY) ? PropDesc::NonWritable : PropDesc::Writable;
}
PropDesc::Enumerability enumerability() const {
return (attrs & JSPROP_ENUMERATE) ? PropDesc::Enumerable : PropDesc::NonEnumerable;
}
PropDesc::Configurability configurability() const {
return (attrs & JSPROP_PERMANENT) ? PropDesc::NonConfigurable : PropDesc::Configurable;
}
/*
* For ES5 compatibility, we allow properties with PropertyOp-flavored
* setters to be shadowed when set. The "own" property thereby created in
* the directly referenced object will have the same getter and setter as
* the prototype property. See bug 552432.
*/
bool shadowable() const {
JS_ASSERT_IF(isDataDescriptor(), writable());
return hasSlot() || (attrs & JSPROP_SHADOWABLE);
}
uint32_t entryCount() {
if (hasTable())
return table().entryCount;
Shape *shape = this;
uint32_t count = 0;
for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront())
++count;
return count;
}
bool isBigEnoughForAShapeTable() {
JS_ASSERT(!hasTable());
Shape *shape = this;
uint32_t count = 0;
for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) {
++count;
if (count >= ShapeTable::MIN_ENTRIES)
return true;
}
return false;
}
#ifdef DEBUG
void dump(JSContext *cx, FILE *fp) const;
void dumpSubtree(JSContext *cx, int level, FILE *fp) const;
#endif
void sweep();
void finalize(FreeOp *fop);
void removeChild(Shape *child);
JS::Zone *zone() const { return tenuredZone(); }
static inline void writeBarrierPre(Shape *shape);
static inline void writeBarrierPost(Shape *shape, void *addr);
/*
* All weak references need a read barrier for incremental GC. This getter
* method implements the read barrier. It's used to obtain initial shapes
* from the compartment.
*/
static inline void readBarrier(Shape *shape);
static inline ThingRootKind rootKind() { return THING_ROOT_SHAPE; }
inline void markChildren(JSTracer *trc);
inline Shape *search(JSContext *cx, jsid id) {
Shape **_;
return search(cx, this, id, &_);
}
/* For JIT usage */
static inline size_t offsetOfBase() { return offsetof(Shape, base_); }
private:
static void staticAsserts() {
JS_STATIC_ASSERT(offsetof(Shape, base_) == offsetof(js::shadow::Shape, base));
JS_STATIC_ASSERT(offsetof(Shape, slotInfo) == offsetof(js::shadow::Shape, slotInfo));
JS_STATIC_ASSERT(FIXED_SLOTS_SHIFT == js::shadow::Shape::FIXED_SLOTS_SHIFT);
}
};
class AutoRooterGetterSetter
{
class Inner : private JS::CustomAutoRooter
{
public:
Inner(JSContext *cx, uint8_t attrs,
PropertyOp *pgetter_, StrictPropertyOp *psetter_)
: CustomAutoRooter(cx), attrs(attrs),
pgetter(pgetter_), psetter(psetter_),
getterRoot(cx, pgetter_), setterRoot(cx, psetter_)
{
JS_ASSERT_IF(attrs & JSPROP_GETTER, !IsPoisonedPtr(*pgetter));
JS_ASSERT_IF(attrs & JSPROP_SETTER, !IsPoisonedPtr(*psetter));
}
private:
virtual void trace(JSTracer *trc);
uint8_t attrs;
PropertyOp *pgetter;
StrictPropertyOp *psetter;
SkipRoot getterRoot, setterRoot;
};
public:
explicit AutoRooterGetterSetter(JSContext *cx, uint8_t attrs,
PropertyOp *pgetter, StrictPropertyOp *psetter
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
{
if (attrs & (JSPROP_GETTER | JSPROP_SETTER))
inner.construct(cx, attrs, pgetter, psetter);
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
private:
mozilla::Maybe<Inner> inner;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
struct EmptyShape : public js::Shape
{
EmptyShape(UnownedBaseShape *base, uint32_t nfixed);
/*
* Lookup an initial shape matching the given parameters, creating an empty
* shape if none was found.
*/
static Shape *getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto, JSObject *metadata,
JSObject *parent, size_t nfixed, uint32_t objectFlags = 0);
static Shape *getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto, JSObject *metadata,
JSObject *parent, gc::AllocKind kind, uint32_t objectFlags = 0);
/*
* Reinsert an alternate initial shape, to be returned by future
* getInitialShape calls, until the new shape becomes unreachable in a GC
* and the table entry is purged.
*/
static void insertInitialShape(JSContext *cx, HandleShape shape, HandleObject proto);
};
/*
* Entries for the per-compartment initialShapes set indexing initial shapes
* for objects in the compartment and the associated types.
*/
struct InitialShapeEntry
{
/*
* Initial shape to give to the object. This is an empty shape, except for
* certain classes (e.g. String, RegExp) which may add certain baked-in
* properties.
*/
ReadBarriered<Shape> shape;
/*
* Matching prototype for the entry. The shape of an object determines its
* prototype, but the prototype cannot be determined from the shape itself.
*/
TaggedProto proto;
/* State used to determine a match on an initial shape. */
struct Lookup {
Class *clasp;
TaggedProto proto;
JSObject *parent;
JSObject *metadata;
uint32_t nfixed;
uint32_t baseFlags;
Lookup(Class *clasp, TaggedProto proto, JSObject *parent, JSObject *metadata,
uint32_t nfixed, uint32_t baseFlags)
: clasp(clasp), proto(proto), parent(parent), metadata(metadata),
nfixed(nfixed), baseFlags(baseFlags)
{}
};
inline InitialShapeEntry();
inline InitialShapeEntry(const ReadBarriered<Shape> &shape, TaggedProto proto);
inline Lookup getLookup() const;
static inline HashNumber hash(const Lookup &lookup);
static inline bool match(const InitialShapeEntry &key, const Lookup &lookup);
};
typedef HashSet<InitialShapeEntry, InitialShapeEntry, SystemAllocPolicy> InitialShapeSet;
struct StackShape
{
/* For performance, StackShape only roots when absolutely necessary. */
UnownedBaseShape *base;
jsid propid;
uint32_t slot_;
uint8_t attrs;
uint8_t flags;
int16_t shortid;
explicit StackShape(UnownedBaseShape *base, jsid propid, uint32_t slot,
uint32_t nfixed, unsigned attrs, unsigned flags, int shortid)
: base(base),
propid(propid),
slot_(slot),
attrs(uint8_t(attrs)),
flags(uint8_t(flags)),
shortid(int16_t(shortid))
{
JS_ASSERT(base);
JS_ASSERT(!JSID_IS_VOID(propid));
JS_ASSERT(slot <= SHAPE_INVALID_SLOT);
}
StackShape(Shape *const &shape)
: base(shape->base()->unowned()),
propid(shape->propidRef()),
slot_(shape->slotInfo & Shape::SLOT_MASK),
attrs(shape->attrs),
flags(shape->flags),
shortid(shape->shortid_)
{}
bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; }
bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
uint32_t slot() const { JS_ASSERT(hasSlot() && !hasMissingSlot()); return slot_; }
uint32_t maybeSlot() const { return slot_; }
uint32_t slotSpan() const {
uint32_t free = JSSLOT_FREE(base->clasp);
return hasMissingSlot() ? free : (maybeSlot() + 1);
}
void setSlot(uint32_t slot) {
JS_ASSERT(slot <= SHAPE_INVALID_SLOT);
slot_ = slot;
}
inline HashNumber hash() const;
class AutoRooter : private JS::CustomAutoRooter
{
public:
explicit AutoRooter(JSContext *cx, const StackShape *shape_
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: CustomAutoRooter(cx), shape(shape_), skip(cx, shape_)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
private:
virtual void trace(JSTracer *trc);
const StackShape *shape;
SkipRoot skip;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
};
} /* namespace js */
/* js::Shape pointer tag bit indicating a collision. */
#define SHAPE_COLLISION (uintptr_t(1))
#define SHAPE_REMOVED ((Shape *) SHAPE_COLLISION)
/* Macros to get and set shape pointer values and collision flags. */
#define SHAPE_IS_FREE(shape) ((shape) == NULL)
#define SHAPE_IS_REMOVED(shape) ((shape) == SHAPE_REMOVED)
#define SHAPE_IS_LIVE(shape) ((shape) > SHAPE_REMOVED)
#define SHAPE_FLAG_COLLISION(spp,shape) (*(spp) = (Shape *) \
(uintptr_t(shape) | SHAPE_COLLISION))
#define SHAPE_HAD_COLLISION(shape) (uintptr_t(shape) & SHAPE_COLLISION)
#define SHAPE_FETCH(spp) SHAPE_CLEAR_COLLISION(*(spp))
#define SHAPE_CLEAR_COLLISION(shape) \
((Shape *) (uintptr_t(shape) & ~SHAPE_COLLISION))
#define SHAPE_STORE_PRESERVING_COLLISION(spp, shape) \
(*(spp) = (Shape *) (uintptr_t(shape) | SHAPE_HAD_COLLISION(*(spp))))
namespace js {
inline Shape *
Shape::search(JSContext *cx, Shape *start, jsid id, Shape ***pspp, bool adding)
{
if (start->inDictionary()) {
*pspp = start->table().search(id, adding);
return SHAPE_FETCH(*pspp);
}
*pspp = NULL;
if (start->hasTable()) {
Shape **spp = start->table().search(id, adding);
return SHAPE_FETCH(spp);
}
if (start->numLinearSearches() == LINEAR_SEARCHES_MAX) {
if (start->isBigEnoughForAShapeTable()) {
if (Shape::hashify(cx, start)) {
Shape **spp = start->table().search(id, adding);
return SHAPE_FETCH(spp);
}
}
/*
* No table built -- there weren't enough entries, or OOM occurred.
* Don't increment numLinearSearches, to keep hasTable() false.
*/
JS_ASSERT(!start->hasTable());
} else {
start->incrementNumLinearSearches();
}
for (Shape *shape = start; shape; shape = shape->parent) {
if (shape->propidRef() == id)
return shape;
}
return NULL;
}
/*
* Keep this function in sync with search. It neither hashifies the start
* shape nor increments linear search count.
*/
inline Shape *
Shape::searchNoHashify(Shape *start, jsid id)
{
/*
* If we have a table, search in the shape table, else do a linear
* search. We never hashify into a table in parallel.
*/
if (start->hasTable()) {
Shape **spp = start->table().search(id, false);
return SHAPE_FETCH(spp);
}
for (Shape *shape = start; shape; shape = shape->parent) {
if (shape->propidRef() == id)
return shape;
}
return NULL;
}
template<> struct RootKind<Shape *> : SpecificRootKind<Shape *, THING_ROOT_SHAPE> {};
template<> struct RootKind<BaseShape *> : SpecificRootKind<BaseShape *, THING_ROOT_BASE_SHAPE> {};
} // namespace js
#ifdef _MSC_VER
#pragma warning(pop)
#pragma warning(pop)
#endif
namespace JS {
template<> class AnchorPermitted<js::Shape *> { };
template<> class AnchorPermitted<const js::Shape *> { };
}
#endif /* vm_Shape_h */