summaryrefslogtreecommitdiffstats
path: root/js/src/vm/NativeObject.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/NativeObject.h')
-rw-r--r--js/src/vm/NativeObject.h1552
1 files changed, 1552 insertions, 0 deletions
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h
new file mode 100644
index 000000000..d2c06eabc
--- /dev/null
+++ b/js/src/vm/NativeObject.h
@@ -0,0 +1,1552 @@
+/* -*- 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_NativeObject_h
+#define vm_NativeObject_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+#include <stdint.h>
+
+#include "jsfriendapi.h"
+#include "jsobj.h"
+#include "NamespaceImports.h"
+
+#include "gc/Barrier.h"
+#include "gc/Heap.h"
+#include "gc/Marking.h"
+#include "js/Value.h"
+#include "vm/Shape.h"
+#include "vm/ShapedObject.h"
+#include "vm/String.h"
+#include "vm/TypeInference.h"
+
+namespace js {
+
+class Shape;
+class TenuringTracer;
+
+/*
+ * To really poison a set of values, using 'magic' or 'undefined' isn't good
+ * enough since often these will just be ignored by buggy code (see bug 629974)
+ * in debug builds and crash in release builds. Instead, we use a safe-for-crash
+ * pointer.
+ */
+static MOZ_ALWAYS_INLINE void
+Debug_SetValueRangeToCrashOnTouch(Value* beg, Value* end)
+{
+#ifdef DEBUG
+ for (Value* v = beg; v != end; ++v)
+ v->setObject(*reinterpret_cast<JSObject*>(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;
+
+/*
+ * ES6 20130308 draft 8.4.2.4 ArraySetLength.
+ *
+ * |id| must be "length", |attrs| are the attributes to be used for the newly-
+ * changed length property, |value| is the value for the new length, and
+ * |result| receives an error code if the change is invalid.
+ */
+extern bool
+ArraySetLength(JSContext* cx, Handle<ArrayObject*> obj, HandleId id,
+ unsigned attrs, HandleValue value, ObjectOpResult& result);
+
+/*
+ * Elements header used for native objects. The elements component of such objects
+ * offers an efficient representation for all or some of the indexed properties
+ * of the object, using a flat array of Values rather than a shape hierarchy
+ * stored in the object's slots. This structure is immediately followed by an
+ * array of elements, with the elements member in an object pointing to the
+ * beginning of that array (the end of this structure).
+ * See below for usage of this structure.
+ *
+ * The sets of properties represented by an object's elements and slots
+ * are disjoint. The elements contain only indexed properties, while the slots
+ * can contain both named and indexed properties; any indexes in the slots are
+ * distinct from those in the elements. If isIndexed() is false for an object,
+ * all indexed properties (if any) are stored in the dense elements.
+ *
+ * Indexes will be stored in the object's slots instead of its elements in
+ * the following case:
+ * - there are more than MIN_SPARSE_INDEX slots total and the load factor
+ * (COUNT / capacity) is less than 0.25
+ * - a property is defined that has non-default property attributes.
+ *
+ * We track these pieces of metadata for dense elements:
+ * - The length property as a uint32_t, accessible for array objects with
+ * ArrayObject::{length,setLength}(). This is unused for non-arrays.
+ * - The number of element slots (capacity), gettable with
+ * getDenseCapacity().
+ * - The array's initialized length, accessible with
+ * getDenseInitializedLength().
+ *
+ * Holes in the array are represented by MagicValue(JS_ELEMENTS_HOLE) values.
+ * These indicate indexes which are not dense properties of the array. The
+ * property may, however, be held by the object's properties.
+ *
+ * The capacity and length of an object's elements are almost entirely
+ * unrelated! In general the length may be greater than, less than, or equal
+ * to the capacity. The first case occurs with |new Array(100)|. The length
+ * is 100, but the capacity remains 0 (indices below length and above capacity
+ * must be treated as holes) until elements between capacity and length are
+ * set. The other two cases are common, depending upon the number of elements
+ * in an array and the underlying allocator used for element storage.
+ *
+ * The only case in which the capacity and length of an object's elements are
+ * related is when the object is an array with non-writable length. In this
+ * case the capacity is always less than or equal to the length. This permits
+ * JIT code to optimize away the check for non-writable length when assigning
+ * to possibly out-of-range elements: such code already has to check for
+ * |index < capacity|, and fallback code checks for non-writable length.
+ *
+ * The initialized length of an object specifies the number of elements that
+ * have been initialized. All elements above the initialized length are
+ * holes in the object, and the memory for all elements between the initialized
+ * length and capacity is left uninitialized. The initialized length is some
+ * value less than or equal to both the object's length and the object's
+ * capacity.
+ *
+ * There is flexibility in exactly the value the initialized length must hold,
+ * e.g. if an array has length 5, capacity 10, completely empty, it is valid
+ * for the initialized length to be any value between zero and 5, as long as
+ * the in memory values below the initialized length have been initialized with
+ * a hole value. However, in such cases we want to keep the initialized length
+ * as small as possible: if the object is known to have no hole values below
+ * its initialized length, then it is "packed" and can be accessed much faster
+ * by JIT code.
+ *
+ * Elements do not track property creation order, so enumerating the elements
+ * of an object does not necessarily visit indexes in the order they were
+ * created.
+ */
+class ObjectElements
+{
+ public:
+ enum Flags: uint32_t {
+ // Integers written to these elements must be converted to doubles.
+ CONVERT_DOUBLE_ELEMENTS = 0x1,
+
+ // Present only if these elements correspond to an array with
+ // non-writable length; never present for non-arrays.
+ NONWRITABLE_ARRAY_LENGTH = 0x2,
+
+ // These elements are shared with another object and must be copied
+ // before they can be changed. A pointer to the original owner of the
+ // elements, which is immutable, is stored immediately after the
+ // elements data. There is one case where elements can be written to
+ // before being copied: when setting the CONVERT_DOUBLE_ELEMENTS flag
+ // the shared elements may change (from ints to doubles) without
+ // making a copy first.
+ COPY_ON_WRITE = 0x4,
+
+ // For TypedArrays only: this TypedArray's storage is mapping shared
+ // memory. This is a static property of the TypedArray, set when it
+ // is created and never changed.
+ SHARED_MEMORY = 0x8,
+
+ // These elements are set to integrity level "frozen".
+ FROZEN = 0x10,
+ };
+
+ 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);
+
+ /* See Flags enum above. */
+ uint32_t flags;
+
+ /*
+ * Number of initialized elements. This is <= the capacity, and for arrays
+ * is <= the length. Memory for elements above the initialized length is
+ * uninitialized, but values between the initialized length and the proper
+ * length are conceptually holes.
+ */
+ uint32_t initializedLength;
+
+ /* Number of allocated slots. */
+ uint32_t capacity;
+
+ /* 'length' property of array objects, unused for other objects. */
+ uint32_t length;
+
+ bool shouldConvertDoubleElements() const {
+ return flags & CONVERT_DOUBLE_ELEMENTS;
+ }
+ void setShouldConvertDoubleElements() {
+ // Note: allow isCopyOnWrite() here, see comment above.
+ 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(!isCopyOnWrite());
+ flags |= NONWRITABLE_ARRAY_LENGTH;
+ }
+ bool isCopyOnWrite() const {
+ return flags & COPY_ON_WRITE;
+ }
+ void clearCopyOnWrite() {
+ MOZ_ASSERT(isCopyOnWrite());
+ flags &= ~COPY_ON_WRITE;
+ }
+
+ 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 bool ConvertElementsToDoubles(JSContext* cx, uintptr_t elements);
+ static bool MakeElementsCopyOnWrite(ExclusiveContext* cx, NativeObject* obj);
+ static bool FreezeElements(ExclusiveContext* cx, HandleNativeObject obj);
+
+ bool isFrozen() const {
+ return flags & FROZEN;
+ }
+ void freeze() {
+ MOZ_ASSERT(!isFrozen());
+ MOZ_ASSERT(!isCopyOnWrite());
+ flags |= FROZEN;
+ }
+ void markNotFrozen() {
+ MOZ_ASSERT(isFrozen());
+ MOZ_ASSERT(!isCopyOnWrite());
+ flags &= ~FROZEN;
+ }
+
+ uint8_t elementAttributes() const {
+ if (isFrozen())
+ return JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;
+ return JSPROP_ENUMERATE;
+ }
+
+ // This is enough slots to store an object of this class. See the static
+ // assertion below.
+ 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");
+
+/*
+ * Shared singletons for objects with no elements.
+ * emptyObjectElementsShared is used only for TypedArrays, when the TA
+ * maps shared memory.
+ */
+extern HeapSlot* const emptyObjectElements;
+extern HeapSlot* const emptyObjectElementsShared;
+
+struct Class;
+class GCMarker;
+class Shape;
+
+class NewObjectCache;
+
+#ifdef DEBUG
+static inline bool
+IsObjectValueInCompartment(const Value& v, JSCompartment* comp);
+#endif
+
+// Operations which change an object's dense elements can either succeed, fail,
+// or be unable to complete. For native objects, the latter is used when the
+// object's elements must become sparse instead. The enum below is used for
+// such operations, and for similar operations on unboxed arrays and methods
+// that work on both kinds of objects.
+enum class DenseElementResult {
+ Failure,
+ Success,
+ Incomplete
+};
+
+/*
+ * NativeObject specifies the internal implementation of a native object.
+ *
+ * Native objects use ShapedObject::shape_ to record property information. Two
+ * native objects with the same shape are guaranteed to have the same number of
+ * fixed slots.
+ *
+ * Native objects extend the base implementation of an object with storage for
+ * the object's named properties and indexed elements.
+ *
+ * These are stored separately from one another. Objects are followed by a
+ * variable-sized array of values for inline storage, which may be used by
+ * either properties of native objects (fixed slots), by elements (fixed
+ * elements), or by other data for certain kinds of objects, such as
+ * ArrayBufferObjects and TypedArrayObjects.
+ *
+ * Named property storage can be split between fixed slots and a dynamically
+ * allocated array (the slots member). For an object with N fixed slots, shapes
+ * with slots [0..N-1] are stored in the fixed slots, and the remainder are
+ * stored in the dynamic array. If all properties fit in the fixed slots, the
+ * 'slots_' member is nullptr.
+ *
+ * Elements are indexed via the 'elements_' member. This member can point to
+ * either the shared emptyObjectElements and emptyObjectElementsShared singletons,
+ * into the inline value array (the address of the third value, to leave room
+ * for a ObjectElements header;in this case numFixedSlots() is zero) or to
+ * a dynamically allocated array.
+ *
+ * Slots and elements may both be non-empty. The slots may be either names or
+ * indexes; no indexed property will be in both the slots and elements.
+ */
+class NativeObject : public ShapedObject
+{
+ protected:
+ /* Slots for object properties. */
+ js::HeapSlot* slots_;
+
+ /* Slots for object dense elements. */
+ 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();
+ }
+
+ HeapSlotArray getDenseElements() {
+ return HeapSlotArray(elements_, !getElementsHeader()->isCopyOnWrite());
+ }
+ HeapSlotArray getDenseElementsAllowCopyOnWrite() {
+ // Backdoor allowing direct access to copy on write elements.
+ 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();
+ }
+
+ // Update the last property, keeping the number of allocated slots in sync
+ // with the object's new slot span.
+ bool setLastProperty(ExclusiveContext* cx, Shape* shape);
+
+ // As for setLastProperty(), but allows the number of fixed slots to
+ // change. This can only be used when fixed slots are being erased from the
+ // object, and only when the object will not require dynamic slots to cover
+ // the new properties.
+ void setLastPropertyShrinkFixedSlots(Shape* shape);
+
+ // As for setLastProperty(), but changes the class associated with the
+ // object to a non-native one. This leaves the object with a type and shape
+ // that are (temporarily) inconsistent.
+ void setLastPropertyMakeNonNative(Shape* shape);
+
+ // As for setLastProperty(), but changes the class associated with the
+ // object to a native one. The object's type has already been changed, and
+ // this brings the shape into sync with it.
+ void setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape);
+
+ // Newly-created TypedArrays that map a SharedArrayBuffer are
+ // marked as shared by giving them an ObjectElements that has the
+ // ObjectElements::SHARED_MEMORY flag set.
+ void setIsSharedMemory() {
+ MOZ_ASSERT(elements_ == emptyObjectElements);
+ elements_ = emptyObjectElementsShared;
+ }
+
+ bool isInWholeCellBuffer() const {
+ const gc::TenuredCell* cell = &asTenured();
+ gc::ArenaCellSet* cells = cell->arena()->bufferedCells;
+ return cells && cells->hasCell(cell);
+ }
+
+ protected:
+#ifdef DEBUG
+ void checkShapeConsistency();
+#else
+ void checkShapeConsistency() { }
+#endif
+
+ Shape*
+ replaceWithNewEquivalentShape(ExclusiveContext* cx,
+ Shape* existingShape, Shape* newShape = nullptr,
+ bool accessorShape = false);
+
+ /*
+ * Remove the last property of an object, provided that it is safe to do so
+ * (the shape and previous shape do not carry conflicting information about
+ * the object itself).
+ */
+ inline void removeLastProperty(ExclusiveContext* cx);
+ inline bool canRemoveLastProperty();
+
+ /*
+ * Update the slot span directly for a dictionary object, and allocate
+ * slots to cover the new span if necessary.
+ */
+ bool setSlotSpan(ExclusiveContext* cx, uint32_t span);
+
+ bool toDictionaryMode(ExclusiveContext* cx);
+
+ private:
+ friend class TenuringTracer;
+
+ /*
+ * Get internal pointers to the range of values starting at start and
+ * running for length.
+ */
+ 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 /* DEBUG */
+ }
+
+ void initializeSlotRange(uint32_t start, uint32_t count);
+
+ /*
+ * Initialize a flat array of slots to this object at a start slot. The
+ * caller must ensure that are enough slots.
+ */
+ void initSlotRange(uint32_t start, const Value* vector, uint32_t length);
+
+ /*
+ * Copy a flat array of slots to this object at a start slot. Caller must
+ * ensure there are enough slots in this object.
+ */
+ void copySlotRange(uint32_t start, const Value* vector, uint32_t length);
+
+#ifdef DEBUG
+ enum SentinelAllowed {
+ SENTINEL_NOT_ALLOWED,
+ SENTINEL_ALLOWED
+ };
+
+ /*
+ * Check that slot is in range for the object's allocated slots.
+ * If sentinelAllowed then slot may equal the slot capacity.
+ */
+ bool slotInRange(uint32_t slot, SentinelAllowed sentinel = SENTINEL_NOT_ALLOWED) const;
+#endif
+
+ /*
+ * Minimum size for dynamically allocated slots in normal Objects.
+ * ArrayObjects don't use this limit and can have a lower slot capacity,
+ * since they normally don't have a lot of slots.
+ */
+ static const uint32_t SLOT_CAPACITY_MIN = 8;
+
+ HeapSlot* fixedSlots() const {
+ return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(NativeObject));
+ }
+
+ public:
+ bool generateOwnShape(ExclusiveContext* cx, Shape* newShape = nullptr) {
+ return replaceWithNewEquivalentShape(cx, lastProperty(), newShape);
+ }
+
+ bool shadowingShapeChange(ExclusiveContext* cx, const Shape& shape);
+ bool clearFlag(ExclusiveContext* cx, BaseShape::Flag flag);
+
+ // The maximum number of slots in an object.
+ // |MAX_SLOTS_COUNT * sizeof(JS::Value)| shouldn't overflow
+ // int32_t (see slotsSizeMustNotOverflow).
+ 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();
+ }
+ uint32_t numUsedFixedSlots() const {
+ uint32_t nslots = lastProperty()->slotSpan(getClass());
+ return Min(nslots, numFixedSlots());
+ }
+ uint32_t numFixedSlotsForCompilation() const;
+
+ uint32_t slotSpan() const {
+ if (inDictionaryMode())
+ return lastProperty()->base()->slotSpan();
+ return lastProperty()->slotSpan();
+ }
+
+ /* Whether a slot is at a fixed offset from this object. */
+ bool isFixedSlot(size_t slot) {
+ return slot < numFixedSlots();
+ }
+
+ /* Index into the dynamic slots array to use for a dynamic slot. */
+ size_t dynamicSlotIndex(size_t slot) {
+ MOZ_ASSERT(slot >= numFixedSlots());
+ return slot - numFixedSlots();
+ }
+
+ /*
+ * Grow or shrink slots immediately before changing the slot span.
+ * The number of allocated slots is not stored explicitly, and changes to
+ * the slots must track changes in the slot span.
+ */
+ bool growSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount);
+ void shrinkSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount);
+
+ /*
+ * This method is static because it's called from JIT code. On OOM, returns
+ * false without leaving a pending exception on the context.
+ */
+ static bool growSlotsDontReportOOM(ExclusiveContext* cx, NativeObject* obj, uint32_t newCount);
+
+ bool hasDynamicSlots() const { return !!slots_; }
+
+ /* Compute dynamicSlotsCount() for this object. */
+ uint32_t numDynamicSlots() const {
+ return dynamicSlotsCount(numFixedSlots(), slotSpan(), getClass());
+ }
+
+ bool empty() const {
+ return lastProperty()->isEmptyShape();
+ }
+
+ Shape* lookup(ExclusiveContext* cx, jsid id);
+ Shape* lookup(ExclusiveContext* cx, PropertyName* name) {
+ return lookup(cx, NameToId(name));
+ }
+
+ bool contains(ExclusiveContext* cx, jsid id) {
+ return lookup(cx, id) != nullptr;
+ }
+ bool contains(ExclusiveContext* cx, PropertyName* name) {
+ return lookup(cx, name) != nullptr;
+ }
+ bool contains(ExclusiveContext* cx, Shape* shape) {
+ return lookup(cx, shape->propid()) == shape;
+ }
+
+ bool containsShapeOrElement(ExclusiveContext* cx, jsid id) {
+ if (JSID_IS_INT(id) && containsDenseElement(JSID_TO_INT(id)))
+ return true;
+ return contains(cx, id);
+ }
+
+ /* Contextless; can be called from other pure code. */
+ 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;
+ }
+
+ /*
+ * Allocate and free an object slot.
+ *
+ * FIXME: bug 593129 -- slot allocation should be done by object methods
+ * after calling object-parameter-free shape methods, avoiding coupling
+ * logic across the object vs. shape module wall.
+ */
+ static bool allocSlot(ExclusiveContext* cx, HandleNativeObject obj, uint32_t* slotp);
+ void freeSlot(ExclusiveContext* cx, uint32_t slot);
+
+ private:
+ static Shape* getChildPropertyOnDictionary(ExclusiveContext* cx, HandleNativeObject obj,
+ HandleShape parent, MutableHandle<StackShape> child);
+ static Shape* getChildProperty(ExclusiveContext* cx, HandleNativeObject obj,
+ HandleShape parent, MutableHandle<StackShape> child);
+
+ public:
+ /* Add a property whose id is not yet in this scope. */
+ static Shape* addProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
+ JSGetterOp getter, JSSetterOp setter,
+ uint32_t slot, unsigned attrs, unsigned flags,
+ bool allowDictionary = true);
+
+ /* Add a data property whose id is not yet in this scope. */
+ Shape* addDataProperty(ExclusiveContext* cx,
+ jsid id_, uint32_t slot, unsigned attrs);
+ Shape* addDataProperty(ExclusiveContext* cx, HandlePropertyName name,
+ uint32_t slot, unsigned attrs);
+
+ /* Add or overwrite a property for id in this scope. */
+ static Shape*
+ putProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
+ JSGetterOp getter, JSSetterOp setter,
+ uint32_t slot, unsigned attrs,
+ unsigned flags);
+ static inline Shape*
+ putProperty(ExclusiveContext* cx, HandleObject obj, PropertyName* name,
+ JSGetterOp getter, JSSetterOp setter,
+ uint32_t slot, unsigned attrs,
+ unsigned flags);
+
+ /* Change the given property into a sibling with the same id in this scope. */
+ static Shape*
+ changeProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape,
+ unsigned attrs, JSGetterOp getter, JSSetterOp setter);
+
+ /* Remove the property named by id from this object. */
+ bool removeProperty(ExclusiveContext* cx, jsid id);
+
+ /* Clear the scope, making it empty. */
+ static void clear(ExclusiveContext* cx, HandleNativeObject obj);
+
+ protected:
+ /*
+ * Internal helper that adds a shape not yet mapped by this object.
+ *
+ * Notes:
+ * 1. getter and setter must be normalized based on flags (see jsscope.cpp).
+ * 2. Checks for non-extensibility must be done by callers.
+ */
+ static Shape*
+ addPropertyInternal(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
+ JSGetterOp getter, JSSetterOp setter, uint32_t slot, unsigned attrs,
+ unsigned flags, ShapeTable::Entry* entry, bool allowDictionary,
+ const AutoKeepShapeTables& keep);
+
+ bool fillInAfterSwap(JSContext* cx, const Vector<Value>& values, void* priv);
+
+ public:
+ // Return true if this object has been converted from shared-immutable
+ // prototype-rooted shape storage to dictionary-shapes in a doubly-linked
+ // list.
+ 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) {
+ /*
+ * This can be used to get the address of the end of the slots for the
+ * object, which may be necessary when fetching zero-length arrays of
+ * slots (e.g. for callObjVarArray).
+ */
+ MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED));
+ return getSlotAddressUnchecked(slot);
+ }
+
+ const HeapSlot* getSlotAddress(uint32_t slot) const {
+ /*
+ * This can be used to get the address of the end of the slots for the
+ * object, which may be necessary when fetching zero-length arrays of
+ * slots (e.g. for callObjVarArray).
+ */
+ MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED));
+ return getSlotAddressUnchecked(slot);
+ }
+
+ HeapSlot& getSlotRef(uint32_t slot) {
+ MOZ_ASSERT(slotInRange(slot));
+ return *getSlotAddress(slot);
+ }
+
+ const HeapSlot& getSlotRef(uint32_t slot) const {
+ MOZ_ASSERT(slotInRange(slot));
+ return *getSlotAddress(slot);
+ }
+
+ void setSlot(uint32_t slot, const Value& value) {
+ MOZ_ASSERT(slotInRange(slot));
+ MOZ_ASSERT(IsObjectValueInCompartment(value, compartment()));
+ getSlotRef(slot).set(this, HeapSlot::Slot, slot, value);
+ }
+
+ void initSlot(uint32_t slot, const Value& value) {
+ MOZ_ASSERT(getSlot(slot).isUndefined());
+ MOZ_ASSERT(slotInRange(slot));
+ MOZ_ASSERT(IsObjectValueInCompartment(value, compartment()));
+ initSlotUnchecked(slot, value);
+ }
+
+ void initSlotUnchecked(uint32_t slot, const Value& value) {
+ getSlotAddressUnchecked(slot)->init(this, HeapSlot::Slot, slot, value);
+ }
+
+ // MAX_FIXED_SLOTS is the biggest number of fixed slots our GC
+ // size classes will give an object.
+ static const uint32_t MAX_FIXED_SLOTS = 16;
+
+ protected:
+ inline bool updateSlotsForSpan(ExclusiveContext* 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].HeapSlot::~HeapSlot();
+ }
+
+ /*
+ * Trigger the write barrier on a range of slots that will no longer be
+ * reachable.
+ */
+ void prepareSlotRangeForOverwrite(size_t start, size_t end) {
+ for (size_t i = start; i < end; i++)
+ getSlotAddressUnchecked(i)->HeapSlot::~HeapSlot();
+ }
+
+ public:
+ static bool rollbackProperties(ExclusiveContext* cx, HandleNativeObject obj,
+ uint32_t slotSpan);
+
+ inline void setSlotWithType(ExclusiveContext* cx, Shape* shape,
+ const Value& value, bool overwriting = true);
+
+ inline const Value& getReservedSlot(uint32_t index) const {
+ MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
+ return getSlot(index);
+ }
+
+ const HeapSlot& getReservedSlotRef(uint32_t index) const {
+ MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
+ return getSlotRef(index);
+ }
+
+ HeapSlot& getReservedSlotRef(uint32_t index) {
+ MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
+ return getSlotRef(index);
+ }
+
+ void initReservedSlot(uint32_t index, const Value& v) {
+ MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
+ initSlot(index, v);
+ }
+
+ void setReservedSlot(uint32_t index, const Value& v) {
+ MOZ_ASSERT(index < JSSLOT_FREE(getClass()));
+ setSlot(index, v);
+ }
+
+ /* For slots which are known to always be fixed, due to the way they are allocated. */
+
+ HeapSlot& getFixedSlotRef(uint32_t slot) {
+ MOZ_ASSERT(slot < numFixedSlots());
+ return fixedSlots()[slot];
+ }
+
+ const Value& getFixedSlot(uint32_t slot) const {
+ MOZ_ASSERT(slot < numFixedSlots());
+ return fixedSlots()[slot];
+ }
+
+ void setFixedSlot(uint32_t slot, const Value& value) {
+ MOZ_ASSERT(slot < numFixedSlots());
+ fixedSlots()[slot].set(this, HeapSlot::Slot, slot, value);
+ }
+
+ void initFixedSlot(uint32_t slot, const Value& value) {
+ MOZ_ASSERT(slot < numFixedSlots());
+ fixedSlots()[slot].init(this, HeapSlot::Slot, slot, value);
+ }
+
+ /*
+ * Get the number of dynamic slots to allocate to cover the properties in
+ * an object with the given number of fixed slots and slot span. The slot
+ * capacity is not stored explicitly, and the allocated size of the slot
+ * array is kept in sync with this count.
+ */
+ static uint32_t dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class* clasp);
+ static uint32_t dynamicSlotsCount(Shape* shape) {
+ return dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), shape->getObjectClass());
+ }
+
+ /* Elements accessors. */
+
+ // The maximum size, in sizeof(Value), of the allocation used for an
+ // object's dense elements. (This includes space used to store an
+ // ObjectElements instance.)
+ // |MAX_DENSE_ELEMENTS_ALLOCATION * sizeof(JS::Value)| shouldn't overflow
+ // int32_t (see elementsSizeMustNotOverflow).
+ static const uint32_t MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1;
+
+ // The maximum number of usable dense elements in an object.
+ 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_);
+ }
+
+ /* Accessors for elements. */
+ bool ensureElements(ExclusiveContext* cx, uint32_t capacity) {
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ MOZ_ASSERT(!denseElementsAreFrozen());
+ if (capacity > getDenseCapacity())
+ return growElements(cx, capacity);
+ return true;
+ }
+
+ static bool goodElementsAllocationAmount(ExclusiveContext* cx, uint32_t reqAllocated,
+ uint32_t length, uint32_t* goodAmount);
+ bool growElements(ExclusiveContext* cx, uint32_t newcap);
+ void shrinkElements(ExclusiveContext* cx, uint32_t cap);
+ void setDynamicElements(ObjectElements* header) {
+ MOZ_ASSERT(!hasDynamicElements());
+ elements_ = header->elements();
+ MOZ_ASSERT(hasDynamicElements());
+ }
+
+ static bool CopyElementsForWrite(ExclusiveContext* cx, NativeObject* obj);
+
+ bool maybeCopyElementsForWrite(ExclusiveContext* cx) {
+ if (denseElementsAreCopyOnWrite())
+ return CopyElementsForWrite(cx, this);
+ return true;
+ }
+
+ private:
+ inline void ensureDenseInitializedLengthNoPackedCheck(ExclusiveContext* cx,
+ uint32_t index, uint32_t extra);
+
+ // Run a post write barrier that encompasses multiple contiguous elements in a
+ // single step.
+ inline void elementsRangeWriteBarrierPost(uint32_t start, uint32_t count) {
+ for (size_t i = 0; i < count; i++) {
+ const Value& v = elements_[start + i];
+ if (v.isObject() && IsInsideNursery(&v.toObject())) {
+ JS::shadow::Runtime* shadowRuntime = shadowRuntimeFromMainThread();
+ shadowRuntime->gcStoreBufferPtr()->putSlot(this, HeapSlot::Element,
+ start + i, count - i);
+ return;
+ }
+ }
+ }
+
+ // See the comment over setDenseElementUnchecked, this applies in the same way.
+ void setDenseInitializedLengthUnchecked(uint32_t length) {
+ MOZ_ASSERT(length <= getDenseCapacity());
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ prepareElementRangeForOverwrite(length, getElementsHeader()->initializedLength);
+ getElementsHeader()->initializedLength = length;
+ }
+
+ // Use this function with care. This is done to allow sparsifying frozen
+ // objects, but should only be called in a few places, and should be
+ // audited carefully!
+ void setDenseElementUnchecked(uint32_t index, const Value& val) {
+ MOZ_ASSERT(index < getDenseInitializedLength());
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ elements_[index].set(this, HeapSlot::Element, index, val);
+ }
+
+ public:
+ void setDenseInitializedLength(uint32_t length) {
+ MOZ_ASSERT(!denseElementsAreFrozen());
+ setDenseInitializedLengthUnchecked(length);
+ }
+
+ inline void ensureDenseInitializedLength(ExclusiveContext* cx,
+ uint32_t index, uint32_t extra);
+
+ void setDenseElement(uint32_t index, const Value& val) {
+ MOZ_ASSERT(!denseElementsAreFrozen());
+ setDenseElementUnchecked(index, val);
+ }
+
+ void initDenseElement(uint32_t index, const Value& val) {
+ MOZ_ASSERT(index < getDenseInitializedLength());
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ MOZ_ASSERT(!denseElementsAreFrozen());
+ elements_[index].init(this, HeapSlot::Element, index, val);
+ }
+
+ void setDenseElementMaybeConvertDouble(uint32_t index, const Value& val) {
+ if (val.isInt32() && shouldConvertDoubleElements())
+ setDenseElement(index, DoubleValue(val.toInt32()));
+ else
+ setDenseElement(index, val);
+ }
+
+ inline void setDenseElementWithType(ExclusiveContext* cx, uint32_t index,
+ const Value& val);
+ inline void initDenseElementWithType(ExclusiveContext* cx, uint32_t index,
+ const Value& val);
+ inline void setDenseElementHole(ExclusiveContext* cx, uint32_t index);
+ static inline void removeDenseElementForSparseIndex(ExclusiveContext* cx,
+ HandleNativeObject obj, uint32_t index);
+
+ inline Value getDenseOrTypedArrayElement(uint32_t idx);
+
+ void copyDenseElements(uint32_t dstStart, const Value* src, uint32_t count) {
+ MOZ_ASSERT(dstStart + count <= getDenseCapacity());
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ MOZ_ASSERT(!denseElementsAreFrozen());
+ if (JS::shadow::Zone::asShadowZone(zone())->needsIncrementalBarrier()) {
+ for (uint32_t i = 0; i < count; ++i)
+ elements_[dstStart + i].set(this, HeapSlot::Element, dstStart + i, src[i]);
+ } else {
+ memcpy(&elements_[dstStart], src, count * sizeof(HeapSlot));
+ elementsRangeWriteBarrierPost(dstStart, count);
+ }
+ }
+
+ void initDenseElements(uint32_t dstStart, const Value* src, uint32_t count) {
+ MOZ_ASSERT(dstStart + count <= getDenseCapacity());
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ MOZ_ASSERT(!denseElementsAreFrozen());
+ memcpy(&elements_[dstStart], src, count * sizeof(HeapSlot));
+ elementsRangeWriteBarrierPost(dstStart, count);
+ }
+
+ void moveDenseElements(uint32_t dstStart, uint32_t srcStart, uint32_t count) {
+ MOZ_ASSERT(dstStart + count <= getDenseCapacity());
+ MOZ_ASSERT(srcStart + count <= getDenseInitializedLength());
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ MOZ_ASSERT(!denseElementsAreFrozen());
+
+ /*
+ * Using memmove here would skip write barriers. Also, we need to consider
+ * an array containing [A, B, C], in the following situation:
+ *
+ * 1. Incremental GC marks slot 0 of array (i.e., A), then returns to JS code.
+ * 2. JS code moves slots 1..2 into slots 0..1, so it contains [B, C, C].
+ * 3. Incremental GC finishes by marking slots 1 and 2 (i.e., C).
+ *
+ * Since normal marking never happens on B, it is very important that the
+ * write barrier is invoked here on B, despite the fact that it exists in
+ * the array before and after the move.
+ */
+ if (JS::shadow::Zone::asShadowZone(zone())->needsIncrementalBarrier()) {
+ if (dstStart < srcStart) {
+ HeapSlot* dst = elements_ + dstStart;
+ HeapSlot* src = elements_ + srcStart;
+ for (uint32_t i = 0; i < count; i++, dst++, src++)
+ dst->set(this, HeapSlot::Element, dst - elements_, *src);
+ } else {
+ HeapSlot* dst = elements_ + dstStart + count - 1;
+ HeapSlot* src = elements_ + srcStart + count - 1;
+ for (uint32_t i = 0; i < count; i++, dst--, src--)
+ dst->set(this, HeapSlot::Element, dst - elements_, *src);
+ }
+ } else {
+ memmove(elements_ + dstStart, elements_ + srcStart, count * sizeof(HeapSlot));
+ elementsRangeWriteBarrierPost(dstStart, count);
+ }
+ }
+
+ void moveDenseElementsNoPreBarrier(uint32_t dstStart, uint32_t srcStart, uint32_t count) {
+ MOZ_ASSERT(!shadowZone()->needsIncrementalBarrier());
+
+ MOZ_ASSERT(dstStart + count <= getDenseCapacity());
+ MOZ_ASSERT(srcStart + count <= getDenseCapacity());
+ MOZ_ASSERT(!denseElementsAreCopyOnWrite());
+ MOZ_ASSERT(!denseElementsAreFrozen());
+
+ memmove(elements_ + dstStart, elements_ + srcStart, count * sizeof(Value));
+ elementsRangeWriteBarrierPost(dstStart, count);
+ }
+
+ bool shouldConvertDoubleElements() {
+ return getElementsHeader()->shouldConvertDoubleElements();
+ }
+
+ inline void setShouldConvertDoubleElements();
+ inline void clearShouldConvertDoubleElements();
+
+ bool denseElementsAreCopyOnWrite() {
+ return getElementsHeader()->isCopyOnWrite();
+ }
+
+ bool denseElementsAreFrozen() {
+ return getElementsHeader()->isFrozen();
+ }
+
+ /* Packed information for this object's elements. */
+ inline bool writeToIndexWouldMarkNotPacked(uint32_t index);
+ inline void markDenseElementsNotPacked(ExclusiveContext* cx);
+
+ // Ensures that the object can hold at least index + extra elements. This
+ // returns DenseElement_Success on success, DenseElement_Failed on failure
+ // to grow the array, or DenseElement_Incomplete when the object is too
+ // sparse to grow (this includes the case of index + extra overflow). In
+ // the last two cases the object is kept intact.
+ inline DenseElementResult ensureDenseElements(ExclusiveContext* cx,
+ uint32_t index, uint32_t extra);
+
+ inline DenseElementResult extendDenseElements(ExclusiveContext* cx,
+ uint32_t requiredCapacity, uint32_t extra);
+
+ /* Convert a single dense element to a sparse property. */
+ static bool sparsifyDenseElement(ExclusiveContext* cx,
+ HandleNativeObject obj, uint32_t index);
+
+ /* Convert all dense elements to sparse properties. */
+ static bool sparsifyDenseElements(ExclusiveContext* cx, HandleNativeObject obj);
+
+ /* Small objects are dense, no matter what. */
+ static const uint32_t MIN_SPARSE_INDEX = 1000;
+
+ /*
+ * Element storage for an object will be sparse if fewer than 1/8 indexes
+ * are filled in.
+ */
+ static const unsigned SPARSE_DENSITY_RATIO = 8;
+
+ /*
+ * Check if after growing the object's elements will be too sparse.
+ * newElementsHint is an estimated number of elements to be added.
+ */
+ bool willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint);
+
+ /*
+ * After adding a sparse index to obj, see if it should be converted to use
+ * dense elements.
+ */
+ static DenseElementResult maybeDensifySparseElements(ExclusiveContext* 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 setFixedElements() {
+ MOZ_ASSERT(canHaveNonEmptyElements());
+ elements_ = fixedElements();
+ }
+
+ inline bool hasDynamicElements() const {
+ /*
+ * Note: for objects with zero fixed slots this could potentially give
+ * a spurious 'true' result, if the end of this object is exactly
+ * aligned with the end of its arena and dynamic slots are allocated
+ * immediately afterwards. Such cases cannot occur for dense arrays
+ * (which have at least two fixed slots) and can only result in a leak.
+ */
+ return !hasEmptyElements() && elements_ != fixedElements();
+ }
+
+ inline bool hasFixedElements() const {
+ return elements_ == fixedElements();
+ }
+
+ inline bool hasEmptyElements() const {
+ return elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared;
+ }
+
+ /*
+ * Get a pointer to the unused data in the object's allocation immediately
+ * following this object, for use with objects which allocate a larger size
+ * class than they need and store non-elements data inline.
+ */
+ 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);
+ }
+
+ /* Private data accessors. */
+
+ inline void*& privateRef(uint32_t nfixed) const { /* XXX should be private, not protected! */
+ /*
+ * The private pointer of an object can hold any word sized value.
+ * Private pointers are stored immediately after the last fixed slot of
+ * the object.
+ */
+ MOZ_ASSERT(nfixed == numFixedSlots());
+ 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) {
+ 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;
+ }
+
+ /* Access private data for an object with a known number of fixed slots. */
+ inline void* getPrivate(uint32_t nfixed) const {
+ return privateRef(nfixed);
+ }
+
+ static inline NativeObject*
+ copy(ExclusiveContext* cx, gc::AllocKind kind, gc::InitialHeap heap,
+ HandleNativeObject templateObject);
+
+ void updateShapeAfterMovingGC();
+ void sweepDictionaryListPointer();
+
+ /* JIT Accessors */
+ static size_t offsetOfElements() { return offsetof(NativeObject, elements_); }
+ static size_t offsetOfFixedElements() {
+ return sizeof(NativeObject) + sizeof(ObjectElements);
+ }
+
+ static size_t getFixedSlotOffset(size_t slot) {
+ return sizeof(NativeObject) + slot * sizeof(Value);
+ }
+ static size_t getPrivateDataOffset(size_t nfixed) { return getFixedSlotOffset(nfixed); }
+ static size_t offsetOfSlots() { return offsetof(NativeObject, slots_); }
+};
+
+// Object class for plain native objects created using '{}' object literals,
+// 'new Object()', 'Object.create', etc.
+class PlainObject : public NativeObject
+{
+ public:
+ static const js::Class class_;
+};
+
+inline void
+NativeObject::privateWriteBarrierPre(void** oldval)
+{
+ JS::shadow::Zone* shadowZone = this->shadowZoneFromAnyThread();
+ if (shadowZone->needsIncrementalBarrier() && *oldval && getClass()->hasTrace())
+ getClass()->doTrace(shadowZone->barrierTracer(), this);
+}
+
+#ifdef DEBUG
+static inline bool
+IsObjectValueInCompartment(const Value& v, JSCompartment* comp)
+{
+ if (!v.isObject())
+ return true;
+ return v.toObject().compartment() == comp;
+}
+#endif
+
+
+/*** Standard internal methods *******************************************************************/
+
+/*
+ * These functions should follow the algorithms in ES6 draft rev 29 section 9.1
+ * ("Ordinary Object Internal Methods"). It's an ongoing project.
+ *
+ * Many native objects are not "ordinary" in ES6, so these functions also have
+ * to serve some of the special needs of Functions (9.2, 9.3, 9.4.1), Arrays
+ * (9.4.2), Strings (9.4.3), and so on.
+ */
+
+extern bool
+NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
+ Handle<JS::PropertyDescriptor> desc,
+ ObjectOpResult& result);
+
+extern bool
+NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, HandleValue value,
+ JSGetterOp getter, JSSetterOp setter, unsigned attrs,
+ ObjectOpResult& result);
+
+extern bool
+NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* name,
+ HandleValue value, GetterOp getter, SetterOp setter,
+ unsigned attrs, ObjectOpResult& result);
+
+extern bool
+NativeDefineElement(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index, HandleValue value,
+ JSGetterOp getter, JSSetterOp setter, unsigned attrs,
+ ObjectOpResult& result);
+
+/* If the result out-param is omitted, throw on failure. */
+extern bool
+NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, HandleValue value,
+ JSGetterOp getter, JSSetterOp setter, unsigned attrs);
+
+extern bool
+NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* name,
+ HandleValue value, JSGetterOp getter, JSSetterOp setter,
+ 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);
+}
+
+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);
+
+/*
+ * Indicates whether an assignment operation is qualified (`x.y = 0`) or
+ * unqualified (`y = 0`). In strict mode, the latter is an error if no such
+ * variable already exists.
+ *
+ * Used as an argument to NativeSetProperty.
+ */
+enum QualifiedBool {
+ Unqualified = 0,
+ Qualified = 1
+};
+
+extern bool
+NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, QualifiedBool qualified, 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);
+
+
+/*** SpiderMonkey nonstandard internal methods ***************************************************/
+
+template <AllowGC allowGC>
+extern bool
+NativeLookupOwnProperty(ExclusiveContext* cx,
+ typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
+ typename MaybeRooted<jsid, allowGC>::HandleType id,
+ typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp);
+
+/*
+ * Get a property from `receiver`, after having already done a lookup and found
+ * the property on a native object `obj`.
+ *
+ * `shape` must not be null and must not be an implicit dense property. It must
+ * be present in obj's shape chain.
+ */
+extern bool
+NativeGetExistingProperty(JSContext* cx, HandleObject receiver, HandleNativeObject obj,
+ HandleShape shape, MutableHandleValue vp);
+
+/* * */
+
+/*
+ * If obj has an already-resolved data property for id, return true and
+ * store the property value in *vp.
+ */
+extern bool
+HasDataProperty(JSContext* cx, NativeObject* obj, jsid id, Value* vp);
+
+inline bool
+HasDataProperty(JSContext* cx, NativeObject* obj, PropertyName* name, Value* vp)
+{
+ return HasDataProperty(cx, obj, NameToId(name), vp);
+}
+
+extern bool
+GetPropertyForNameLookup(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp);
+
+} /* namespace js */
+
+template <>
+inline bool
+JSObject::is<js::NativeObject>() const { return isNative(); }
+
+namespace js {
+
+// Alternate to JSObject::as<NativeObject>() that tolerates null pointers.
+inline NativeObject*
+MaybeNativeObject(JSObject* obj)
+{
+ return obj ? &obj->as<NativeObject>() : nullptr;
+}
+
+// Defined in NativeObject-inl.h.
+bool IsPackedArray(JSObject* obj);
+
+} // namespace js
+
+
+/*** Inline functions declared in jsobj.h that use the native declarations above *****************/
+
+inline bool
+js::HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
+{
+ if (HasPropertyOp op = obj->getOpsHasProperty())
+ return op(cx, obj, id, foundp);
+ return NativeHasProperty(cx, obj.as<NativeObject>(), id, foundp);
+}
+
+inline bool
+js::GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
+ MutableHandleValue vp)
+{
+ if (GetPropertyOp op = obj->getOpsGetProperty())
+ return op(cx, obj, receiver, id, vp);
+ return NativeGetProperty(cx, obj.as<NativeObject>(), receiver, id, vp);
+}
+
+inline bool
+js::GetPropertyNoGC(JSContext* cx, JSObject* obj, const Value& receiver, jsid id, Value* vp)
+{
+ if (obj->getOpsGetProperty())
+ return false;
+ return NativeGetPropertyNoGC(cx, &obj->as<NativeObject>(), receiver, id, vp);
+}
+
+inline bool
+js::SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result)
+{
+ if (obj->getOpsSetProperty())
+ return JSObject::nonNativeSetProperty(cx, obj, id, v, receiver, result);
+ return NativeSetProperty(cx, obj.as<NativeObject>(), id, v, receiver, Qualified, result);
+}
+
+inline bool
+js::SetElement(JSContext* cx, HandleObject obj, uint32_t index, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result)
+{
+ if (obj->getOpsSetProperty())
+ return JSObject::nonNativeSetElement(cx, obj, index, v, receiver, result);
+ return NativeSetElement(cx, obj.as<NativeObject>(), index, v, receiver, result);
+}
+
+#endif /* vm_NativeObject_h */