#ifndef vm_NativeObject_h
#define vm_NativeObject_h
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include <stdint.h>
#include "jsfriendapi.h"
#include "NamespaceImports.h"
#include "gc/Barrier.h"
#include "gc/Heap.h"
#include "gc/Marking.h"
#include "js/Value.h"
#include "vm/JSObject.h"
#include "vm/Shape.h"
#include "vm/ShapedObject.h"
#include "vm/StringType.h"
namespace js {
class Shape;
class TenuringTracer;
class UnboxedPlainObject;
static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch(Value* beg,
Value* end) {
#ifdef DEBUG
for (Value* v = beg; v != end; ++v) {
*v = js::PoisonedObjectValue(0x48);
}
#endif
}
static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch(Value* vec,
size_t len) {
#ifdef DEBUG
Debug_SetValueRangeToCrashOnTouch(vec, vec + len);
#endif
}
static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch(GCPtrValue* vec,
size_t len) {
#ifdef DEBUG
Debug_SetValueRangeToCrashOnTouch((Value*)vec, len);
#endif
}
static MOZ_ALWAYS_INLINE void Debug_SetSlotRangeToCrashOnTouch(HeapSlot* vec,
uint32_t len) {
#ifdef DEBUG
Debug_SetValueRangeToCrashOnTouch((Value*)vec, len);
#endif
}
static MOZ_ALWAYS_INLINE void Debug_SetSlotRangeToCrashOnTouch(HeapSlot* begin,
HeapSlot* end) {
#ifdef DEBUG
Debug_SetValueRangeToCrashOnTouch((Value*)begin, end - begin);
#endif
}
class ArrayObject;
extern bool ArraySetLength(JSContext* cx, Handle<ArrayObject*> obj, HandleId id,
unsigned attrs, HandleValue value,
ObjectOpResult& result);
class ObjectElements {
public:
enum Flags : uint16_t {
CONVERT_DOUBLE_ELEMENTS = 0x1,
NONWRITABLE_ARRAY_LENGTH = 0x2,
COPY_ON_WRITE = 0x4,
SHARED_MEMORY = 0x8,
SEALED = 0x10,
FROZEN = 0x20,
};
static const size_t NumShiftedElementsBits = 11;
static const size_t MaxShiftedElements = (1 << NumShiftedElementsBits) - 1;
static const size_t NumShiftedElementsShift = 32 - NumShiftedElementsBits;
static const size_t FlagsMask = (1 << NumShiftedElementsShift) - 1;
static_assert(MaxShiftedElements == 2047,
"MaxShiftedElements should match the comment");
private:
friend class ::JSObject;
friend class ArrayObject;
friend class NativeObject;
friend class TenuringTracer;
friend bool js::SetIntegrityLevel(JSContext* cx, HandleObject obj,
IntegrityLevel level);
friend bool ArraySetLength(JSContext* cx, Handle<ArrayObject*> obj,
HandleId id, unsigned attrs, HandleValue value,
ObjectOpResult& result);
uint32_t flags;
uint32_t initializedLength;
uint32_t capacity;
uint32_t length;
bool shouldConvertDoubleElements() const {
return flags & CONVERT_DOUBLE_ELEMENTS;
}
void setShouldConvertDoubleElements() {
flags |= CONVERT_DOUBLE_ELEMENTS;
}
void clearShouldConvertDoubleElements() {
MOZ_ASSERT(!isCopyOnWrite());
flags &= ~CONVERT_DOUBLE_ELEMENTS;
}
bool hasNonwritableArrayLength() const {
return flags & NONWRITABLE_ARRAY_LENGTH;
}
void setNonwritableArrayLength() {
MOZ_ASSERT(capacity == initializedLength);
MOZ_ASSERT(numShiftedElements() == 0);
MOZ_ASSERT(!isCopyOnWrite());
flags |= NONWRITABLE_ARRAY_LENGTH;
}
bool isCopyOnWrite() const { return flags & COPY_ON_WRITE; }
void clearCopyOnWrite() {
MOZ_ASSERT(isCopyOnWrite());
flags &= ~COPY_ON_WRITE;
}
void addShiftedElements(uint32_t count) {
MOZ_ASSERT(count < capacity);
MOZ_ASSERT(count < initializedLength);
MOZ_ASSERT(!(flags &
(NONWRITABLE_ARRAY_LENGTH | SEALED | FROZEN | COPY_ON_WRITE)));
uint32_t numShifted = numShiftedElements() + count;
MOZ_ASSERT(numShifted <= MaxShiftedElements);
flags = (numShifted << NumShiftedElementsShift) | (flags & FlagsMask);
capacity -= count;
initializedLength -= count;
}
void unshiftShiftedElements(uint32_t count) {
MOZ_ASSERT(count > 0);
MOZ_ASSERT(!(flags &
(NONWRITABLE_ARRAY_LENGTH | SEALED | FROZEN | COPY_ON_WRITE)));
uint32_t numShifted = numShiftedElements();
MOZ_ASSERT(count <= numShifted);
numShifted -= count;
flags = (numShifted << NumShiftedElementsShift) | (flags & FlagsMask);
capacity += count;
initializedLength += count;
}
void clearShiftedElements() {
flags &= FlagsMask;
MOZ_ASSERT(numShiftedElements() == 0);
}
void seal() {
MOZ_ASSERT(!isSealed());
MOZ_ASSERT(!isFrozen());
MOZ_ASSERT(!isCopyOnWrite());
flags |= SEALED;
}
void freeze() {
MOZ_ASSERT(isSealed());
MOZ_ASSERT(!isFrozen());
MOZ_ASSERT(!isCopyOnWrite());
flags |= FROZEN;
}
public:
constexpr ObjectElements(uint32_t capacity, uint32_t length)
: flags(0), initializedLength(0), capacity(capacity), length(length) {}
enum class SharedMemory { IsShared };
constexpr ObjectElements(uint32_t capacity, uint32_t length,
SharedMemory shmem)
: flags(SHARED_MEMORY),
initializedLength(0),
capacity(capacity),
length(length) {}
HeapSlot* elements() {
return reinterpret_cast<HeapSlot*>(uintptr_t(this) +
sizeof(ObjectElements));
}
const HeapSlot* elements() const {
return reinterpret_cast<const HeapSlot*>(uintptr_t(this) +
sizeof(ObjectElements));
}
static ObjectElements* fromElements(HeapSlot* elems) {
return reinterpret_cast<ObjectElements*>(uintptr_t(elems) -
sizeof(ObjectElements));
}
bool isSharedMemory() const { return flags & SHARED_MEMORY; }
GCPtrNativeObject& ownerObject() const {
MOZ_ASSERT(isCopyOnWrite());
return *(GCPtrNativeObject*)(&elements()[initializedLength]);
}
static int offsetOfFlags() {
return int(offsetof(ObjectElements, flags)) - int(sizeof(ObjectElements));
}
static int offsetOfInitializedLength() {
return int(offsetof(ObjectElements, initializedLength)) -
int(sizeof(ObjectElements));
}
static int offsetOfCapacity() {
return int(offsetof(ObjectElements, capacity)) -
int(sizeof(ObjectElements));
}
static int offsetOfLength() {
return int(offsetof(ObjectElements, length)) - int(sizeof(ObjectElements));
}
static void ConvertElementsToDoubles(JSContext* cx, uintptr_t elements);
static bool MakeElementsCopyOnWrite(JSContext* cx, NativeObject* obj);
static MOZ_MUST_USE bool PreventExtensions(JSContext* cx, NativeObject* obj);
static void FreezeOrSeal(JSContext* cx, NativeObject* obj,
IntegrityLevel level);
bool isSealed() const { return flags & SEALED; }
bool isFrozen() const { return flags & FROZEN; }
uint8_t elementAttributes() const {
if (isFrozen()) {
return JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;
}
if (isSealed()) {
return JSPROP_ENUMERATE | JSPROP_PERMANENT;
}
return JSPROP_ENUMERATE;
}
uint32_t numShiftedElements() const {
uint32_t numShifted = flags >> NumShiftedElementsShift;
MOZ_ASSERT_IF(numShifted > 0, !(flags & (NONWRITABLE_ARRAY_LENGTH | SEALED |
FROZEN | COPY_ON_WRITE)));
return numShifted;
}
uint32_t numAllocatedElements() const {
return VALUES_PER_HEADER + capacity + numShiftedElements();
}
static const size_t VALUES_PER_HEADER = 2;
};
static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) ==
sizeof(ObjectElements),
"ObjectElements doesn't fit in the given number of slots");
extern HeapSlot* const emptyObjectElements;
extern HeapSlot* const emptyObjectElementsShared;
struct Class;
class AutoCheckShapeConsistency;
class GCMarker;
class Shape;
class NewObjectCache;
enum class DenseElementResult { Failure, Success, Incomplete };
enum class ShouldUpdateTypes { Update, DontUpdate };
class NativeObject : public ShapedObject {
protected:
js::HeapSlot* slots_;
js::HeapSlot* elements_;
friend class ::JSObject;
private:
static void staticAsserts() {
static_assert(sizeof(NativeObject) == sizeof(JSObject_Slots0),
"native object size must match GC thing size");
static_assert(sizeof(NativeObject) == sizeof(shadow::Object),
"shadow interface must match actual implementation");
static_assert(sizeof(NativeObject) % sizeof(Value) == 0,
"fixed slots after an object must be aligned");
static_assert(
offsetof(NativeObject, group_) == offsetof(shadow::Object, group),
"shadow type must match actual type");
static_assert(
offsetof(NativeObject, slots_) == offsetof(shadow::Object, slots),
"shadow slots must match actual slots");
static_assert(
offsetof(NativeObject, elements_) == offsetof(shadow::Object, _1),
"shadow placeholder must match actual elements");
static_assert(MAX_FIXED_SLOTS <= Shape::FIXED_SLOTS_MAX,
"verify numFixedSlots() bitfield is big enough");
static_assert(sizeof(NativeObject) + MAX_FIXED_SLOTS * sizeof(Value) ==
JSObject::MAX_BYTE_SIZE,
"inconsistent maximum object size");
}
public:
Shape* lastProperty() const {
MOZ_ASSERT(shape());
return shape();
}
uint32_t propertyCount() const { return lastProperty()->entryCount(); }
bool hasShapeTable() const { return lastProperty()->hasTable(); }
bool hasShapeIC() const { return lastProperty()->hasIC(); }
HeapSlotArray getDenseElements() {
return HeapSlotArray(elements_, !getElementsHeader()->isCopyOnWrite());
}
HeapSlotArray getDenseElementsAllowCopyOnWrite() {
return HeapSlotArray(elements_, true);
}
const Value& getDenseElement(uint32_t idx) const {
MOZ_ASSERT(idx < getDenseInitializedLength());
return elements_[idx];
}
bool containsDenseElement(uint32_t idx) {
return idx < getDenseInitializedLength() &&
!elements_[idx].isMagic(JS_ELEMENTS_HOLE);
}
uint32_t getDenseInitializedLength() const {
return getElementsHeader()->initializedLength;
}
uint32_t getDenseCapacity() const { return getElementsHeader()->capacity; }
bool isSharedMemory() const { return getElementsHeader()->isSharedMemory(); }
MOZ_ALWAYS_INLINE bool setLastProperty(JSContext* cx, Shape* shape);
void setLastPropertyShrinkFixedSlots(Shape* shape);
void setLastPropertyMakeNonNative(Shape* shape);
void setLastPropertyMakeNative(JSContext* cx, Shape* shape);
void setIsSharedMemory() {
MOZ_ASSERT(elements_ == emptyObjectElements);
elements_ = emptyObjectElementsShared;
}
inline bool isInWholeCellBuffer() const;
static inline JS::Result<NativeObject*, JS::OOM&> create(
JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
js::HandleShape shape, js::HandleObjectGroup group);
static inline JS::Result<NativeObject*, JS::OOM&> createWithTemplate(
JSContext* cx, js::gc::InitialHeap heap, HandleObject templateObject);
#ifdef DEBUG
static void enableShapeConsistencyChecks();
#endif
protected:
#ifdef DEBUG
friend class js::AutoCheckShapeConsistency;
void checkShapeConsistency();
#else
void checkShapeConsistency() {}
#endif
static Shape* replaceWithNewEquivalentShape(JSContext* cx,
HandleNativeObject obj,
Shape* existingShape,
Shape* newShape = nullptr,
bool accessorShape = false);
inline void removeLastProperty(JSContext* cx);
inline bool canRemoveLastProperty();
bool setSlotSpan(JSContext* cx, uint32_t span);
static MOZ_MUST_USE bool toDictionaryMode(JSContext* cx,
HandleNativeObject obj);
private:
friend class TenuringTracer;
void getSlotRangeUnchecked(uint32_t start, uint32_t length,
HeapSlot** fixedStart, HeapSlot** fixedEnd,
HeapSlot** slotsStart, HeapSlot** slotsEnd) {
MOZ_ASSERT(start + length >= start);
uint32_t fixed = numFixedSlots();
if (start < fixed) {
if (start + length < fixed) {
*fixedStart = &fixedSlots()[start];
*fixedEnd = &fixedSlots()[start + length];
*slotsStart = *slotsEnd = nullptr;
} else {
uint32_t localCopy = fixed - start;
*fixedStart = &fixedSlots()[start];
*fixedEnd = &fixedSlots()[start + localCopy];
*slotsStart = &slots_[0];
*slotsEnd = &slots_[length - localCopy];
}
} else {
*fixedStart = *fixedEnd = nullptr;
*slotsStart = &slots_[start - fixed];
*slotsEnd = &slots_[start - fixed + length];
}
}
void getSlotRange(uint32_t start, uint32_t length, HeapSlot** fixedStart,
HeapSlot** fixedEnd, HeapSlot** slotsStart,
HeapSlot** slotsEnd) {
MOZ_ASSERT(slotInRange(start + length, SENTINEL_ALLOWED));
getSlotRangeUnchecked(start, length, fixedStart, fixedEnd, slotsStart,
slotsEnd);
}
protected:
friend class GCMarker;
friend class Shape;
friend class NewObjectCache;
void invalidateSlotRange(uint32_t start, uint32_t length) {
#ifdef DEBUG
HeapSlot* fixedStart;
HeapSlot* fixedEnd;
HeapSlot* slotsStart;
HeapSlot* slotsEnd;
getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
Debug_SetSlotRangeToCrashOnTouch(fixedStart, fixedEnd);
Debug_SetSlotRangeToCrashOnTouch(slotsStart, slotsEnd);
#endif
}
void initializeSlotRange(uint32_t start, uint32_t count);
void initSlotRange(uint32_t start, const Value* vector, uint32_t length);
#ifdef DEBUG
enum SentinelAllowed{SENTINEL_NOT_ALLOWED, SENTINEL_ALLOWED};
bool slotInRange(uint32_t slot,
SentinelAllowed sentinel = SENTINEL_NOT_ALLOWED) const;
bool slotIsFixed(uint32_t slot) const;
bool isNumFixedSlots(uint32_t nfixed) const;
#endif
static const uint32_t SLOT_CAPACITY_MIN = 8;
HeapSlot* fixedSlots() const {
return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(NativeObject));
}
public:
void initSlots(HeapSlot* slots) { slots_ = slots; }
static MOZ_MUST_USE bool generateOwnShape(JSContext* cx,
HandleNativeObject obj,
Shape* newShape = nullptr) {
return replaceWithNewEquivalentShape(cx, obj, obj->lastProperty(),
newShape);
}
static MOZ_MUST_USE bool reshapeForShadowedProp(JSContext* cx,
HandleNativeObject obj);
static MOZ_MUST_USE bool reshapeForProtoMutation(JSContext* cx,
HandleNativeObject obj);
static bool clearFlag(JSContext* cx, HandleNativeObject obj,
BaseShape::Flag flag);
static const uint32_t MAX_SLOTS_COUNT = (1 << 28) - 1;
static void slotsSizeMustNotOverflow() {
static_assert(
NativeObject::MAX_SLOTS_COUNT <= INT32_MAX / sizeof(JS::Value),
"every caller of this method requires that a slot "
"number (or slot count) count multiplied by "
"sizeof(Value) can't overflow uint32_t (and sometimes "
"int32_t, too)");
}
uint32_t numFixedSlots() const {
return reinterpret_cast<const shadow::Object*>(this)->numFixedSlots();
}
inline uint32_t numFixedSlotsMaybeForwarded() const;
uint32_t numUsedFixedSlots() const {
uint32_t nslots = lastProperty()->slotSpan(getClass());
return Min(nslots, numFixedSlots());
}
uint32_t slotSpan() const {
if (inDictionaryMode()) {
return lastProperty()->base()->slotSpan();
}
return lastProperty()->slotSpan();
}
bool isFixedSlot(size_t slot) { return slot < numFixedSlots(); }
size_t dynamicSlotIndex(size_t slot) {
MOZ_ASSERT(slot >= numFixedSlots());
return slot - numFixedSlots();
}
bool hasAllFlags(js::BaseShape::Flag flags) const {
MOZ_ASSERT(flags);
return shape()->hasAllObjectFlags(flags);
}
bool nonProxyIsExtensible() const = delete;
bool isExtensible() const {
return !hasAllFlags(js::BaseShape::NOT_EXTENSIBLE);
}
bool isIndexed() const { return hasAllFlags(js::BaseShape::INDEXED); }
static bool setHadElementsAccess(JSContext* cx, HandleNativeObject obj) {
return setFlags(cx, obj, js::BaseShape::HAD_ELEMENTS_ACCESS);
}
bool hadElementsAccess() const {
return hasAllFlags(js::BaseShape::HAD_ELEMENTS_ACCESS);
}
bool hasInterestingSymbol() const {
return hasAllFlags(js::BaseShape::HAS_INTERESTING_SYMBOL);
}
bool growSlots(JSContext* cx, uint32_t oldCount, uint32_t newCount);
void shrinkSlots(JSContext* cx, uint32_t oldCount, uint32_t newCount);
static bool growSlotsPure(JSContext* cx, NativeObject* obj,
uint32_t newCount);
static bool addDenseElementPure(JSContext* cx, NativeObject* obj);
bool hasDynamicSlots() const { return !!slots_; }
MOZ_ALWAYS_INLINE uint32_t numDynamicSlots() const;
bool empty() const { return lastProperty()->isEmptyShape(); }
Shape* lookup(JSContext* cx, jsid id);
Shape* lookup(JSContext* cx, PropertyName* name) {
return lookup(cx, NameToId(name));
}
bool contains(JSContext* cx, jsid id) { return lookup(cx, id) != nullptr; }
bool contains(JSContext* cx, PropertyName* name) {
return lookup(cx, name) != nullptr;
}
bool contains(JSContext* cx, Shape* shape) {
return lookup(cx, shape->propid()) == shape;
}
bool containsShapeOrElement(JSContext* cx, jsid id) {
if (JSID_IS_INT(id) && containsDenseElement(JSID_TO_INT(id))) {
return true;
}
return contains(cx, id);
}
Shape* lookupPure(jsid id);
Shape* lookupPure(PropertyName* name) { return lookupPure(NameToId(name)); }
bool containsPure(jsid id) { return lookupPure(id) != nullptr; }
bool containsPure(PropertyName* name) { return containsPure(NameToId(name)); }
bool containsPure(Shape* shape) {
return lookupPure(shape->propid()) == shape;
}
static bool allocDictionarySlot(JSContext* cx, HandleNativeObject obj,
uint32_t* slotp);
void freeSlot(JSContext* cx, uint32_t slot);
private:
static MOZ_ALWAYS_INLINE Shape* getChildDataProperty(
JSContext* cx, HandleNativeObject obj, HandleShape parent,
MutableHandle<StackShape> child);
static MOZ_ALWAYS_INLINE Shape* getChildAccessorProperty(
JSContext* cx, HandleNativeObject obj, HandleShape parent,
MutableHandle<StackShape> child);
static MOZ_ALWAYS_INLINE bool maybeConvertToOrGrowDictionaryForAdd(
JSContext* cx, HandleNativeObject obj, HandleId id, ShapeTable** table,
ShapeTable::Entry** entry, const AutoKeepShapeCaches& keep);
static bool maybeToDictionaryModeForPut(JSContext* cx, HandleNativeObject obj,
MutableHandleShape shape);
public:
static MOZ_ALWAYS_INLINE Shape* addDataProperty(JSContext* cx,
HandleNativeObject obj,
HandleId id, uint32_t slot,
unsigned attrs);
static MOZ_ALWAYS_INLINE Shape* addAccessorProperty(
JSContext* cx, HandleNativeObject obj, HandleId id, JSGetterOp getter,
JSSetterOp setter, unsigned attrs);
static Shape* addEnumerableDataProperty(JSContext* cx, HandleNativeObject obj,
HandleId id);
static Shape* addDataProperty(JSContext* cx, HandleNativeObject obj,
HandlePropertyName name, uint32_t slot,
unsigned attrs);
static Shape* putDataProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, unsigned attrs);
static Shape* putAccessorProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, JSGetterOp getter,
JSSetterOp setter, unsigned attrs);
static Shape* changeProperty(JSContext* cx, HandleNativeObject obj,
HandleShape shape, unsigned attrs,
JSGetterOp getter, JSSetterOp setter);
static bool removeProperty(JSContext* cx, HandleNativeObject obj, jsid id);
static void clear(JSContext* cx, HandleNativeObject obj);
protected:
static Shape* addDataPropertyInternal(JSContext* cx, HandleNativeObject obj,
HandleId id, uint32_t slot,
unsigned attrs, ShapeTable* table,
ShapeTable::Entry* entry,
const AutoKeepShapeCaches& keep);
static Shape* addAccessorPropertyInternal(
JSContext* cx, HandleNativeObject obj, HandleId id, JSGetterOp getter,
JSSetterOp setter, unsigned attrs, ShapeTable* table,
ShapeTable::Entry* entry, const AutoKeepShapeCaches& keep);
static MOZ_MUST_USE bool fillInAfterSwap(JSContext* cx,
HandleNativeObject obj,
const AutoValueVector& values,
void* priv);
public:
bool inDictionaryMode() const { return lastProperty()->inDictionary(); }
const Value& getSlot(uint32_t slot) const {
MOZ_ASSERT(slotInRange(slot));
uint32_t fixed = numFixedSlots();
if (slot < fixed) {
return fixedSlots()[slot];
}
return slots_[slot - fixed];
}
const HeapSlot* getSlotAddressUnchecked(uint32_t slot) const {
uint32_t fixed = numFixedSlots();
if (slot < fixed) {
return fixedSlots() + slot;
}
return slots_ + (slot - fixed);
}
HeapSlot* getSlotAddressUnchecked(uint32_t slot) {
uint32_t fixed = numFixedSlots();
if (slot < fixed) {
return fixedSlots() + slot;
}
return slots_ + (slot - fixed);
}
HeapSlot* getSlotAddress(uint32_t slot) {
MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED));
return getSlotAddressUnchecked(slot);
}
const HeapSlot* getSlotAddress(uint32_t slot) const {
MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED));
return getSlotAddressUnchecked(slot);
}
MOZ_ALWAYS_INLINE HeapSlot& getSlotRef(uint32_t slot) {
MOZ_ASSERT(slotInRange(slot));
return *getSlotAddress(slot);
}
MOZ_ALWAYS_INLINE const HeapSlot& getSlotRef(uint32_t slot) const {
MOZ_ASSERT(slotInRange(slot));
return *getSlotAddress(slot);
}
MOZ_ALWAYS_INLINE void checkStoredValue(const Value& v) {
MOZ_ASSERT(IsObjectValueInCompartment(v, compartment()));
MOZ_ASSERT(AtomIsMarked(zoneFromAnyThread(), v));
}
MOZ_ALWAYS_INLINE void setSlot(uint32_t slot, const Value& value) {
MOZ_ASSERT(slotInRange(slot));
checkStoredValue(value);
getSlotRef(slot).set(this, HeapSlot::Slot, slot, value);
}
MOZ_ALWAYS_INLINE void initSlot(uint32_t slot, const Value& value) {
MOZ_ASSERT(getSlot(slot).isUndefined());
MOZ_ASSERT(slotInRange(slot));
checkStoredValue(value);
initSlotUnchecked(slot, value);
}
MOZ_ALWAYS_INLINE void initSlotUnchecked(uint32_t slot, const Value& value) {
getSlotAddressUnchecked(slot)->init(this, HeapSlot::Slot, slot, value);
}
static constexpr uint32_t MAX_FIXED_SLOTS = shadow::Object::MAX_FIXED_SLOTS;
protected:
MOZ_ALWAYS_INLINE bool updateSlotsForSpan(JSContext* cx, size_t oldSpan,
size_t newSpan);
private:
void prepareElementRangeForOverwrite(size_t start, size_t end) {
MOZ_ASSERT(end <= getDenseInitializedLength());
MOZ_ASSERT(!denseElementsAreCopyOnWrite());
for (size_t i = start; i < end; i++) {
elements_[i].destroy();
}
}
void prepareSlotRangeForOverwrite(size_t start, size_t end) {
for (size_t i = start; i < end; i++) {
getSlotAddressUnchecked(i)->destroy();
}
}
inline void shiftDenseElementsUnchecked(uint32_t count);
public:
static bool rollbackProperties(JSContext* cx, HandleNativeObject obj,
uint32_t slotSpan);
MOZ_ALWAYS_INLINE void setSlotWithType(JSContext* cx, Shape* shape,
const Value& value,
bool overwriting = true);
MOZ_ALWAYS_INLINE const Value& getReservedSlot(uint32_t index) const {
MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
return getSlot(index);
}
MOZ_ALWAYS_INLINE const HeapSlot& getReservedSlotRef(uint32_t index) const {
MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
return getSlotRef(index);
}
MOZ_ALWAYS_INLINE HeapSlot& getReservedSlotRef(uint32_t index) {
MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
return getSlotRef(index);
}
MOZ_ALWAYS_INLINE void initReservedSlot(uint32_t index, const Value& v) {
MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
initSlot(index, v);
}
MOZ_ALWAYS_INLINE void setReservedSlot(uint32_t index, const Value& v) {
MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
setSlot(index, v);
}
HeapSlot& getFixedSlotRef(uint32_t slot) {
MOZ_ASSERT(slotIsFixed(slot));
return fixedSlots()[slot];
}
const Value& getFixedSlot(uint32_t slot) const {
MOZ_ASSERT(slotIsFixed(slot));
return fixedSlots()[slot];
}
void setFixedSlot(uint32_t slot, const Value& value) {
MOZ_ASSERT(slotIsFixed(slot));
checkStoredValue(value);
fixedSlots()[slot].set(this, HeapSlot::Slot, slot, value);
}
void initFixedSlot(uint32_t slot, const Value& value) {
MOZ_ASSERT(slotIsFixed(slot));
checkStoredValue(value);
fixedSlots()[slot].init(this, HeapSlot::Slot, slot, value);
}
static MOZ_ALWAYS_INLINE uint32_t dynamicSlotsCount(uint32_t nfixed,
uint32_t span,
const Class* clasp);
static MOZ_ALWAYS_INLINE uint32_t dynamicSlotsCount(Shape* shape);
static const uint32_t MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1;
static const uint32_t MAX_DENSE_ELEMENTS_COUNT =
MAX_DENSE_ELEMENTS_ALLOCATION - ObjectElements::VALUES_PER_HEADER;
static void elementsSizeMustNotOverflow() {
static_assert(
NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX / sizeof(JS::Value),
"every caller of this method require that an element "
"count multiplied by sizeof(Value) can't overflow "
"uint32_t (and sometimes int32_t ,too)");
}
ObjectElements* getElementsHeader() const {
return ObjectElements::fromElements(elements_);
}
inline HeapSlot* unshiftedElements() const {
return elements_ - getElementsHeader()->numShiftedElements();
}
void* getUnshiftedElementsHeader() const {
return ObjectElements::fromElements(unshiftedElements());
}
uint32_t unshiftedIndex(uint32_t index) const {
return index + getElementsHeader()->numShiftedElements();
}
bool ensureElements(JSContext* cx, uint32_t capacity) {
MOZ_ASSERT(!denseElementsAreCopyOnWrite());
MOZ_ASSERT(isExtensible());
if (capacity > getDenseCapacity()) {
return growElements(cx, capacity);
}
return true;
}
inline bool tryShiftDenseElements(uint32_t count);
bool tryUnshiftDenseElements(uint32_t count);
void moveShiftedElements();
void maybeMoveShiftedElements();
static bool goodElementsAllocationAmount(JSContext* cx, uint32_t reqAllocated,
uint32_t length,
uint32_t* goodAmount);
bool growElements(JSContext* cx, uint32_t newcap);
void shrinkElements(JSContext* cx, uint32_t cap);
void setDynamicElements(ObjectElements* header) {
MOZ_ASSERT(!hasDynamicElements());
elements_ = header->elements();
MOZ_ASSERT(hasDynamicElements());
}
static bool CopyElementsForWrite(JSContext* cx, NativeObject* obj);
bool maybeCopyElementsForWrite(JSContext* cx) {
if (denseElementsAreCopyOnWrite()) {
return CopyElementsForWrite(cx, this);
}
return true;
}
private:
inline void ensureDenseInitializedLengthNoPackedCheck(uint32_t index,
uint32_t extra);
inline void elementsRangeWriteBarrierPost(uint32_t start, uint32_t count);
public:
void shrinkCapacityToInitializedLength(JSContext* cx) {
if (getElementsHeader()->numShiftedElements() > 0) {
moveShiftedElements();
}
ObjectElements* header = getElementsHeader();
uint32_t len = header->initializedLength;
if (header->capacity > len) {
shrinkElements(cx, len);
header = getElementsHeader();
header->capacity = len;
}
}
private:
void setDenseInitializedLengthInternal(uint32_t length) {
MOZ_ASSERT(length <= getDenseCapacity());
MOZ_ASSERT(!denseElementsAreCopyOnWrite());
MOZ_ASSERT(!denseElementsAreFrozen());
prepareElementRangeForOverwrite(length,
getElementsHeader()->initializedLength);
getElementsHeader()->initializedLength = length;
}
public:
void setDenseInitializedLength(uint32_t length) {
MOZ_ASSERT(isExtensible());
setDenseInitializedLengthInternal(length);
}
void setDenseInitializedLengthMaybeNonExtensible(JSContext* cx,
uint32_t length) {
setDenseInitializedLengthInternal(length);
if (!isExtensible()) {
shrinkCapacityToInitializedLength(cx);
}
}
inline void ensureDenseInitializedLength(JSContext* cx, uint32_t index,
uint32_t extra);
void setDenseElement(uint32_t index, const Value& val) {
MOZ_ASSERT(index < getDenseInitializedLength());
MOZ_ASSERT(!denseElementsAreCopyOnWrite());
MOZ_ASSERT(!denseElementsAreFrozen());
checkStoredValue(val);
elements_[index].set(this, HeapSlot::Element, unshiftedIndex(index), val);
}
void initDenseElement(uint32_t index, const Value& val) {
MOZ_ASSERT(index < getDenseInitializedLength());
MOZ_ASSERT(!denseElementsAreCopyOnWrite());
MOZ_ASSERT(isExtensible());
checkStoredValue(val);
elements_[index].init(this, HeapSlot::Element, unshiftedIndex(index), val);
}
void setDenseElementMaybeConvertDouble(uint32_t index, const Value& val) {
if (val.isInt32() && shouldConvertDoubleElements()) {
setDenseElement(index, DoubleValue(val.toInt32()));
} else {
setDenseElement(index, val);
}
}
private:
inline void addDenseElementType(JSContext* cx, uint32_t index,
const Value& val);
public:
inline void setDenseElementWithType(JSContext* cx, uint32_t index,
const Value& val);
inline void initDenseElementWithType(JSContext* cx, uint32_t index,
const Value& val);
inline void setDenseElementHole(JSContext* cx, uint32_t index);
inline void removeDenseElementForSparseIndex(JSContext* cx, uint32_t index);
inline Value getDenseOrTypedArrayElement(uint32_t idx);
inline void copyDenseElements(uint32_t dstStart, const Value* src,
uint32_t count);
inline void initDenseElements(const Value* src, uint32_t count);
inline void initDenseElements(NativeObject* src, uint32_t srcStart,
uint32_t count);
inline void moveDenseElements(uint32_t dstStart, uint32_t srcStart,
uint32_t count);
inline void moveDenseElementsNoPreBarrier(uint32_t dstStart,
uint32_t srcStart, uint32_t count);
inline void reverseDenseElementsNoPreBarrier(uint32_t length);
inline DenseElementResult setOrExtendDenseElements(
JSContext* cx, uint32_t start, const Value* vp, uint32_t count,
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
bool shouldConvertDoubleElements() {
return getElementsHeader()->shouldConvertDoubleElements();
}
inline void setShouldConvertDoubleElements();
inline void clearShouldConvertDoubleElements();
bool denseElementsAreCopyOnWrite() {
return getElementsHeader()->isCopyOnWrite();
}
bool denseElementsAreSealed() const {
return getElementsHeader()->isSealed();
}
bool denseElementsAreFrozen() const {
return getElementsHeader()->isFrozen();
}
inline bool writeToIndexWouldMarkNotPacked(uint32_t index);
inline void markDenseElementsNotPacked(JSContext* cx);
inline DenseElementResult ensureDenseElements(JSContext* cx, uint32_t index,
uint32_t extra);
inline DenseElementResult extendDenseElements(JSContext* cx,
uint32_t requiredCapacity,
uint32_t extra);
static const uint32_t MIN_SPARSE_INDEX = 1000;
static const unsigned SPARSE_DENSITY_RATIO = 8;
bool willBeSparseElements(uint32_t requiredCapacity,
uint32_t newElementsHint);
static DenseElementResult maybeDensifySparseElements(JSContext* cx,
HandleNativeObject obj);
inline HeapSlot* fixedElements() const {
static_assert(2 * sizeof(Value) == sizeof(ObjectElements),
"when elements are stored inline, the first two "
"slots will hold the ObjectElements header");
return &fixedSlots()[2];
}
#ifdef DEBUG
bool canHaveNonEmptyElements();
#endif
void setEmptyElements() { elements_ = emptyObjectElements; }
void setFixedElements(uint32_t numShifted = 0) {
MOZ_ASSERT(canHaveNonEmptyElements());
elements_ = fixedElements() + numShifted;
}
inline bool hasDynamicElements() const {
return !hasEmptyElements() && !hasFixedElements();
}
inline bool hasFixedElements() const {
return unshiftedElements() == fixedElements();
}
inline bool hasEmptyElements() const {
return elements_ == emptyObjectElements ||
elements_ == emptyObjectElementsShared;
}
inline uint8_t* fixedData(size_t nslots) const;
inline void privateWriteBarrierPre(void** oldval);
void privateWriteBarrierPost(void** pprivate) {
gc::Cell** cellp = reinterpret_cast<gc::Cell**>(pprivate);
MOZ_ASSERT(cellp);
MOZ_ASSERT(*cellp);
gc::StoreBuffer* storeBuffer = (*cellp)->storeBuffer();
if (storeBuffer) {
storeBuffer->putCell(cellp);
}
}
inline void*& privateRef(
uint32_t nfixed) const {
MOZ_ASSERT(isNumFixedSlots(nfixed));
MOZ_ASSERT(hasPrivate());
HeapSlot* end = &fixedSlots()[nfixed];
return *reinterpret_cast<void**>(end);
}
bool hasPrivate() const { return getClass()->hasPrivate(); }
void* getPrivate() const { return privateRef(numFixedSlots()); }
void setPrivate(void* data) {
void** pprivate = &privateRef(numFixedSlots());
privateWriteBarrierPre(pprivate);
*pprivate = data;
}
void setPrivateGCThing(gc::Cell* cell) {
#ifdef DEBUG
if (IsMarkedBlack(this)) {
JS::AssertCellIsNotGray(cell);
}
#endif
void** pprivate = &privateRef(numFixedSlots());
privateWriteBarrierPre(pprivate);
*pprivate = reinterpret_cast<void*>(cell);
privateWriteBarrierPost(pprivate);
}
void setPrivateUnbarriered(void* data) {
void** pprivate = &privateRef(numFixedSlots());
*pprivate = data;
}
void initPrivate(void* data) { privateRef(numFixedSlots()) = data; }
inline void* getPrivate(uint32_t nfixed) const { return privateRef(nfixed); }
void setPrivateUnbarriered(uint32_t nfixed, void* data) {
void** pprivate = &privateRef(nfixed);
*pprivate = data;
}
inline js::gc::AllocKind allocKindForTenure() const;
void updateShapeAfterMovingGC();
void sweepDictionaryListPointer();
void updateDictionaryListPointerAfterMinorGC(NativeObject* old);
JS::Realm* realm() const { return nonCCWRealm(); }
inline js::GlobalObject& global() const;
static size_t offsetOfElements() { return offsetof(NativeObject, elements_); }
static size_t offsetOfFixedElements() {
return sizeof(NativeObject) + sizeof(ObjectElements);
}
static constexpr size_t getFixedSlotOffset(size_t slot) {
return sizeof(NativeObject) + slot * sizeof(Value);
}
static constexpr size_t getPrivateDataOffset(size_t nfixed) {
return getFixedSlotOffset(nfixed);
}
static size_t offsetOfSlots() { return offsetof(NativeObject, slots_); }
};
class PlainObject : public NativeObject {
public:
static const js::Class class_;
inline js::gc::AllocKind allocKindForTenure() const;
};
inline void NativeObject::privateWriteBarrierPre(void** oldval) {
JS::shadow::Zone* shadowZone = this->shadowZoneFromAnyThread();
if (shadowZone->needsIncrementalBarrier() && *oldval &&
getClass()->hasTrace()) {
getClass()->doTrace(shadowZone->barrierTracer(), this);
}
}
extern bool NativeDefineProperty(JSContext* cx, HandleNativeObject obj,
HandleId id,
Handle<JS::PropertyDescriptor> desc,
ObjectOpResult& result);
extern bool NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, HandleValue value,
unsigned attrs, ObjectOpResult& result);
extern bool NativeDefineAccessorProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, GetterOp getter,
SetterOp setter, unsigned attrs);
extern bool NativeDefineAccessorProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, HandleObject getter,
HandleObject setter, unsigned attrs);
extern bool NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, HandleValue value,
unsigned attrs);
extern bool NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
PropertyName* name, HandleValue value,
unsigned attrs);
extern bool NativeHasProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, bool* foundp);
extern bool NativeGetOwnPropertyDescriptor(
JSContext* cx, HandleNativeObject obj, HandleId id,
MutableHandle<JS::PropertyDescriptor> desc);
extern bool NativeGetProperty(JSContext* cx, HandleNativeObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp);
extern bool NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj,
const Value& receiver, jsid id, Value* vp);
inline bool NativeGetProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, MutableHandleValue vp) {
RootedValue receiver(cx, ObjectValue(*obj));
return NativeGetProperty(cx, obj, receiver, id, vp);
}
extern bool NativeGetElement(JSContext* cx, HandleNativeObject obj,
HandleValue reciever, int32_t index,
MutableHandleValue vp);
bool GetSparseElementHelper(JSContext* cx, HandleArrayObject obj,
int32_t int_id, MutableHandleValue result);
bool SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result);
bool SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id,
HandleValue v, HandleValue receiver,
ObjectOpResult& result);
bool AddOrUpdateSparseElementHelper(JSContext* cx, HandleArrayObject obj,
int32_t int_id, HandleValue v, bool strict);
enum QualifiedBool { Unqualified = 0, Qualified = 1 };
template <QualifiedBool Qualified>
extern bool NativeSetProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, HandleValue v, HandleValue receiver,
ObjectOpResult& result);
extern bool NativeSetElement(JSContext* cx, HandleNativeObject obj,
uint32_t index, HandleValue v,
HandleValue receiver, ObjectOpResult& result);
extern bool NativeDeleteProperty(JSContext* cx, HandleNativeObject obj,
HandleId id, ObjectOpResult& result);
template <AllowGC allowGC>
extern bool NativeLookupOwnProperty(
JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
typename MaybeRooted<jsid, allowGC>::HandleType id,
typename MaybeRooted<PropertyResult, allowGC>::MutableHandleType propp);
extern bool NativeGetExistingProperty(JSContext* cx, HandleObject receiver,
HandleNativeObject obj, HandleShape shape,
MutableHandleValue vp);
extern bool GetNameBoundInEnvironment(JSContext* cx, HandleObject env,
HandleId id, MutableHandleValue vp);
}
template <>
inline bool JSObject::is<js::NativeObject>() const {
return isNative();
}
namespace js {
inline NativeObject* MaybeNativeObject(JSObject* obj) {
return obj ? &obj->as<NativeObject>() : nullptr;
}
bool IsPackedArray(JSObject* obj);
extern void AddPropertyTypesAfterProtoChange(JSContext* cx, NativeObject* obj,
ObjectGroup* oldGroup);
extern bool CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target,
HandleNativeObject from,
HandlePlainObject excludedItems,
bool* optimized);
extern bool CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target,
Handle<UnboxedPlainObject*> from,
HandlePlainObject excludedItems,
bool* optimized);
}
#endif