#ifndef vm_Shape_h
#define vm_Shape_h
#include "mozilla/Attributes.h"
#include "mozilla/GuardObjects.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/TemplateLib.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "jstypes.h"
#include "NamespaceImports.h"
#include "gc/Barrier.h"
#include "gc/FreeOp.h"
#include "gc/Heap.h"
#include "gc/Rooting.h"
#include "js/HashTable.h"
#include "js/MemoryMetrics.h"
#include "js/RootingAPI.h"
#include "js/UbiNode.h"
#include "vm/JSAtom.h"
#include "vm/ObjectGroup.h"
#include "vm/Printer.h"
#include "vm/StringType.h"
#include "vm/SymbolType.h"
MOZ_ALWAYS_INLINE size_t JSSLOT_FREE(const js::Class* clasp) {
MOZ_ASSERT(!clasp->isProxy());
return JSCLASS_RESERVED_SLOTS(clasp);
}
namespace js {
class Shape;
struct StackShape;
struct ShapeHasher : public DefaultHasher<Shape*> {
typedef Shape* Key;
typedef StackShape Lookup;
static MOZ_ALWAYS_INLINE HashNumber hash(const Lookup& l);
static MOZ_ALWAYS_INLINE bool match(Key k, const Lookup& l);
};
typedef HashSet<Shape*, ShapeHasher, SystemAllocPolicy> KidsHash;
class KidsPointer {
private:
enum { SHAPE = 0, HASH = 1, TAG = 1 };
uintptr_t w;
public:
bool isNull() const { return !w; }
void setNull() { w = 0; }
bool isShape() const { return (w & TAG) == SHAPE && !isNull(); }
Shape* toShape() const {
MOZ_ASSERT(isShape());
return reinterpret_cast<Shape*>(w & ~uintptr_t(TAG));
}
void setShape(Shape* shape) {
MOZ_ASSERT(shape);
MOZ_ASSERT(
(reinterpret_cast<uintptr_t>(static_cast<Shape*>(shape)) & TAG) == 0);
w = reinterpret_cast<uintptr_t>(static_cast<Shape*>(shape)) | SHAPE;
}
bool isHash() const { return (w & TAG) == HASH; }
KidsHash* toHash() const {
MOZ_ASSERT(isHash());
return reinterpret_cast<KidsHash*>(w & ~uintptr_t(TAG));
}
void setHash(KidsHash* hash) {
MOZ_ASSERT(hash);
MOZ_ASSERT((reinterpret_cast<uintptr_t>(hash) & TAG) == 0);
w = reinterpret_cast<uintptr_t>(hash) | HASH;
}
#ifdef DEBUG
void checkConsistency(Shape* aKid) const;
#endif
};
class PropertyTree {
friend class ::JSFunction;
#ifdef DEBUG
JS::Zone* zone_;
#endif
bool insertChild(JSContext* cx, Shape* parent, Shape* child);
PropertyTree();
public:
enum { MAX_HEIGHT = 512, MAX_HEIGHT_WITH_ELEMENTS_ACCESS = 128 };
explicit PropertyTree(JS::Zone* zone)
#ifdef DEBUG
: zone_(zone)
#endif
{
}
MOZ_ALWAYS_INLINE Shape* inlinedGetChild(JSContext* cx, Shape* parent,
JS::Handle<StackShape> child);
Shape* getChild(JSContext* cx, Shape* parent, JS::Handle<StackShape> child);
};
class TenuringTracer;
typedef JSGetterOp GetterOp;
typedef JSSetterOp SetterOp;
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 AutoKeepShapeCaches;
class ShapeIC {
public:
friend class NativeObject;
friend class BaseShape;
friend class Shape;
ShapeIC() : size_(0), nextFreeIndex_(0), entries_(nullptr) {}
~ShapeIC() = default;
bool isFull() const {
MOZ_ASSERT(nextFreeIndex_ <= size_);
return size_ == nextFreeIndex_;
}
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
return mallocSizeOf(this) + mallocSizeOf(entries_.get());
}
uint32_t entryCount() { return nextFreeIndex_; }
bool init(JSContext* cx);
void trace(JSTracer* trc);
#ifdef JSGC_HASH_TABLE_CHECKS
void checkAfterMovingGC();
#endif
MOZ_ALWAYS_INLINE bool search(jsid id, Shape** foundShape);
MOZ_ALWAYS_INLINE bool appendEntry(jsid id, Shape* shape) {
MOZ_ASSERT(nextFreeIndex_ <= size_);
if (nextFreeIndex_ == size_) {
return false;
}
entries_[nextFreeIndex_].id_ = id;
entries_[nextFreeIndex_].shape_ = shape;
nextFreeIndex_++;
return true;
}
private:
static const uint32_t MAX_SIZE = 7;
class Entry {
public:
jsid id_;
Shape* shape_;
Entry() = delete;
Entry(const Entry&) = delete;
Entry& operator=(const Entry&) = delete;
};
uint8_t size_;
uint8_t nextFreeIndex_;
UniquePtr<Entry[], JS::FreePolicy> entries_;
};
class ShapeTable {
public:
friend class NativeObject;
friend class BaseShape;
friend class Shape;
friend class ShapeCachePtr;
class Entry {
static const uintptr_t SHAPE_COLLISION = 1;
static Shape* const SHAPE_REMOVED;
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<Shape*>(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<Shape*>(uintptr_t(shape_) | SHAPE_COLLISION);
}
void setPreservingCollision(Shape* shape) {
shape_ = reinterpret_cast<Shape*>(uintptr_t(shape) |
uintptr_t(hadCollision()));
}
};
private:
static const uint32_t HASH_BITS = mozilla::tl::BitSize<HashNumber>::value;
static const uint32_t MIN_SIZE_LOG2 = 2;
static const uint32_t MIN_SIZE = JS_BIT(MIN_SIZE_LOG2);
uint32_t hashShift_;
uint32_t entryCount_;
uint32_t removedCount_;
uint32_t freeList_;
UniquePtr<Entry[], JS::FreePolicy>
entries_;
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE 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) {
}
~ShapeTable() = default;
uint32_t entryCount() const { return entryCount_; }
uint32_t freeList() const { return freeList_; }
void setFreeList(uint32_t slot) { freeList_ = slot; }
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
return mallocSizeOf(this) + mallocSizeOf(entries_.get());
}
bool init(JSContext* cx, Shape* lastProp);
bool change(JSContext* cx, int log2Delta);
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE Entry& search(jsid id, const AutoKeepShapeCaches&);
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE Entry& search(jsid id, const JS::AutoCheckCannotGC&);
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());
}
uint32_t capacity() const { return JS_BIT(HASH_BITS - hashShift_); }
bool needsToGrow() const {
uint32_t size = capacity();
return entryCount_ + removedCount_ >= size - (size >> 2);
}
bool grow(JSContext* cx);
};
class ShapeCachePtr {
uintptr_t p;
enum class CacheType {
IC = 0x1,
Table = 0x2,
};
static const uint32_t MASK_BITS = 0x3;
static const uintptr_t CACHETYPE_MASK = 0x3;
void* getPointer() const {
uintptr_t ptrVal = p & ~CACHETYPE_MASK;
return reinterpret_cast<void*>(ptrVal);
}
CacheType getType() const {
return static_cast<CacheType>(p & CACHETYPE_MASK);
}
public:
static const uint32_t MIN_ENTRIES = 3;
ShapeCachePtr() : p(0) {}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE bool search(jsid id, Shape* start, Shape** foundShape);
bool isIC() const { return (getType() == CacheType::IC); }
bool isTable() const { return (getType() == CacheType::Table); }
bool isInitialized() const { return isTable() || isIC(); }
ShapeTable* getTablePointer() const {
MOZ_ASSERT(isTable());
return reinterpret_cast<ShapeTable*>(getPointer());
}
ShapeIC* getICPointer() const {
MOZ_ASSERT(isIC());
return reinterpret_cast<ShapeIC*>(getPointer());
}
void initializeTable(ShapeTable* table) {
MOZ_ASSERT(!isTable());
maybePurgeCache();
uintptr_t tableptr = uintptr_t(table);
MOZ_ASSERT((tableptr & CACHETYPE_MASK) == 0);
tableptr |= static_cast<uintptr_t>(CacheType::Table);
p = tableptr;
}
void initializeIC(ShapeIC* ic) {
MOZ_ASSERT(!isTable() && !isIC());
uintptr_t icptr = uintptr_t(ic);
MOZ_ASSERT((icptr & CACHETYPE_MASK) == 0);
icptr |= static_cast<uintptr_t>(CacheType::IC);
p = icptr;
}
void destroy(FreeOp* fop) {
if (isTable()) {
fop->delete_<ShapeTable>(getTablePointer());
} else if (isIC()) {
fop->delete_<ShapeIC>(getICPointer());
}
p = 0;
}
void maybePurgeCache() {
if (isTable()) {
ShapeTable* table = getTablePointer();
if (table->freeList() == SHAPE_INVALID_SLOT) {
js_delete<ShapeTable>(getTablePointer());
p = 0;
}
} else if (isIC()) {
js_delete<ShapeIC>(getICPointer());
p = 0;
}
}
void trace(JSTracer* trc);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
size_t size = 0;
if (isIC()) {
size = getICPointer()->sizeOfIncludingThis(mallocSizeOf);
} else if (isTable()) {
size = getTablePointer()->sizeOfIncludingThis(mallocSizeOf);
}
return size;
}
uint32_t entryCount() {
uint32_t count = 0;
if (isIC()) {
count = getICPointer()->entryCount();
} else if (isTable()) {
count = getTablePointer()->entryCount();
}
return count;
}
#ifdef JSGC_HASH_TABLE_CHECKS
void checkAfterMovingGC();
#endif
};
class MOZ_RAII AutoKeepShapeCaches {
JSContext* cx_;
bool prev_;
public:
void operator=(const AutoKeepShapeCaches&) = delete;
AutoKeepShapeCaches(const AutoKeepShapeCaches&) = delete;
explicit inline AutoKeepShapeCaches(JSContext* cx);
inline ~AutoKeepShapeCaches();
};
class AccessorShape;
class Shape;
class UnownedBaseShape;
struct StackBaseShape;
class BaseShape : public gc::TenuredCell {
public:
friend class Shape;
friend struct StackBaseShape;
friend struct StackShape;
enum Flag {
OWNED_SHAPE = 0x1,
DELEGATE = 0x8,
NOT_EXTENSIBLE = 0x10,
INDEXED = 0x20,
HAS_INTERESTING_SYMBOL = 0x40,
HAD_ELEMENTS_ACCESS = 0x80,
ITERATED_SINGLETON = 0x200,
NEW_GROUP_UNKNOWN = 0x400,
UNCACHEABLE_PROTO = 0x800,
IMMUTABLE_PROTOTYPE = 0x1000,
QUALIFIED_VAROBJ = 0x2000,
OBJECT_FLAG_MASK = 0xfff8
};
private:
const Class* clasp_;
uint32_t flags;
uint32_t slotSpan_;
GCPtrUnownedBaseShape unowned_;
ShapeCachePtr cache_;
BaseShape(const BaseShape& base) = delete;
BaseShape& operator=(const BaseShape& other) = delete;
public:
void finalize(FreeOp* fop);
explicit inline BaseShape(const StackBaseShape& base);
~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(cache_.isInitialized(), isOwned());
return cache_.isTable();
}
bool hasIC() const {
MOZ_ASSERT_IF(cache_.isInitialized(), isOwned());
return cache_.isIC();
}
void setTable(ShapeTable* table) {
MOZ_ASSERT(isOwned());
cache_.initializeTable(table);
}
void setIC(ShapeIC* ic) {
MOZ_ASSERT(isOwned());
cache_.initializeIC(ic);
}
ShapeCachePtr getCache(const AutoKeepShapeCaches&) const {
MOZ_ASSERT_IF(cache_.isInitialized(), isOwned());
return cache_;
}
ShapeCachePtr getCache(const JS::AutoCheckCannotGC&) const {
MOZ_ASSERT_IF(cache_.isInitialized(), isOwned());
return cache_;
}
ShapeTable* maybeTable(const AutoKeepShapeCaches&) const {
MOZ_ASSERT_IF(cache_.isInitialized(), isOwned());
return (cache_.isTable()) ? cache_.getTablePointer() : nullptr;
}
ShapeTable* maybeTable(const JS::AutoCheckCannotGC&) const {
MOZ_ASSERT_IF(cache_.isInitialized(), isOwned());
return (cache_.isTable()) ? cache_.getTablePointer() : nullptr;
}
ShapeIC* maybeIC(const AutoKeepShapeCaches&) const {
MOZ_ASSERT_IF(cache_.isInitialized(), isOwned());
return (cache_.isIC()) ? cache_.getICPointer() : nullptr;
}
ShapeIC* maybeIC(const JS::AutoCheckCannotGC&) const {
MOZ_ASSERT_IF(cache_.isInitialized(), isOwned());
return (cache_.isIC()) ? cache_.getICPointer() : nullptr;
}
void maybePurgeCache() { cache_.maybePurgeCache(); }
uint32_t slotSpan() const {
MOZ_ASSERT(isOwned());
return slotSpan_;
}
void setSlotSpan(uint32_t slotSpan) {
MOZ_ASSERT(isOwned());
slotSpan_ = slotSpan;
}
static UnownedBaseShape* getUnowned(JSContext* cx, StackBaseShape& base);
inline UnownedBaseShape* unowned();
inline UnownedBaseShape* baseUnowned();
inline UnownedBaseShape* toUnowned();
void assertConsistency();
static inline size_t offsetOfFlags() { return offsetof(BaseShape, flags); }
static const JS::TraceKind TraceKind = JS::TraceKind::BaseShape;
void traceChildren(JSTracer* trc);
void traceChildrenSkipShapeCache(JSTracer* trc);
#ifdef DEBUG
bool canSkipMarkingShapeCache(Shape* lastShape);
#endif
private:
static void staticAsserts() {
JS_STATIC_ASSERT(offsetof(BaseShape, clasp_) ==
offsetof(js::shadow::BaseShape, clasp_));
static_assert(sizeof(BaseShape) % gc::CellAlignBytes == 0,
"Things inheriting from gc::Cell must have a size that's "
"a multiple of gc::CellAlignBytes");
}
void traceShapeCache(JSTracer* trc);
};
class UnownedBaseShape : public BaseShape {};
UnownedBaseShape* BaseShape::unowned() {
return isOwned() ? baseUnowned() : toUnowned();
}
UnownedBaseShape* BaseShape::toUnowned() {
MOZ_ASSERT(!isOwned() && !unowned_);
return static_cast<UnownedBaseShape*>(this);
}
UnownedBaseShape* BaseShape::baseUnowned() {
MOZ_ASSERT(isOwned() && unowned_);
return unowned_;
}
struct StackBaseShape : public DefaultHasher<ReadBarriered<UnownedBaseShape*>> {
uint32_t flags;
const Class* clasp;
explicit StackBaseShape(BaseShape* base)
: flags(base->flags & BaseShape::OBJECT_FLAG_MASK), clasp(base->clasp_) {}
inline StackBaseShape(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());
}
explicit Lookup(const ReadBarriered<UnownedBaseShape*>& base)
: flags(base.unbarrieredGet()->getObjectFlags()),
clasp(base.unbarrieredGet()->clasp()) {
MOZ_ASSERT(!base.unbarrieredGet()->isOwned());
}
};
static HashNumber hash(const Lookup& lookup) {
return mozilla::HashGeneric(lookup.flags, lookup.clasp);
}
static inline bool match(const ReadBarriered<UnownedBaseShape*>& key,
const Lookup& lookup) {
return key.unbarrieredGet()->flags == lookup.flags &&
key.unbarrieredGet()->clasp_ == lookup.clasp;
}
};
static MOZ_ALWAYS_INLINE js::HashNumber HashId(jsid id) {
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));
}
}
namespace mozilla {
template <>
struct DefaultHasher<jsid> {
typedef jsid Lookup;
static HashNumber hash(jsid id) { return js::HashId(id); }
static bool match(jsid id1, jsid id2) { return id1 == id2; }
};
}
namespace js {
using BaseShapeSet =
JS::WeakCache<JS::GCHashSet<ReadBarriered<UnownedBaseShape*>,
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<Shape>;
friend class js::gc::RelocationOverlay;
protected:
GCPtrBaseShape base_;
PreBarrieredId propid_;
enum ImmutableFlags : uint32_t {
SLOT_MASK = JS_BIT(24) - 1,
FIXED_SLOTS_MAX = 0x1f,
FIXED_SLOTS_SHIFT = 24,
FIXED_SLOTS_MASK = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT),
IN_DICTIONARY = 1 << 29,
ACCESSOR_SHAPE = 1 << 30,
};
enum MutableFlags : uint8_t {
LINEAR_SEARCHES_MAX = 0x5,
LINEAR_SEARCHES_MASK = 0x7,
OVERWRITTEN = 0x08,
HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x10,
CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x20,
};
uint32_t immutableFlags;
uint8_t attrs;
uint8_t mutableFlags;
GCPtrShape parent;
union {
KidsPointer kids;
GCPtrShape* listp;
};
template <MaybeAdding Adding = MaybeAdding::NotAdding>
static MOZ_ALWAYS_INLINE Shape* search(JSContext* cx, Shape* start, jsid id);
template <MaybeAdding Adding = MaybeAdding::NotAdding>
static inline MOZ_MUST_USE bool search(JSContext* cx, Shape* start, jsid id,
const AutoKeepShapeCaches&,
Shape** pshape, ShapeTable** ptable,
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);
static Shape* replaceLastProperty(JSContext* cx, StackBaseShape& base,
TaggedProto proto, HandleShape shape);
static bool hashify(JSContext* cx, Shape* shape);
static bool cachify(JSContext* 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(),
isDataProperty() == (p->maybeSlot() != maybeSlot()));
parent = p;
}
bool ensureOwnBaseShape(JSContext* cx) {
if (base()->isOwned()) {
return true;
}
return makeOwnBaseShape(cx);
}
bool makeOwnBaseShape(JSContext* cx);
MOZ_ALWAYS_INLINE MOZ_MUST_USE bool maybeCreateCacheForLookup(JSContext* cx);
MOZ_ALWAYS_INLINE void updateDictionaryTable(ShapeTable* table,
ShapeTable::Entry* entry,
const AutoKeepShapeCaches& keep);
public:
bool hasTable() const { return base()->hasTable(); }
bool hasIC() const { return base()->hasIC(); }
ShapeIC* maybeIC(const AutoKeepShapeCaches& keep) const {
return base()->maybeIC(keep);
}
ShapeIC* maybeIC(const JS::AutoCheckCannotGC& check) const {
return base()->maybeIC(check);
}
ShapeTable* maybeTable(const AutoKeepShapeCaches& keep) const {
return base()->maybeTable(keep);
}
ShapeTable* maybeTable(const JS::AutoCheckCannotGC& check) const {
return base()->maybeTable(check);
}
ShapeCachePtr getCache(const AutoKeepShapeCaches& keep) const {
return base()->getCache(keep);
}
ShapeCachePtr getCache(const JS::AutoCheckCannotGC& check) const {
return base()->getCache(check);
}
bool appendShapeToIC(jsid id, Shape* shape,
const JS::AutoCheckCannotGC& check) {
MOZ_ASSERT(hasIC());
ShapeCachePtr cache = getCache(check);
return cache.getICPointer()->appendEntry(id, shape);
}
template <typename T>
MOZ_MUST_USE ShapeTable* ensureTableForDictionary(JSContext* 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 (inDictionary()) {
info->shapesMallocHeapDictTables +=
getCache(nogc).sizeOfExcludingThis(mallocSizeOf);
} else {
info->shapesMallocHeapTreeTables +=
getCache(nogc).sizeOfExcludingThis(mallocSizeOf);
}
if (!inDictionary() && kids.isHash()) {
info->shapesMallocHeapTreeKids +=
kids.toHash()->shallowSizeOfIncludingThis(mallocSizeOf);
}
}
bool isAccessorShape() const {
MOZ_ASSERT_IF(immutableFlags & ACCESSOR_SHAPE,
getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE);
return immutableFlags & ACCESSOR_SHAPE;
}
AccessorShape& asAccessorShape() const {
MOZ_ASSERT(isAccessorShape());
return *(AccessorShape*)this;
}
const GCPtrShape& previous() const { return parent; }
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);
}
explicit Range(Shape* shape) : cursor((JSContext*)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(JSContext* 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:
inline Shape(const StackShape& other, uint32_t nfixed);
inline Shape(UnownedBaseShape* base, uint32_t nfixed);
Shape(const Shape& other) = delete;
static inline Shape* new_(JSContext* cx, Handle<StackShape> other,
uint32_t nfixed);
bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
public:
bool inDictionary() const { return immutableFlags & IN_DICTIONARY; }
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(); }
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(); }
Value setterValue() const {
MOZ_ASSERT(hasSetterValue());
if (JSObject* setterObj = setterObject()) {
return ObjectValue(*setterObj);
}
return UndefinedValue();
}
Value setterOrUndefined() const {
return hasSetterValue() ? setterValue() : UndefinedValue();
}
void setOverwritten() { mutableFlags |= OVERWRITTEN; }
bool hadOverwrite() const { return mutableFlags & OVERWRITTEN; }
bool matches(const Shape* other) const {
return propid_.get() == other->propid_.get() &&
matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs,
other->getter(), other->setter());
}
inline bool matches(const StackShape& other) const;
bool matchesParamsAfterId(BaseShape* base, uint32_t aslot, unsigned aattrs,
GetterOp rawGetter, SetterOp rawSetter) const {
return base->unowned() == this->base()->unowned() && maybeSlot() == aslot &&
attrs == aattrs && getter() == rawGetter && setter() == rawSetter;
}
BaseShape* base() const { return base_.get(); }
static bool isDataProperty(unsigned attrs, GetterOp getter, SetterOp setter) {
return !(attrs & (JSPROP_GETTER | JSPROP_SETTER)) && !getter && !setter;
}
bool isDataProperty() const {
MOZ_ASSERT(!isEmptyShape());
return isDataProperty(attrs, getter(), setter());
}
uint32_t slot() const {
MOZ_ASSERT(isDataProperty() && !hasMissingSlot());
return maybeSlot();
}
uint32_t maybeSlot() const { return immutableFlags & 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 = clasp->isProxy() ? 0 : 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);
immutableFlags = (immutableFlags & ~Shape::SLOT_MASK) | slot;
}
uint32_t numFixedSlots() const {
return (immutableFlags & FIXED_SLOTS_MASK) >> FIXED_SLOTS_SHIFT;
}
void setNumFixedSlots(uint32_t nfixed) {
MOZ_ASSERT(nfixed < FIXED_SLOTS_MAX);
immutableFlags = immutableFlags & ~FIXED_SLOTS_MASK;
immutableFlags = immutableFlags | (nfixed << FIXED_SLOTS_SHIFT);
}
uint32_t numLinearSearches() const {
return mutableFlags & LINEAR_SEARCHES_MASK;
}
void incrementNumLinearSearches() {
uint32_t count = numLinearSearches();
MOZ_ASSERT(count < LINEAR_SEARCHES_MAX);
mutableFlags = (mutableFlags & ~LINEAR_SEARCHES_MASK) | (count + 1);
}
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 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;
}
uint32_t entryCount() {
JS::AutoCheckCannotGC nogc;
if (ShapeTable* table = maybeTable(nogc)) {
return table->entryCount();
}
uint32_t count = 0;
for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront()) {
++count;
}
return count;
}
private:
bool isBigEnoughForAShapeTableSlow() {
uint32_t count = 0;
for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront()) {
++count;
if (count >= ShapeCachePtr::MIN_ENTRIES) {
return true;
}
}
return false;
}
void clearCachedBigEnoughForShapeTable() {
mutableFlags &= ~(HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE |
CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE);
}
public:
bool isBigEnoughForAShapeTable() {
MOZ_ASSERT(!hasTable());
if (mutableFlags & HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE) {
bool res = mutableFlags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
MOZ_ASSERT(res == isBigEnoughForAShapeTableSlow());
return res;
}
MOZ_ASSERT(!(mutableFlags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE));
bool res = isBigEnoughForAShapeTableSlow();
if (res) {
mutableFlags |= CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
}
mutableFlags |= HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
return res;
}
#ifdef DEBUG
void dump(js::GenericPrinter& out) const;
void dump() const;
void dumpSubtree(int level, js::GenericPrinter& out) const;
#endif
void sweep();
void finalize(FreeOp* fop);
void removeChild(Shape* child);
static const JS::TraceKind TraceKind = JS::TraceKind::Shape;
void traceChildren(JSTracer* trc);
MOZ_ALWAYS_INLINE Shape* search(JSContext* cx, jsid id);
MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id);
void fixupAfterMovingGC();
void fixupGetterSetterForBarrier(JSTracer* trc);
void updateBaseShapeAfterMovingGC();
static inline size_t offsetOfBaseShape() { return offsetof(Shape, base_); }
#ifdef DEBUG
static inline size_t offsetOfImmutableFlags() {
return offsetof(Shape, immutableFlags);
}
static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; }
#endif
private:
void fixupDictionaryShapeAfterMovingGC();
void fixupShapeTreeAfterMovingGC();
static Shape* fromParentFieldPointer(uintptr_t p) {
return reinterpret_cast<Shape*>(p - offsetof(Shape, parent));
}
static void staticAsserts() {
JS_STATIC_ASSERT(offsetof(Shape, base_) ==
offsetof(js::shadow::Shape, base));
JS_STATIC_ASSERT(offsetof(Shape, immutableFlags) ==
offsetof(js::shadow::Shape, immutableFlags));
JS_STATIC_ASSERT(FIXED_SLOTS_SHIFT == js::shadow::Shape::FIXED_SLOTS_SHIFT);
JS_STATIC_ASSERT(FIXED_SLOTS_MASK == js::shadow::Shape::FIXED_SLOTS_MASK);
}
};
class AccessorShape : public Shape {
friend class Shape;
friend class NativeObject;
union {
GetterOp rawGetter;
JSObject* getterObj;
};
union {
SetterOp rawSetter;
JSObject* setterObj;
};
public:
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 {
public:
inline Inner(uint8_t attrs, GetterOp* pgetter_, SetterOp* psetter_);
void trace(JSTracer* trc);
private:
uint8_t attrs;
GetterOp* pgetter;
SetterOp* psetter;
};
public:
inline AutoRooterGetterSetter(JSContext* cx, uint8_t attrs, GetterOp* pgetter,
SetterOp* psetter
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
private:
mozilla::Maybe<Rooted<Inner>> 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_(JSContext* cx, Handle<UnownedBaseShape*> base,
uint32_t nfixed);
static Shape* getInitialShape(JSContext* cx, const Class* clasp,
TaggedProto proto, size_t nfixed,
uint32_t objectFlags = 0);
static Shape* getInitialShape(JSContext* cx, const Class* clasp,
TaggedProto proto, gc::AllocKind kind,
uint32_t objectFlags = 0);
static void insertInitialShape(JSContext* cx, HandleShape shape,
HandleObject proto);
template <class ObjectSubclass>
static inline bool ensureInitialCustomShape(JSContext* cx,
Handle<ObjectSubclass*> obj);
};
struct InitialShapeEntry {
ReadBarriered<Shape*> shape;
ReadBarriered<TaggedProto> proto;
struct Lookup {
const Class* clasp;
TaggedProto proto;
uint32_t nfixed;
uint32_t baseFlags;
Lookup(const Class* clasp, const TaggedProto& proto, uint32_t nfixed,
uint32_t baseFlags)
: clasp(clasp), proto(proto), nfixed(nfixed), baseFlags(baseFlags) {}
explicit Lookup(const InitialShapeEntry& entry)
: proto(entry.proto.unbarrieredGet()) {
const Shape* shape = entry.shape.unbarrieredGet();
clasp = shape->getObjectClass();
nfixed = shape->numFixedSlots();
baseFlags = shape->getObjectFlags();
}
};
inline InitialShapeEntry();
inline InitialShapeEntry(Shape* shape, const TaggedProto& proto);
static HashNumber hash(const Lookup& lookup) {
HashNumber hash = MovableCellHasher<TaggedProto>::hash(lookup.proto);
return mozilla::AddToHash(
hash, mozilla::HashGeneric(lookup.clasp, lookup.nfixed));
}
static inline bool match(const InitialShapeEntry& key, const Lookup& lookup) {
const Shape* shape = key.shape.unbarrieredGet();
return lookup.clasp == shape->getObjectClass() &&
lookup.nfixed == shape->numFixedSlots() &&
lookup.baseFlags == shape->getObjectFlags() &&
key.proto.unbarrieredGet() == lookup.proto;
}
static void rekey(InitialShapeEntry& k, const InitialShapeEntry& newKey) {
k = newKey;
}
bool needsSweep() {
Shape* ushape = shape.unbarrieredGet();
TaggedProto uproto = proto.unbarrieredGet();
JSObject* protoObj = uproto.raw();
return (
gc::IsAboutToBeFinalizedUnbarriered(&ushape) ||
(uproto.isObject() && gc::IsAboutToBeFinalizedUnbarriered(&protoObj)));
}
bool operator==(const InitialShapeEntry& other) const {
return shape == other.shape && proto == other.proto;
}
};
using InitialShapeSet = JS::WeakCache<
JS::GCHashSet<InitialShapeEntry, InitialShapeEntry, SystemAllocPolicy>>;
struct StackShape {
UnownedBaseShape* base;
jsid propid;
GetterOp rawGetter;
SetterOp rawSetter;
uint32_t immutableFlags;
uint8_t attrs;
uint8_t mutableFlags;
explicit StackShape(UnownedBaseShape* base, jsid propid, uint32_t slot,
unsigned attrs)
: base(base),
propid(propid),
rawGetter(nullptr),
rawSetter(nullptr),
immutableFlags(slot),
attrs(uint8_t(attrs)),
mutableFlags(0) {
MOZ_ASSERT(base);
MOZ_ASSERT(!JSID_IS_VOID(propid));
MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT);
}
explicit StackShape(Shape* shape)
: base(shape->base()->unowned()),
propid(shape->propidRef()),
rawGetter(shape->getter()),
rawSetter(shape->setter()),
immutableFlags(shape->immutableFlags),
attrs(shape->attrs),
mutableFlags(shape->mutableFlags) {}
void updateGetterSetter(GetterOp rawGetter, SetterOp rawSetter) {
if (rawGetter || rawSetter || (attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
immutableFlags |= Shape::ACCESSOR_SHAPE;
} else {
immutableFlags &= ~Shape::ACCESSOR_SHAPE;
}
this->rawGetter = rawGetter;
this->rawSetter = rawSetter;
}
bool isDataProperty() const {
MOZ_ASSERT(!JSID_IS_EMPTY(propid));
return Shape::isDataProperty(attrs, rawGetter, rawSetter);
}
bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
uint32_t slot() const {
MOZ_ASSERT(isDataProperty() && !hasMissingSlot());
return maybeSlot();
}
uint32_t maybeSlot() const { return immutableFlags & Shape::SLOT_MASK; }
void setSlot(uint32_t slot) {
MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT);
immutableFlags = (immutableFlags & ~Shape::SLOT_MASK) | slot;
}
bool isAccessorShape() const {
return immutableFlags & Shape::ACCESSOR_SHAPE;
}
HashNumber hash() const {
HashNumber hash = HashId(propid);
return mozilla::AddToHash(
hash,
mozilla::HashGeneric(base, attrs, maybeSlot(), rawGetter, rawSetter));
}
static void trace(StackShape* stackShape, JSTracer* trc) {
stackShape->trace(trc);
}
void trace(JSTracer* trc);
};
template <typename Wrapper>
class WrappedPtrOperations<StackShape, Wrapper> {
const StackShape& ss() const {
return static_cast<const Wrapper*>(this)->get();
}
public:
bool isDataProperty() const { return ss().isDataProperty(); }
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 <typename Wrapper>
class MutableWrappedPtrOperations<StackShape, Wrapper>
: public WrappedPtrOperations<StackShape, Wrapper> {
StackShape& ss() { return static_cast<Wrapper*>(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; }
};
inline Shape::Shape(const StackShape& other, uint32_t nfixed)
: base_(other.base),
propid_(other.propid),
immutableFlags(other.immutableFlags),
attrs(other.attrs),
mutableFlags(other.mutableFlags),
parent(nullptr),
listp(nullptr) {
setNumFixedSlots(nfixed);
#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(!isEmptyShape(), AtomIsMarked(zone(), propid()));
kids.setNull();
}
class NurseryShapesRef : public gc::BufferableRef {
Zone* zone_;
public:
explicit NurseryShapesRef(Zone* zone) : zone_(zone) {}
void trace(JSTracer* trc) override;
};
inline Shape::Shape(UnownedBaseShape* base, uint32_t nfixed)
: base_(base),
propid_(JSID_EMPTY),
immutableFlags(SHAPE_INVALID_SLOT | (nfixed << FIXED_SLOTS_SHIFT)),
attrs(0),
mutableFlags(0),
parent(nullptr),
listp(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 Shape* Shape::searchLinear(jsid id) {
for (Shape* shape = this; shape;) {
if (shape->propidRef() == id) {
return shape;
}
shape = shape->parent;
}
return nullptr;
}
inline bool Shape::matches(const StackShape& other) const {
return propid_.get() == other.propid &&
matchesParamsAfterId(other.base, other.maybeSlot(), other.attrs,
other.rawGetter, other.rawSetter);
}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE bool ShapeCachePtr::search(jsid id, Shape* start,
Shape** foundShape) {
bool found = false;
if (isIC()) {
ShapeIC* ic = getICPointer();
found = ic->search(id, foundShape);
} else if (isTable()) {
ShapeTable* table = getTablePointer();
ShapeTable::Entry& entry = table->searchUnchecked<Adding>(id);
*foundShape = entry.shape();
found = true;
}
return found;
}
MOZ_ALWAYS_INLINE bool ShapeIC::search(jsid id, Shape** foundShape) {
Entry* entriesArray = entries_.get();
for (uint8_t i = 0; i < nextFreeIndex_; i++) {
Entry& entry = entriesArray[i];
if (entry.id_ == id) {
*foundShape = entry.shape_;
return true;
}
}
return false;
}
Shape* ReshapeForAllocKind(JSContext* cx, Shape* shape, TaggedProto proto,
gc::AllocKind allocKind);
}
namespace JS {
namespace ubi {
template <>
class Concrete<js::Shape> : TracerConcrete<js::Shape> {
protected:
explicit Concrete(js::Shape* ptr) : TracerConcrete<js::Shape>(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<js::BaseShape> : TracerConcrete<js::BaseShape> {
protected:
explicit Concrete(js::BaseShape* ptr) : TracerConcrete<js::BaseShape>(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[];
};
} }
#endif