/* -*- 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 "mozilla/MathAlgorithms.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/TemplateLib.h" #include "jsapi.h" #include "jsfriendapi.h" #include "jspropertytree.h" #include "jstypes.h" #include "NamespaceImports.h" #include "gc/Barrier.h" #include "gc/Heap.h" #include "gc/Marking.h" #include "gc/Rooting.h" #include "js/HashTable.h" #include "js/MemoryMetrics.h" #include "js/RootingAPI.h" #include "js/UbiNode.h" #include "vm/ObjectGroup.h" #include "vm/String.h" #include "vm/Symbol.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 LINEAR_SEARCHES_MAX 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. * * To save memory, shape tables can be discarded on GC and recreated when * needed. AutoKeepShapeTables can be used to avoid discarding shape tables * for a particular zone. Methods operating on ShapeTables take either an * AutoCheckCannotGC or AutoKeepShapeTables argument, to help ensure tables * are not purged while we're using them. * * 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. * * For getters/setters, an AccessorShape is allocated. This is a slightly fatter * type with extra fields for the getter/setter data. * * 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. */ #define JSSLOT_FREE(clasp) JSCLASS_RESERVED_SLOTS(clasp) namespace js { class TenuringTracer; typedef JSGetterOp GetterOp; typedef JSSetterOp SetterOp; /* 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; enum class MaybeAdding { Adding = true, NotAdding = false }; class AutoKeepShapeTables; /* * Shapes use multiplicative hashing, but specialized to * minimize footprint. */ class ShapeTable { public: friend class NativeObject; friend class BaseShape; static const uint32_t MIN_ENTRIES = 11; class Entry { // js::Shape pointer tag bit indicating a collision. static const uintptr_t SHAPE_COLLISION = 1; static Shape* const SHAPE_REMOVED; // = SHAPE_COLLISION Shape* shape_; Entry() = delete; Entry(const Entry&) = delete; Entry& operator=(const Entry&) = delete; public: bool isFree() const { return shape_ == nullptr; } bool isRemoved() const { return shape_ == SHAPE_REMOVED; } bool isLive() const { return !isFree() && !isRemoved(); } bool hadCollision() const { return uintptr_t(shape_) & SHAPE_COLLISION; } void setFree() { shape_ = nullptr; } void setRemoved() { shape_ = SHAPE_REMOVED; } Shape* shape() const { return reinterpret_cast(uintptr_t(shape_) & ~SHAPE_COLLISION); } void setShape(Shape* shape) { MOZ_ASSERT(isFree()); MOZ_ASSERT(shape); MOZ_ASSERT(shape != SHAPE_REMOVED); shape_ = shape; MOZ_ASSERT(!hadCollision()); } void flagCollision() { shape_ = reinterpret_cast(uintptr_t(shape_) | SHAPE_COLLISION); } void setPreservingCollision(Shape* shape) { shape_ = reinterpret_cast(uintptr_t(shape) | uintptr_t(hadCollision())); } }; private: static const uint32_t HASH_BITS = mozilla::tl::BitSize::value; // This value is low because it's common for a ShapeTable to be created // with an entryCount of zero. static const uint32_t MIN_SIZE_LOG2 = 2; static const uint32_t MIN_SIZE = JS_BIT(MIN_SIZE_LOG2); uint32_t 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 */ Entry* entries_; /* table of ptrs to shared tree nodes */ template Entry& searchUnchecked(jsid id); public: explicit ShapeTable(uint32_t nentries) : hashShift_(HASH_BITS - MIN_SIZE_LOG2), entryCount_(nentries), removedCount_(0), freeList_(SHAPE_INVALID_SLOT), entries_(nullptr) { /* NB: entries is set by init, which must be called. */ } ~ShapeTable() { js_free(entries_); } uint32_t entryCount() const { return entryCount_; } uint32_t freeList() const { return freeList_; } void setFreeList(uint32_t slot) { freeList_ = slot; } /* * This counts the ShapeTable object itself (which must be * heap-allocated) and its |entries| array. */ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { return mallocSizeOf(this) + mallocSizeOf(entries_); } // init() is fallible and reports OOM to the context. bool init(ExclusiveContext* cx, Shape* lastProp); // change() is fallible but does not report OOM. bool change(ExclusiveContext* cx, int log2Delta); template MOZ_ALWAYS_INLINE Entry& search(jsid id, const AutoKeepShapeTables&) { return searchUnchecked(id); } template MOZ_ALWAYS_INLINE Entry& search(jsid id, const JS::AutoCheckCannotGC&) { return searchUnchecked(id); } void trace(JSTracer* trc); #ifdef JSGC_HASH_TABLE_CHECKS void checkAfterMovingGC(); #endif private: Entry& getEntry(uint32_t i) const { MOZ_ASSERT(i < capacity()); return entries_[i]; } void decEntryCount() { MOZ_ASSERT(entryCount_ > 0); entryCount_--; } void incEntryCount() { entryCount_++; MOZ_ASSERT(entryCount_ + removedCount_ <= capacity()); } void incRemovedCount() { removedCount_++; MOZ_ASSERT(entryCount_ + removedCount_ <= capacity()); } /* By definition, hashShift = HASH_BITS - log2(capacity). */ uint32_t capacity() const { return JS_BIT(HASH_BITS - hashShift_); } /* 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(ExclusiveContext* cx); }; // Ensures no shape tables are purged in the current zone. class MOZ_RAII AutoKeepShapeTables { ExclusiveContext* cx_; bool prev_; AutoKeepShapeTables(const AutoKeepShapeTables&) = delete; void operator=(const AutoKeepShapeTables&) = delete; public: explicit inline AutoKeepShapeTables(ExclusiveContext* cx); inline ~AutoKeepShapeTables(); }; /* * Use the reserved attribute bit to mean shadowability. */ #define JSPROP_SHADOWABLE JSPROP_INTERNAL_USE_BIT /* * 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 zone 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 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 AccessorShape; class Shape; class UnownedBaseShape; struct StackBaseShape; class BaseShape : public gc::TenuredCell { public: friend class Shape; friend struct StackBaseShape; friend struct StackShape; friend void gc::MergeCompartments(JSCompartment* source, JSCompartment* target); enum Flag { /* Owned by the referring shape. */ OWNED_SHAPE = 0x1, /* (0x2 and 0x4 are unused) */ /* * 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. * * If you add a new flag here, please add appropriate code to * JSObject::dump to dump it as part of object representation. */ DELEGATE = 0x8, NOT_EXTENSIBLE = 0x10, INDEXED = 0x20, /* (0x40 is unused) */ HAD_ELEMENTS_ACCESS = 0x80, WATCHED = 0x100, ITERATED_SINGLETON = 0x200, NEW_GROUP_UNKNOWN = 0x400, UNCACHEABLE_PROTO = 0x800, IMMUTABLE_PROTOTYPE = 0x1000, // See JSObject::isQualifiedVarObj(). QUALIFIED_VAROBJ = 0x2000, // 0x4000 is unused. // For a function used as an interpreted constructor, whether a 'new' // type had constructor information cleared. NEW_SCRIPT_CLEARED = 0x8000, OBJECT_FLAG_MASK = 0xfff8 }; private: const Class* clasp_; /* Class of referring object. */ uint32_t flags; /* Vector of above flags. */ uint32_t slotSpan_; /* Object slot span for BaseShapes at * dictionary last properties. */ /* For owned BaseShapes, the canonical unowned BaseShape. */ GCPtrUnownedBaseShape unowned_; /* For owned BaseShapes, the shape's shape table. */ ShapeTable* table_; #if JS_BITS_PER_WORD == 32 // Ensure sizeof(BaseShape) is a multiple of gc::CellSize. uint32_t padding_; #endif BaseShape(const BaseShape& base) = delete; BaseShape& operator=(const BaseShape& other) = delete; public: void finalize(FreeOp* fop); explicit inline BaseShape(const StackBaseShape& base); /* Not defined: BaseShapes must not be stack allocated. */ ~BaseShape(); const Class* clasp() const { return clasp_; } bool isOwned() const { return !!(flags & OWNED_SHAPE); } static void copyFromUnowned(BaseShape& dest, UnownedBaseShape& src); inline void adoptUnowned(UnownedBaseShape* other); void setOwned(UnownedBaseShape* unowned) { flags |= OWNED_SHAPE; unowned_ = unowned; } uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; } bool hasTable() const { MOZ_ASSERT_IF(table_, isOwned()); return table_ != nullptr; } void setTable(ShapeTable* table) { MOZ_ASSERT(isOwned()); table_ = table; } ShapeTable* maybeTable(const AutoKeepShapeTables&) const { MOZ_ASSERT_IF(table_, isOwned()); return table_; } ShapeTable* maybeTable(const JS::AutoCheckCannotGC&) const { MOZ_ASSERT_IF(table_, isOwned()); return table_; } void maybePurgeTable() { if (table_ && table_->freeList() == SHAPE_INVALID_SLOT) { js_delete(table_); table_ = nullptr; } } uint32_t slotSpan() const { MOZ_ASSERT(isOwned()); return slotSpan_; } void setSlotSpan(uint32_t slotSpan) { MOZ_ASSERT(isOwned()); slotSpan_ = slotSpan; } /* * Lookup base shapes from the zone's baseShapes table, adding if not * already found. */ static UnownedBaseShape* getUnowned(ExclusiveContext* cx, 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. */ void assertConsistency(); /* For JIT usage */ static inline size_t offsetOfFlags() { return offsetof(BaseShape, flags); } static const JS::TraceKind TraceKind = JS::TraceKind::BaseShape; void traceChildren(JSTracer* trc); void traceChildrenSkipShapeTable(JSTracer* trc); #ifdef DEBUG bool canSkipMarkingShapeTable(Shape* lastShape); #endif private: static void staticAsserts() { JS_STATIC_ASSERT(offsetof(BaseShape, clasp_) == offsetof(js::shadow::BaseShape, clasp_)); static_assert(sizeof(BaseShape) % gc::CellSize == 0, "Things inheriting from gc::Cell must have a size that's " "a multiple of gc::CellSize"); } void traceShapeTable(JSTracer* trc); }; class UnownedBaseShape : public BaseShape {}; UnownedBaseShape* BaseShape::unowned() { return isOwned() ? baseUnowned() : toUnowned(); } UnownedBaseShape* BaseShape::toUnowned() { MOZ_ASSERT(!isOwned() && !unowned_); return static_cast(this); } UnownedBaseShape* BaseShape::baseUnowned() { MOZ_ASSERT(isOwned() && unowned_); return unowned_; } /* Entries for the per-zone baseShapes set of unowned base shapes. */ struct StackBaseShape : public DefaultHasher> { uint32_t flags; const Class* clasp; explicit StackBaseShape(BaseShape* base) : flags(base->flags & BaseShape::OBJECT_FLAG_MASK), clasp(base->clasp_) {} inline StackBaseShape(ExclusiveContext* cx, const Class* clasp, uint32_t objectFlags); explicit inline StackBaseShape(Shape* shape); struct Lookup { uint32_t flags; const Class* clasp; MOZ_IMPLICIT Lookup(const StackBaseShape& base) : flags(base.flags), clasp(base.clasp) {} MOZ_IMPLICIT Lookup(UnownedBaseShape* base) : flags(base->getObjectFlags()), clasp(base->clasp()) { MOZ_ASSERT(!base->isOwned()); } }; static inline HashNumber hash(const Lookup& lookup); static inline bool match(ReadBarriered key, const Lookup& lookup); }; static MOZ_ALWAYS_INLINE js::HashNumber HashId(jsid id) { // HashGeneric alone would work, but bits of atom and symbol addresses // could then be recovered from the hash code. See bug 1330769. if (MOZ_LIKELY(JSID_IS_ATOM(id))) return JSID_TO_ATOM(id)->hash(); if (JSID_IS_SYMBOL(id)) return JSID_TO_SYMBOL(id)->hash(); return mozilla::HashGeneric(JSID_BITS(id)); } template <> struct DefaultHasher { typedef jsid Lookup; static HashNumber hash(jsid id) { return HashId(id); } static bool match(jsid id1, jsid id2) { return id1 == id2; } }; using BaseShapeSet = JS::GCHashSet, StackBaseShape, SystemAllocPolicy>; class Shape : public gc::TenuredCell { friend class ::JSObject; friend class ::JSFunction; friend class NativeObject; friend class PropertyTree; friend class TenuringTracer; friend struct StackBaseShape; friend struct StackShape; friend class JS::ubi::Concrete; friend class js::gc::RelocationOverlay; protected: GCPtrBaseShape base_; PreBarrieredId propid_; enum SlotInfo : uint32_t { /* Number of fixed slots in objects with this shape. */ // FIXED_SLOTS_MAX is the biggest count of fixed slots a Shape can store 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 }; uint32_t slotInfo; /* mask of above info */ uint8_t attrs; /* attributes, see jsapi.h JSPROP_* */ uint8_t flags; /* flags, see below for defines */ GCPtrShape 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 */ GCPtrShape* 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_ */ }; template static inline Shape* search(ExclusiveContext* cx, Shape* start, jsid id); template static inline MOZ_MUST_USE bool search(ExclusiveContext* cx, Shape* start, jsid id, const AutoKeepShapeTables&, Shape** pshape, ShapeTable::Entry** pentry); static inline Shape* searchNoHashify(Shape* start, jsid id); void removeFromDictionary(NativeObject* obj); void insertIntoDictionary(GCPtrShape* dictp); inline void initDictionaryShape(const StackShape& child, uint32_t nfixed, GCPtrShape* dictp); /* Replace the base shape of the last shape in a non-dictionary lineage with base. */ static Shape* replaceLastProperty(ExclusiveContext* cx, StackBaseShape& base, TaggedProto proto, HandleShape shape); /* * This function is thread safe if every shape in the lineage of |shape| * is thread local, which is the case when we clone the entire shape * lineage in preparation for converting an object to dictionary mode. */ static bool hashify(ExclusiveContext* cx, Shape* shape); void handoffTableTo(Shape* newShape); void setParent(Shape* p) { MOZ_ASSERT_IF(p && !p->hasMissingSlot() && !inDictionary(), p->maybeSlot() <= maybeSlot()); MOZ_ASSERT_IF(p && !inDictionary(), hasSlot() == (p->maybeSlot() != maybeSlot())); parent = p; } bool ensureOwnBaseShape(ExclusiveContext* cx) { if (base()->isOwned()) return true; return makeOwnBaseShape(cx); } bool makeOwnBaseShape(ExclusiveContext* cx); MOZ_ALWAYS_INLINE MOZ_MUST_USE bool maybeCreateTableForLookup(ExclusiveContext* cx); public: bool hasTable() const { return base()->hasTable(); } ShapeTable* maybeTable(const AutoKeepShapeTables& keep) const { return base()->maybeTable(keep); } ShapeTable* maybeTable(const JS::AutoCheckCannotGC& check) const { return base()->maybeTable(check); } template MOZ_MUST_USE ShapeTable* ensureTableForDictionary(ExclusiveContext* cx, const T& nogc) { MOZ_ASSERT(inDictionary()); if (ShapeTable* table = maybeTable(nogc)) return table; if (!hashify(cx, this)) return nullptr; ShapeTable* table = maybeTable(nogc); MOZ_ASSERT(table); return table; } void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ShapeInfo* info) const { JS::AutoCheckCannotGC nogc; if (ShapeTable* table = maybeTable(nogc)) { if (inDictionary()) info->shapesMallocHeapDictTables += table->sizeOfIncludingThis(mallocSizeOf); else info->shapesMallocHeapTreeTables += table->sizeOfIncludingThis(mallocSizeOf); } if (!inDictionary() && kids.isHash()) info->shapesMallocHeapTreeKids += kids.toHash()->sizeOfIncludingThis(mallocSizeOf); } bool isAccessorShape() const { MOZ_ASSERT_IF(flags & ACCESSOR_SHAPE, getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE); return flags & ACCESSOR_SHAPE; } AccessorShape& asAccessorShape() const { MOZ_ASSERT(isAccessorShape()); return *(AccessorShape*)this; } const GCPtrShape& previous() const { return parent; } template class Range { protected: friend class Shape; typename MaybeRooted::RootType cursor; public: Range(ExclusiveContext* cx, Shape* shape) : cursor(cx, shape) { JS_STATIC_ASSERT(allowGC == CanGC); } explicit Range(Shape* shape) : cursor((ExclusiveContext*) nullptr, shape) { JS_STATIC_ASSERT(allowGC == NoGC); } bool empty() const { return !cursor || cursor->isEmptyShape(); } Shape& front() const { MOZ_ASSERT(!empty()); return *cursor; } void popFront() { MOZ_ASSERT(!empty()); cursor = cursor->parent; } }; const Class* getObjectClass() const { return base()->clasp_; } static Shape* setObjectFlags(ExclusiveContext* cx, BaseShape::Flag flag, TaggedProto proto, Shape* last); uint32_t getObjectFlags() const { return base()->getObjectFlags(); } bool hasAllObjectFlags(BaseShape::Flag flags) const { MOZ_ASSERT(flags); MOZ_ASSERT(!(flags & ~BaseShape::OBJECT_FLAG_MASK)); return (base()->flags & flags) == flags; } 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 stored in per-object dictionary, not shared property tree. */ IN_DICTIONARY = 0x01, /* * Slotful property was stored to more than once. This is used as a * hint for type inference. */ OVERWRITTEN = 0x02, /* * This shape is an AccessorShape, a fat Shape that can store * getter/setter information. */ ACCESSOR_SHAPE = 0x04, /* Flags used to speed up isBigEnoughForAShapeTable(). */ HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x08, CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x10, }; /* Get a shape identical to this one, without parent/kids information. */ inline Shape(const StackShape& other, uint32_t nfixed); /* Used by EmptyShape (see jsscopeinlines.h). */ inline Shape(UnownedBaseShape* base, uint32_t nfixed); /* Copy constructor disabled, to avoid misuse of the above form. */ Shape(const Shape& other) = delete; /* Allocate a new shape based on the given StackShape. */ static inline Shape* new_(ExclusiveContext* cx, Handle other, uint32_t nfixed); /* * 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: bool inDictionary() const { return (flags & IN_DICTIONARY) != 0; } inline GetterOp getter() const; bool hasDefaultGetter() const { return !getter(); } GetterOp getterOp() const { MOZ_ASSERT(!hasGetterValue()); return getter(); } inline JSObject* getterObject() const; bool hasGetterObject() const { return hasGetterValue() && getterObject(); } // Per ES5, decode null getterObj as the undefined value, which encodes as null. Value getterValue() const { MOZ_ASSERT(hasGetterValue()); if (JSObject* getterObj = getterObject()) return ObjectValue(*getterObj); return UndefinedValue(); } Value getterOrUndefined() const { return hasGetterValue() ? getterValue() : UndefinedValue(); } inline SetterOp setter() const; bool hasDefaultSetter() const { return !setter(); } SetterOp setterOp() const { MOZ_ASSERT(!hasSetterValue()); return setter(); } inline JSObject* setterObject() const; bool hasSetterObject() const { return hasSetterValue() && setterObject(); } // Per ES5, decode null setterObj as the undefined value, which encodes as null. Value setterValue() const { MOZ_ASSERT(hasSetterValue()); if (JSObject* setterObj = setterObject()) return ObjectValue(*setterObj); return UndefinedValue(); } Value setterOrUndefined() const { return hasSetterValue() ? setterValue() : UndefinedValue(); } void setOverwritten() { flags |= OVERWRITTEN; } bool hadOverwrite() const { return flags & OVERWRITTEN; } void update(GetterOp getter, SetterOp setter, uint8_t attrs); bool matches(const Shape* other) const { return propid_.get() == other->propid_.get() && matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs, other->flags, other->getter(), other->setter()); } inline bool matches(const StackShape& other) const; bool matchesParamsAfterId(BaseShape* base, uint32_t aslot, unsigned aattrs, unsigned aflags, GetterOp rawGetter, SetterOp rawSetter) const { return base->unowned() == this->base()->unowned() && maybeSlot() == aslot && attrs == aattrs && getter() == rawGetter && setter() == rawSetter; } bool set(JSContext* cx, HandleNativeObject obj, HandleObject receiver, MutableHandleValue vp, ObjectOpResult& result); BaseShape* base() const { return base_.get(); } bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; } uint32_t slot() const { MOZ_ASSERT(hasSlot() && !hasMissingSlot()); return maybeSlot(); } uint32_t maybeSlot() const { return slotInfo & SLOT_MASK; } bool isEmptyShape() const { MOZ_ASSERT_IF(JSID_IS_EMPTY(propid_), hasMissingSlot()); return JSID_IS_EMPTY(propid_); } uint32_t slotSpan(const Class* clasp) const { MOZ_ASSERT(!inDictionary()); uint32_t free = JSSLOT_FREE(clasp); return hasMissingSlot() ? free : Max(free, maybeSlot() + 1); } uint32_t slotSpan() const { return slotSpan(getObjectClass()); } void setSlot(uint32_t slot) { MOZ_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) { MOZ_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(); MOZ_ASSERT(count < LINEAR_SEARCHES_MAX); slotInfo = slotInfo & ~LINEAR_SEARCHES_MASK; slotInfo = slotInfo | ((count + 1) << LINEAR_SEARCHES_SHIFT); } const PreBarrieredId& propid() const { MOZ_ASSERT(!isEmptyShape()); MOZ_ASSERT(!JSID_IS_VOID(propid_)); return propid_; } PreBarrieredId& propidRef() { MOZ_ASSERT(!JSID_IS_VOID(propid_)); return propid_; } jsid propidRaw() const { // Return the actual jsid, not an internal reference. return propid(); } 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 { 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; } bool hasShadowable() const { return attrs & JSPROP_SHADOWABLE; } uint32_t entryCount() { JS::AutoCheckCannotGC nogc; if (ShapeTable* table = maybeTable(nogc)) return table->entryCount(); uint32_t count = 0; for (Shape::Range r(this); !r.empty(); r.popFront()) ++count; return count; } private: bool isBigEnoughForAShapeTableSlow() { uint32_t count = 0; for (Shape::Range r(this); !r.empty(); r.popFront()) { ++count; if (count >= ShapeTable::MIN_ENTRIES) return true; } return false; } void clearCachedBigEnoughForShapeTable() { flags &= ~(HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE | CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE); } public: bool isBigEnoughForAShapeTable() { MOZ_ASSERT(!hasTable()); // isBigEnoughForAShapeTableSlow is pretty inefficient so we only call // it once and cache the result. if (flags & HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE) { bool res = flags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE; MOZ_ASSERT(res == isBigEnoughForAShapeTableSlow()); return res; } MOZ_ASSERT(!(flags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE)); bool res = isBigEnoughForAShapeTableSlow(); if (res) flags |= CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE; flags |= HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE; return res; } #ifdef DEBUG void dump(FILE* fp) const; void dumpSubtree(int level, FILE* fp) const; #endif void sweep(); void finalize(FreeOp* fop); void removeChild(Shape* child); static const JS::TraceKind TraceKind = JS::TraceKind::Shape; void traceChildren(JSTracer* trc); inline Shape* search(ExclusiveContext* cx, jsid id); MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id); void fixupAfterMovingGC(); void fixupGetterSetterForBarrier(JSTracer* trc); void updateBaseShapeAfterMovingGC(); /* For JIT usage */ static inline size_t offsetOfBase() { return offsetof(Shape, base_); } static inline size_t offsetOfSlotInfo() { return offsetof(Shape, slotInfo); } static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; } private: void fixupDictionaryShapeAfterMovingGC(); void fixupShapeTreeAfterMovingGC(); 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); } }; /* Fat Shape used for accessor properties. */ class AccessorShape : public Shape { friend class Shape; friend class NativeObject; union { GetterOp rawGetter; /* getter hook for shape */ JSObject* getterObj; /* user-defined callable "get" object or null if shape->hasGetterValue() */ }; union { SetterOp rawSetter; /* setter hook for shape */ JSObject* setterObj; /* user-defined callable "set" object or null if shape->hasSetterValue() */ }; public: /* Get a shape identical to this one, without parent/kids information. */ inline AccessorShape(const StackShape& other, uint32_t nfixed); }; inline StackBaseShape::StackBaseShape(Shape* shape) : flags(shape->getObjectFlags()), clasp(shape->getObjectClass()) {} class MOZ_RAII AutoRooterGetterSetter { class Inner final : private JS::CustomAutoRooter { public: inline Inner(ExclusiveContext* cx, uint8_t attrs, GetterOp* pgetter_, SetterOp* psetter_); private: virtual void trace(JSTracer* trc); uint8_t attrs; GetterOp* pgetter; SetterOp* psetter; }; public: inline AutoRooterGetterSetter(ExclusiveContext* cx, uint8_t attrs, GetterOp* pgetter, SetterOp* psetter MOZ_GUARD_OBJECT_NOTIFIER_PARAM); inline AutoRooterGetterSetter(ExclusiveContext* cx, uint8_t attrs, JSNative* pgetter, JSNative* psetter MOZ_GUARD_OBJECT_NOTIFIER_PARAM); private: mozilla::Maybe inner; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; struct EmptyShape : public js::Shape { EmptyShape(UnownedBaseShape* base, uint32_t nfixed) : js::Shape(base, nfixed) { } static Shape* new_(ExclusiveContext* cx, Handle base, uint32_t nfixed); /* * Lookup an initial shape matching the given parameters, creating an empty * shape if none was found. */ static Shape* getInitialShape(ExclusiveContext* cx, const Class* clasp, TaggedProto proto, size_t nfixed, uint32_t objectFlags = 0); static Shape* getInitialShape(ExclusiveContext* cx, const Class* clasp, TaggedProto proto, 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(ExclusiveContext* cx, HandleShape shape, HandleObject proto); /* * Some object subclasses are allocated with a built-in set of properties. * The first time such an object is created, these built-in properties must * be set manually, to compute an initial shape. Afterward, that initial * shape can be reused for newly-created objects that use the subclass's * standard prototype. This method should be used in a post-allocation * init method, to ensure that objects of such subclasses compute and cache * the initial shape, if it hasn't already been computed. */ template static inline bool ensureInitialCustomShape(ExclusiveContext* cx, Handle obj); }; // InitialShapeProto stores either: // // * A TaggedProto (or ReadBarriered). // // * A JSProtoKey. This is used instead of the TaggedProto if the proto is one // of the global's builtin prototypes. For instance, if the proto is the // initial Object.prototype, we use key_ = JSProto_Object, proto_ = nullptr. // // Using the JSProtoKey here is an optimization that lets us share more shapes // across compartments within a zone. template class InitialShapeProto { template friend class InitialShapeProto; JSProtoKey key_; PtrType proto_; public: InitialShapeProto() : key_(JSProto_LIMIT), proto_() {} InitialShapeProto(JSProtoKey key, TaggedProto proto) : key_(key), proto_(proto) {} template explicit InitialShapeProto(const InitialShapeProto& other) : key_(other.key()), proto_(other.proto_) {} explicit InitialShapeProto(TaggedProto proto) : key_(JSProto_LIMIT), proto_(proto) {} explicit InitialShapeProto(JSProtoKey key) : key_(key), proto_(nullptr) { MOZ_ASSERT(key < JSProto_LIMIT); } HashNumber hashCode() const { return proto_.hashCode() ^ HashNumber(key_); } template bool match(const InitialShapeProto& other) const { return key_ == other.key_ && proto_.uniqueId() == other.proto_.unbarrieredGet().uniqueId(); } JSProtoKey key() const { return key_; } const PtrType& proto() const { return proto_; } void setProto(TaggedProto proto) { proto_ = proto; } }; /* * Entries for the per-zone initialShapes set indexing initial shapes for * objects in the zone 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; /* * Matching prototype for the entry. The shape of an object determines its * prototype, but the prototype cannot be determined from the shape itself. */ using ShapeProto = InitialShapeProto>; ShapeProto proto; /* State used to determine a match on an initial shape. */ struct Lookup { using ShapeProto = InitialShapeProto; const Class* clasp; ShapeProto proto; uint32_t nfixed; uint32_t baseFlags; Lookup(const Class* clasp, ShapeProto proto, uint32_t nfixed, uint32_t baseFlags) : clasp(clasp), proto(proto), nfixed(nfixed), baseFlags(baseFlags) {} }; inline InitialShapeEntry(); inline InitialShapeEntry(Shape* shape, const Lookup::ShapeProto& proto); static inline HashNumber hash(const Lookup& lookup); static inline bool match(const InitialShapeEntry& key, const Lookup& lookup); static void rekey(InitialShapeEntry& k, const InitialShapeEntry& newKey) { k = newKey; } bool needsSweep() { Shape* ushape = shape.unbarrieredGet(); JSObject* protoObj = proto.proto().raw(); return (gc::IsAboutToBeFinalizedUnbarriered(&ushape) || (proto.proto().isObject() && gc::IsAboutToBeFinalizedUnbarriered(&protoObj))); } }; using InitialShapeSet = JS::GCHashSet; struct StackShape { /* For performance, StackShape only roots when absolutely necessary. */ UnownedBaseShape* base; jsid propid; GetterOp rawGetter; SetterOp rawSetter; uint32_t slot_; uint8_t attrs; uint8_t flags; explicit StackShape(UnownedBaseShape* base, jsid propid, uint32_t slot, unsigned attrs, unsigned flags) : base(base), propid(propid), rawGetter(nullptr), rawSetter(nullptr), slot_(slot), attrs(uint8_t(attrs)), flags(uint8_t(flags)) { MOZ_ASSERT(base); MOZ_ASSERT(!JSID_IS_VOID(propid)); MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT); MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); } explicit StackShape(Shape* shape) : base(shape->base()->unowned()), propid(shape->propidRef()), rawGetter(shape->getter()), rawSetter(shape->setter()), slot_(shape->maybeSlot()), attrs(shape->attrs), flags(shape->flags) {} void updateGetterSetter(GetterOp rawGetter, SetterOp rawSetter) { if (rawGetter || rawSetter || (attrs & (JSPROP_GETTER|JSPROP_SETTER))) flags |= Shape::ACCESSOR_SHAPE; else flags &= ~Shape::ACCESSOR_SHAPE; this->rawGetter = rawGetter; this->rawSetter = rawSetter; } bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; } bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; } uint32_t slot() const { MOZ_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) { MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT); slot_ = slot; } bool isAccessorShape() const { return flags & Shape::ACCESSOR_SHAPE; } HashNumber hash() const { HashNumber hash = uintptr_t(base); /* Accumulate from least to most random so the low bits are most random. */ hash = mozilla::RotateLeft(hash, 4) ^ attrs; hash = mozilla::RotateLeft(hash, 4) ^ slot_; hash = mozilla::RotateLeft(hash, 4) ^ HashId(propid); hash = mozilla::RotateLeft(hash, 4) ^ uintptr_t(rawGetter); hash = mozilla::RotateLeft(hash, 4) ^ uintptr_t(rawSetter); return hash; } // Traceable implementation. static void trace(StackShape* stackShape, JSTracer* trc) { stackShape->trace(trc); } void trace(JSTracer* trc); }; template class StackShapeOperations { const StackShape& ss() const { return static_cast(this)->get(); } public: bool hasSlot() const { return ss().hasSlot(); } bool hasMissingSlot() const { return ss().hasMissingSlot(); } uint32_t slot() const { return ss().slot(); } uint32_t maybeSlot() const { return ss().maybeSlot(); } uint32_t slotSpan() const { return ss().slotSpan(); } bool isAccessorShape() const { return ss().isAccessorShape(); } uint8_t attrs() const { return ss().attrs; } }; template class MutableStackShapeOperations : public StackShapeOperations { StackShape& ss() { return static_cast(this)->get(); } public: void updateGetterSetter(GetterOp rawGetter, SetterOp rawSetter) { ss().updateGetterSetter(rawGetter, rawSetter); } void setSlot(uint32_t slot) { ss().setSlot(slot); } void setBase(UnownedBaseShape* base) { ss().base = base; } void setAttrs(uint8_t attrs) { ss().attrs = attrs; } }; template <> class RootedBase : public MutableStackShapeOperations> {}; template <> class HandleBase : public StackShapeOperations> {}; template <> class MutableHandleBase : public MutableStackShapeOperations> {}; inline Shape::Shape(const StackShape& other, uint32_t nfixed) : base_(other.base), propid_(other.propid), slotInfo(other.maybeSlot() | (nfixed << FIXED_SLOTS_SHIFT)), attrs(other.attrs), flags(other.flags), parent(nullptr) { #ifdef DEBUG gc::AllocKind allocKind = getAllocKind(); MOZ_ASSERT_IF(other.isAccessorShape(), allocKind == gc::AllocKind::ACCESSOR_SHAPE); MOZ_ASSERT_IF(allocKind == gc::AllocKind::SHAPE, !other.isAccessorShape()); #endif MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); kids.setNull(); } // This class is used to add a post barrier on the AccessorShape's getter/setter // objects. It updates the pointers and the shape's entry in the parent's // KidsHash table. class ShapeGetterSetterRef : public gc::BufferableRef { AccessorShape* shape_; public: explicit ShapeGetterSetterRef(AccessorShape* shape) : shape_(shape) {} void trace(JSTracer* trc) override { shape_->fixupGetterSetterForBarrier(trc); } }; static inline void GetterSetterWriteBarrierPost(AccessorShape* shape) { MOZ_ASSERT(shape); if (shape->hasGetterObject()) { gc::StoreBuffer* sb = reinterpret_cast(shape->getterObject())->storeBuffer(); if (sb) { sb->putGeneric(ShapeGetterSetterRef(shape)); return; } } if (shape->hasSetterObject()) { gc::StoreBuffer* sb = reinterpret_cast(shape->setterObject())->storeBuffer(); if (sb) { sb->putGeneric(ShapeGetterSetterRef(shape)); return; } } } inline AccessorShape::AccessorShape(const StackShape& other, uint32_t nfixed) : Shape(other, nfixed), rawGetter(other.rawGetter), rawSetter(other.rawSetter) { MOZ_ASSERT(getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE); GetterSetterWriteBarrierPost(this); } inline Shape::Shape(UnownedBaseShape* base, uint32_t nfixed) : base_(base), propid_(JSID_EMPTY), slotInfo(SHAPE_INVALID_SLOT | (nfixed << FIXED_SLOTS_SHIFT)), attrs(JSPROP_SHARED), flags(0), parent(nullptr) { MOZ_ASSERT(base); kids.setNull(); } inline GetterOp Shape::getter() const { return isAccessorShape() ? asAccessorShape().rawGetter : nullptr; } inline SetterOp Shape::setter() const { return isAccessorShape() ? asAccessorShape().rawSetter : nullptr; } inline JSObject* Shape::getterObject() const { MOZ_ASSERT(hasGetterValue()); return asAccessorShape().getterObj; } inline JSObject* Shape::setterObject() const { MOZ_ASSERT(hasSetterValue()); return asAccessorShape().setterObj; } inline void Shape::initDictionaryShape(const StackShape& child, uint32_t nfixed, GCPtrShape* dictp) { if (child.isAccessorShape()) new (this) AccessorShape(child, nfixed); else new (this) Shape(child, nfixed); this->flags |= IN_DICTIONARY; this->listp = nullptr; if (dictp) insertIntoDictionary(dictp); } inline Shape* Shape::searchLinear(jsid id) { for (Shape* shape = this; shape; ) { if (shape->propidRef() == id) return shape; shape = shape->parent; } return nullptr; } /* * 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. */ JS::AutoCheckCannotGC nogc; if (ShapeTable* table = start->maybeTable(nogc)) { ShapeTable::Entry& entry = table->search(id, nogc); return entry.shape(); } return start->searchLinear(id); } inline bool Shape::matches(const StackShape& other) const { return propid_.get() == other.propid && matchesParamsAfterId(other.base, other.slot_, other.attrs, other.flags, other.rawGetter, other.rawSetter); } // Property lookup hooks on objects are required to return a non-nullptr shape // to signify that the property has been found. For cases where the property is // not actually represented by a Shape, use a dummy value. This includes all // properties of non-native objects, and dense elements for native objects. // Use separate APIs for these two cases. template static inline void MarkNonNativePropertyFound(typename MaybeRooted::MutableHandleType propp) { propp.set(reinterpret_cast(1)); } template static inline void MarkDenseOrTypedArrayElementFound(typename MaybeRooted::MutableHandleType propp) { propp.set(reinterpret_cast(1)); } static inline bool IsImplicitDenseOrTypedArrayElement(Shape* prop) { return prop == reinterpret_cast(1); } static inline bool IsImplicitNonNativeProperty(Shape* prop) { return prop == reinterpret_cast(1); } Shape* ReshapeForAllocKind(JSContext* cx, Shape* shape, TaggedProto proto, gc::AllocKind allocKind); } // namespace js #ifdef _MSC_VER #pragma warning(pop) #pragma warning(pop) #endif // JS::ubi::Nodes can point to Shapes and BaseShapes; they're js::gc::Cell // instances that occupy a compartment. namespace JS { namespace ubi { template<> class Concrete : TracerConcrete { protected: explicit Concrete(js::Shape *ptr) : TracerConcrete(ptr) { } public: static void construct(void *storage, js::Shape *ptr) { new (storage) Concrete(ptr); } Size size(mozilla::MallocSizeOf mallocSizeOf) const override; const char16_t* typeName() const override { return concreteTypeName; } static const char16_t concreteTypeName[]; }; template<> class Concrete : TracerConcrete { protected: explicit Concrete(js::BaseShape *ptr) : TracerConcrete(ptr) { } public: static void construct(void *storage, js::BaseShape *ptr) { new (storage) Concrete(ptr); } Size size(mozilla::MallocSizeOf mallocSizeOf) const override; const char16_t* typeName() const override { return concreteTypeName; } static const char16_t concreteTypeName[]; }; } // namespace ubi } // namespace JS #endif /* vm_Shape_h */