diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/vm/NativeObject.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/NativeObject.h')
-rw-r--r-- | js/src/vm/NativeObject.h | 1892 |
1 files changed, 1892 insertions, 0 deletions
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h new file mode 100644 index 0000000000..b964c88a83 --- /dev/null +++ b/js/src/vm/NativeObject.h @@ -0,0 +1,1892 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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 "mozilla/Maybe.h" + +#include <algorithm> +#include <stdint.h> + +#include "NamespaceImports.h" + +#include "gc/Barrier.h" +#include "gc/MaybeRooted.h" +#include "gc/ZoneAllocator.h" +#include "js/shadow/Object.h" // JS::shadow::Object +#include "js/shadow/Zone.h" // JS::shadow::Zone +#include "js/Value.h" +#include "vm/GetterSetter.h" +#include "vm/JSAtom.h" +#include "vm/JSObject.h" +#include "vm/Shape.h" +#include "vm/StringType.h" + +namespace js { + +class PropertyResult; + +namespace gc { +class TenuringTracer; +} // namespace gc + +#ifdef ENABLE_RECORD_TUPLE +// Defined in vm/RecordTupleShared.{h,cpp}. We cannot include that file +// because it causes circular dependencies. +extern bool IsExtendedPrimitiveWrapper(const JSObject& obj); +#endif + +/* + * 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 = 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( + GCPtr<Value>* 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", |desc| is the new non-accessor descriptor, and + * |result| receives an error code if the change is invalid. + */ +extern bool ArraySetLength(JSContext* cx, Handle<ArrayObject*> obj, HandleId id, + Handle<PropertyDescriptor> desc, + ObjectOpResult& result); + +/* + * [SMDOC] NativeObject Elements layout + * + * 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. + * + * + * [SMDOC] NativeObject shifted elements optimization + * + * Shifted elements + * ---------------- + * It's pretty common to use an array as a queue, like this: + * + * while (arr.length > 0) + * foo(arr.shift()); + * + * To ensure we don't get quadratic behavior on this, elements can be 'shifted' + * in memory. tryShiftDenseElements does this by incrementing elements_ to point + * to the next element and moving the ObjectElements header in memory (so it's + * stored where the shifted Value used to be). + * + * Shifted elements can be moved when we grow the array, when the array is + * made non-extensible (for simplicity, shifted elements are not supported on + * objects that are non-extensible, have copy-on-write elements, or on arrays + * with non-writable length). + */ +class ObjectElements { + public: + enum Flags : uint16_t { + // Elements are stored inline in the object allocation. + // An object allocated with the FIXED flag set can have the flag unset later + // if `growElements()` is called to increase the capacity beyond what was + // initially allocated. Once the flag is unset, it will remain so for the + // rest of the lifetime of the object. + FIXED = 0x1, + + // Present only if these elements correspond to an array with + // non-writable length; never present for non-arrays. + NONWRITABLE_ARRAY_LENGTH = 0x2, + +#ifdef ENABLE_RECORD_TUPLE + // Records, Tuples and Boxes must be atomized before being hashed. We store + // the "is atomized" flag here for tuples, and in fixed slots for records + // and boxes. + TUPLE_IS_ATOMIZED = 0x4, +#endif + + // 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 not extensible. If this flag is set, the object's + // Shape must also have the NotExtensible flag. This exists on + // ObjectElements in addition to Shape to simplify JIT code. + NOT_EXTENSIBLE = 0x10, + + // These elements are set to integrity level "sealed". If this flag is + // set, the NOT_EXTENSIBLE flag must be set as well. + SEALED = 0x20, + + // These elements are set to integrity level "frozen". If this flag is + // set, the SEALED flag must be set as well. + // + // This flag must only be set if the Shape has the FrozenElements flag. + // The Shape flag ensures a shape guard can be used to guard against frozen + // elements. The ObjectElements flag is convenient for JIT code and + // ObjectElements assertions. + FROZEN = 0x40, + + // If this flag is not set, the elements are guaranteed to contain no hole + // values (the JS_ELEMENTS_HOLE MagicValue) in [0, initializedLength). + NON_PACKED = 0x80, + + // If this flag is not set, there's definitely no for-in iterator that + // covers these dense elements so elements can be deleted without calling + // SuppressDeletedProperty. This is used by fast paths for various Array + // builtins. See also NativeObject::denseElementsMaybeInIteration. + MAYBE_IN_ITERATION = 0x100, + }; + + // The flags word stores both the flags and the number of shifted elements. + // Allow shifting 2047 elements before actually moving the elements. + 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 gc::TenuringTracer; +#ifdef ENABLE_RECORD_TUPLE + friend class TupleType; +#endif + + friend bool js::SetIntegrityLevel(JSContext* cx, HandleObject obj, + IntegrityLevel level); + + friend bool ArraySetLength(JSContext* cx, Handle<ArrayObject*> obj, + HandleId id, Handle<PropertyDescriptor> desc, + ObjectOpResult& result); + + // The NumShiftedElementsBits high bits of this are used to store the + // number of shifted elements, the other bits are available for the flags. + // 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 hasNonwritableArrayLength() const { + return flags & NONWRITABLE_ARRAY_LENGTH; + } + void setNonwritableArrayLength() { + // See ArrayObject::setNonWritableLength. + MOZ_ASSERT(capacity == initializedLength); + MOZ_ASSERT(numShiftedElements() == 0); + flags |= NONWRITABLE_ARRAY_LENGTH; + } + +#ifdef ENABLE_RECORD_TUPLE + void setTupleIsAtomized() { flags |= TUPLE_IS_ATOMIZED; } + + bool tupleIsAtomized() const { return flags & TUPLE_IS_ATOMIZED; } +#endif + + void addShiftedElements(uint32_t count) { + MOZ_ASSERT(count < capacity); + MOZ_ASSERT(count < initializedLength); + MOZ_ASSERT(!( + flags & (NONWRITABLE_ARRAY_LENGTH | NOT_EXTENSIBLE | SEALED | FROZEN))); + 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 | NOT_EXTENSIBLE | SEALED | FROZEN))); + 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 markNonPacked() { flags |= NON_PACKED; } + + void markMaybeInIteration() { flags |= MAYBE_IN_ITERATION; } + bool maybeInIteration() { return flags & MAYBE_IN_ITERATION; } + + void setNotExtensible() { + MOZ_ASSERT(!isNotExtensible()); + flags |= NOT_EXTENSIBLE; + } + bool isNotExtensible() { return flags & NOT_EXTENSIBLE; } + + void seal() { + MOZ_ASSERT(isNotExtensible()); + MOZ_ASSERT(!isSealed()); + MOZ_ASSERT(!isFrozen()); + flags |= SEALED; + } + void freeze() { + MOZ_ASSERT(isNotExtensible()); + MOZ_ASSERT(isSealed()); + MOZ_ASSERT(!isFrozen()); + flags |= FROZEN; + } + + bool isFrozen() const { return 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; } + + 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 PrepareForPreventExtensions(JSContext* cx, NativeObject* obj); + static void PreventExtensions(NativeObject* obj); + [[nodiscard]] static bool FreezeOrSeal(JSContext* cx, + Handle<NativeObject*> obj, + IntegrityLevel level); + + bool isSealed() const { return flags & SEALED; } + + bool isPacked() const { return !(flags & NON_PACKED); } + + JS::PropertyAttributes elementAttributes() const { + if (isFrozen()) { + return {JS::PropertyAttribute::Enumerable}; + } + if (isSealed()) { + return {JS::PropertyAttribute::Enumerable, + JS::PropertyAttribute::Writable}; + } + return {JS::PropertyAttribute::Configurable, + JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable}; + } + + uint32_t numShiftedElements() const { + uint32_t numShifted = flags >> NumShiftedElementsShift; + MOZ_ASSERT_IF(numShifted > 0, + !(flags & (NONWRITABLE_ARRAY_LENGTH | NOT_EXTENSIBLE | + SEALED | FROZEN))); + return numShifted; + } + + uint32_t numAllocatedElements() const { + return VALUES_PER_HEADER + capacity + numShiftedElements(); + } + + // 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"); + +/* + * Slots header used for native objects. The header stores the capacity and the + * slot data follows in memory. + */ +class alignas(HeapSlot) ObjectSlots { + uint32_t capacity_; + uint32_t dictionarySlotSpan_; + uint64_t maybeUniqueId_; + + public: + // Special values for maybeUniqueId_ to indicate no unique ID is present. + static constexpr uint64_t NoUniqueIdInDynamicSlots = 0; + static constexpr uint64_t NoUniqueIdInSharedEmptySlots = 1; + static constexpr uint64_t LastNoUniqueIdValue = NoUniqueIdInSharedEmptySlots; + + static constexpr size_t VALUES_PER_HEADER = 2; + + static inline size_t allocCount(size_t slotCount) { + static_assert(sizeof(ObjectSlots) == + ObjectSlots::VALUES_PER_HEADER * sizeof(HeapSlot)); +#ifdef MOZ_VALGRIND + if (slotCount == 0) { + // Add an extra unused slot so that NativeObject::slots_ always points + // into the allocation otherwise valgrind thinks this is a leak. + slotCount = 1; + } +#endif + return slotCount + VALUES_PER_HEADER; + } + + static inline size_t allocSize(size_t slotCount) { + return allocCount(slotCount) * sizeof(HeapSlot); + } + + static ObjectSlots* fromSlots(HeapSlot* slots) { + MOZ_ASSERT(slots); + return reinterpret_cast<ObjectSlots*>(uintptr_t(slots) - + sizeof(ObjectSlots)); + } + + static constexpr size_t offsetOfCapacity() { + return offsetof(ObjectSlots, capacity_); + } + static constexpr size_t offsetOfDictionarySlotSpan() { + return offsetof(ObjectSlots, dictionarySlotSpan_); + } + static constexpr size_t offsetOfMaybeUniqueId() { + return offsetof(ObjectSlots, maybeUniqueId_); + } + static constexpr size_t offsetOfSlots() { return sizeof(ObjectSlots); } + + constexpr explicit ObjectSlots(uint32_t capacity, uint32_t dictionarySlotSpan, + uint64_t maybeUniqueId); + + constexpr uint32_t capacity() const { return capacity_; } + + constexpr uint32_t dictionarySlotSpan() const { return dictionarySlotSpan_; } + + bool isSharedEmptySlots() const { + return maybeUniqueId_ == NoUniqueIdInSharedEmptySlots; + } + + constexpr bool hasUniqueId() const { + return maybeUniqueId_ > LastNoUniqueIdValue; + } + uint64_t uniqueId() const { + MOZ_ASSERT(hasUniqueId()); + return maybeUniqueId_; + } + uintptr_t maybeUniqueId() const { return hasUniqueId() ? maybeUniqueId_ : 0; } + void setUniqueId(uint64_t uid) { + MOZ_ASSERT(uid > LastNoUniqueIdValue); + MOZ_ASSERT(!isSharedEmptySlots()); + maybeUniqueId_ = uid; + } + + void setDictionarySlotSpan(uint32_t span) { dictionarySlotSpan_ = span; } + + HeapSlot* slots() const { + return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectSlots)); + } +}; + +/* + * 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; + +/* + * Shared singletons for objects with no dynamic slots. + */ +extern HeapSlot* const emptyObjectSlots; +extern HeapSlot* const emptyObjectSlotsForDictionaryObject[]; + +class AutoCheckShapeConsistency; +class GCMarker; + +// Operations which change an object's dense elements can either succeed, fail, +// or be unable to complete. The latter is used when the object's elements must +// become sparse instead. The enum below is used for such operations. +enum class DenseElementResult { Failure, Success, Incomplete }; + +// Stores a slot offset in bytes relative to either the NativeObject* address +// (if isFixedSlot) or to NativeObject::slots_ (if !isFixedSlot). +class TaggedSlotOffset { + uint32_t bits_ = 0; + + public: + static constexpr size_t OffsetShift = 1; + static constexpr size_t IsFixedSlotFlag = 0b1; + + static constexpr size_t MaxOffset = SHAPE_MAXIMUM_SLOT * sizeof(Value); + static_assert((uint64_t(MaxOffset) << OffsetShift) <= UINT32_MAX, + "maximum slot offset must fit in TaggedSlotOffset"); + + constexpr TaggedSlotOffset() = default; + + TaggedSlotOffset(uint32_t offset, bool isFixedSlot) + : bits_((offset << OffsetShift) | isFixedSlot) { + MOZ_ASSERT(offset <= MaxOffset); + } + + uint32_t offset() const { return bits_ >> OffsetShift; } + bool isFixedSlot() const { return bits_ & IsFixedSlotFlag; } + + bool operator==(const TaggedSlotOffset& other) const { + return bits_ == other.bits_; + } + bool operator!=(const TaggedSlotOffset& other) const { + return !(*this == other); + } +}; + +/* + * [SMDOC] NativeObject layout + * + * 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 JSObject { + 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(JS::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(offsetOfShape() == offsetof(JS::shadow::Object, shape), + "shadow type must match actual type"); + static_assert( + offsetof(NativeObject, slots_) == offsetof(JS::shadow::Object, slots), + "shadow slots must match actual slots"); + static_assert( + offsetof(NativeObject, elements_) == offsetof(JS::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"); + + // Sanity check NativeObject size is what we expect. +#ifdef JS_64BIT + static_assert(sizeof(NativeObject) == 3 * sizeof(void*)); +#else + static_assert(sizeof(NativeObject) == 4 * sizeof(void*)); +#endif + } + + public: + NativeShape* shape() const { return &JSObject::shape()->asNative(); } + SharedShape* sharedShape() const { return &shape()->asShared(); } + DictionaryShape* dictionaryShape() const { return &shape()->asDictionary(); } + + PropertyInfoWithKey getLastProperty() const { + return shape()->lastProperty(); + } + + HeapSlotArray getDenseElements() const { return HeapSlotArray(elements_); } + + const Value& getDenseElement(uint32_t idx) const { + MOZ_ASSERT(idx < getDenseInitializedLength()); + return elements_[idx]; + } + bool containsDenseElement(uint32_t idx) const { + 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 object's shape and allocate slots if needed to match the shape's + // slot span. + MOZ_ALWAYS_INLINE bool setShapeAndAddNewSlots(JSContext* cx, + SharedShape* newShape, + uint32_t oldSpan, + uint32_t newSpan); + + // Methods optimized for adding/removing a single slot. Must only be used for + // non-dictionary objects. + MOZ_ALWAYS_INLINE bool setShapeAndAddNewSlot(JSContext* cx, + SharedShape* newShape, + uint32_t slot); + void setShapeAndRemoveLastSlot(JSContext* cx, SharedShape* newShape, + uint32_t slot); + + MOZ_ALWAYS_INLINE bool canReuseShapeForNewProperties( + NativeShape* newShape) const { + NativeShape* oldShape = shape(); + MOZ_ASSERT(oldShape->propMapLength() == 0, + "object must have no properties"); + MOZ_ASSERT(newShape->propMapLength() > 0, + "new shape must have at least one property"); + if (oldShape->numFixedSlots() != newShape->numFixedSlots()) { + return false; + } + if (oldShape->isDictionary() || newShape->isDictionary()) { + return false; + } + if (oldShape->base() != newShape->base()) { + return false; + } + MOZ_ASSERT(oldShape->getObjectClass() == newShape->getObjectClass()); + MOZ_ASSERT(oldShape->proto() == newShape->proto()); + MOZ_ASSERT(oldShape->realm() == newShape->realm()); + // We only handle the common case where the old shape has no object flags + // (expected because it's an empty object) and the new shape has just the + // HasEnumerable flag that we can copy safely. + if (!oldShape->objectFlags().isEmpty()) { + return false; + } + MOZ_ASSERT(newShape->hasObjectFlag(ObjectFlag::HasEnumerable)); + return newShape->objectFlags() == ObjectFlags({ObjectFlag::HasEnumerable}); + } + + // 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; + } + + inline bool isInWholeCellBuffer() const; + + static inline NativeObject* create(JSContext* cx, gc::AllocKind kind, + gc::Heap heap, Handle<SharedShape*> shape, + gc::AllocSite* site = nullptr); + +#ifdef DEBUG + static void enableShapeConsistencyChecks(); +#endif + + protected: +#ifdef DEBUG + friend class js::AutoCheckShapeConsistency; + void checkShapeConsistency(); +#else + void checkShapeConsistency() {} +#endif + + void maybeFreeDictionaryPropSlots(JSContext* cx, DictionaryPropMap* map, + uint32_t mapLength); + + [[nodiscard]] static bool toDictionaryMode(JSContext* cx, + Handle<NativeObject*> obj); + + private: + inline void setEmptyDynamicSlots(uint32_t dictonarySlotSpan); + + inline void setDictionaryModeSlotSpan(uint32_t span); + + friend class gc::TenuringTracer; + + // Given a slot range from |start| to |end| exclusive, call |fun| with + // pointers to the corresponding fixed slot and/or dynamic slot ranges. + template <typename Fun> + void forEachSlotRangeUnchecked(uint32_t start, uint32_t end, const Fun& fun) { + MOZ_ASSERT(end >= start); + uint32_t nfixed = numFixedSlots(); + if (start < nfixed) { + HeapSlot* fixedStart = &fixedSlots()[start]; + HeapSlot* fixedEnd = &fixedSlots()[std::min(nfixed, end)]; + fun(fixedStart, fixedEnd); + start = nfixed; + } + if (end > nfixed) { + HeapSlot* dynStart = &slots_[start - nfixed]; + HeapSlot* dynEnd = &slots_[end - nfixed]; + fun(dynStart, dynEnd); + } + } + + template <typename Fun> + void forEachSlotRange(uint32_t start, uint32_t end, const Fun& fun) { + MOZ_ASSERT(slotInRange(end, SENTINEL_ALLOWED)); + forEachSlotRangeUnchecked(start, end, fun); + } + + protected: + friend class DictionaryPropMap; + friend class GCMarker; + friend class Shape; + + void invalidateSlotRange(uint32_t start, uint32_t end) { +#ifdef DEBUG + forEachSlotRange(start, end, [](HeapSlot* slotsStart, HeapSlot* slotsEnd) { + Debug_SetSlotRangeToCrashOnTouch(slotsStart, slotsEnd); + }); +#endif /* DEBUG */ + } + + void initFixedSlots(uint32_t numSlots) { + MOZ_ASSERT(numSlots == numUsedFixedSlots()); + HeapSlot* slots = fixedSlots(); + for (uint32_t i = 0; i < numSlots; i++) { + slots[i].initAsUndefined(); + } + } + void initDynamicSlots(uint32_t numSlots) { + MOZ_ASSERT(numSlots == sharedShape()->slotSpan() - numFixedSlots()); + HeapSlot* slots = slots_; + for (uint32_t i = 0; i < numSlots; i++) { + slots[i].initAsUndefined(); + } + } + void initSlots(uint32_t nfixed, uint32_t slotSpan) { + initFixedSlots(std::min(nfixed, slotSpan)); + if (slotSpan > nfixed) { + initDynamicSlots(slotSpan - nfixed); + } + } + +#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; + + /* + * Check whether a slot is a fixed slot. + */ + bool slotIsFixed(uint32_t slot) const; + + /* + * Check whether the supplied number of fixed slots is correct. + */ + bool isNumFixedSlots(uint32_t nfixed) 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 - ObjectSlots::VALUES_PER_HEADER; + + /* + * Minimum size for dynamically allocated elements in normal Objects. + */ + static const uint32_t ELEMENT_CAPACITY_MIN = + 8 - ObjectElements::VALUES_PER_HEADER; + + HeapSlot* fixedSlots() const { + return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(NativeObject)); + } + + public: + inline void initEmptyDynamicSlots(); + + [[nodiscard]] static bool generateNewDictionaryShape( + JSContext* cx, Handle<NativeObject*> obj); + + // 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 JS::shadow::Object*>(this)->numFixedSlots(); + } + + // Get the number of fixed slots when the shape pointer may have been + // forwarded by a moving GC. You need to use this rather that + // numFixedSlots() in a trace hook if you access an object that is not the + // object being traced, since it may have a stale shape pointer. + inline uint32_t numFixedSlotsMaybeForwarded() const; + + uint32_t numUsedFixedSlots() const { + uint32_t nslots = sharedShape()->slotSpan(); + return std::min(nslots, numFixedSlots()); + } + + uint32_t slotSpan() const { + if (inDictionaryMode()) { + return dictionaryModeSlotSpan(); + } + MOZ_ASSERT(getSlotsHeader()->dictionarySlotSpan() == 0); + return sharedShape()->slotSpan(); + } + + uint32_t dictionaryModeSlotSpan() const { + MOZ_ASSERT(inDictionaryMode()); + return getSlotsHeader()->dictionarySlotSpan(); + } + + /* 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(); + } + + // Native objects are never proxies. Call isExtensible instead. + bool nonProxyIsExtensible() const = delete; + + bool isExtensible() const { +#ifdef ENABLE_RECORD_TUPLE + if (IsExtendedPrimitiveWrapper(*this)) { + return false; + } +#endif + return !hasFlag(ObjectFlag::NotExtensible); + } + + /* + * Whether there may be indexed properties on this object, excluding any in + * the object's elements. + */ + bool isIndexed() const { return hasFlag(ObjectFlag::Indexed); } + + bool hasInterestingSymbol() const { + return hasFlag(ObjectFlag::HasInterestingSymbol); + } + + bool hasEnumerableProperty() const { + return hasFlag(ObjectFlag::HasEnumerable); + } + + static bool setHadGetterSetterChange(JSContext* cx, + Handle<NativeObject*> obj) { + return setFlag(cx, obj, ObjectFlag::HadGetterSetterChange); + } + bool hadGetterSetterChange() const { + return hasFlag(ObjectFlag::HadGetterSetterChange); + } + + bool allocateInitialSlots(JSContext* cx, uint32_t capacity); + + /* + * 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(JSContext* cx, uint32_t oldCapacity, uint32_t newCapacity); + bool growSlotsForNewSlot(JSContext* cx, uint32_t numFixed, uint32_t slot); + void shrinkSlots(JSContext* cx, uint32_t oldCapacity, uint32_t newCapacity); + + bool allocateSlots(JSContext* cx, uint32_t newCapacity); + + /* + * 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 growSlotsPure(JSContext* cx, NativeObject* obj, + uint32_t newCapacity); + + /* + * Like growSlotsPure but for dense elements. This will return + * false if we failed to allocate a dense element for some reason (OOM, too + * many dense elements, non-writable array length, etc). + */ + static bool addDenseElementPure(JSContext* cx, NativeObject* obj); + + /* + * Indicates whether this object has an ObjectSlots allocation attached. The + * capacity of this can be zero if it is only used to hold a unique ID. + */ + bool hasDynamicSlots() const { + return !getSlotsHeader()->isSharedEmptySlots(); + } + + /* Compute the number of dynamic slots required for this object. */ + MOZ_ALWAYS_INLINE uint32_t calculateDynamicSlots() const; + + MOZ_ALWAYS_INLINE uint32_t numDynamicSlots() const; + +#ifdef DEBUG + uint32_t outOfLineNumDynamicSlots() const; +#endif + + bool empty() const { return shape()->propMapLength() == 0; } + + mozilla::Maybe<PropertyInfo> lookup(JSContext* cx, jsid id); + mozilla::Maybe<PropertyInfo> lookup(JSContext* cx, PropertyName* name) { + return lookup(cx, NameToId(name)); + } + + bool contains(JSContext* cx, jsid id) { return lookup(cx, id).isSome(); } + bool contains(JSContext* cx, PropertyName* name) { + return lookup(cx, name).isSome(); + } + bool contains(JSContext* cx, jsid id, PropertyInfo prop) { + mozilla::Maybe<PropertyInfo> found = lookup(cx, id); + return found.isSome() && *found == prop; + } + + /* Contextless; can be called from other pure code. */ + mozilla::Maybe<PropertyInfo> lookupPure(jsid id); + mozilla::Maybe<PropertyInfo> lookupPure(PropertyName* name) { + return lookupPure(NameToId(name)); + } + + bool containsPure(jsid id) { return lookupPure(id).isSome(); } + bool containsPure(PropertyName* name) { return containsPure(NameToId(name)); } + bool containsPure(jsid id, PropertyInfo prop) { + mozilla::Maybe<PropertyInfo> found = lookupPure(id); + return found.isSome() && *found == prop; + } + + private: + /* + * 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 allocDictionarySlot(JSContext* cx, Handle<NativeObject*> obj, + uint32_t* slotp); + + void freeDictionarySlot(uint32_t slot); + + static MOZ_ALWAYS_INLINE bool maybeConvertToDictionaryForAdd( + JSContext* cx, Handle<NativeObject*> obj); + + public: + // Add a new property. Must only be used when the |id| is not already present + // in the object's shape. Checks for non-extensibility must be done by the + // callers. + static bool addProperty(JSContext* cx, Handle<NativeObject*> obj, HandleId id, + PropertyFlags flags, uint32_t* slotOut); + + static bool addProperty(JSContext* cx, Handle<NativeObject*> obj, + Handle<PropertyName*> name, PropertyFlags flags, + uint32_t* slotOut) { + RootedId id(cx, NameToId(name)); + return addProperty(cx, obj, id, flags, slotOut); + } + + static bool addPropertyInReservedSlot(JSContext* cx, + Handle<NativeObject*> obj, HandleId id, + uint32_t slot, PropertyFlags flags); + static bool addPropertyInReservedSlot(JSContext* cx, + Handle<NativeObject*> obj, + Handle<PropertyName*> name, + uint32_t slot, PropertyFlags flags) { + RootedId id(cx, NameToId(name)); + return addPropertyInReservedSlot(cx, obj, id, slot, flags); + } + + static bool addCustomDataProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, PropertyFlags flags); + + // Change a property with key |id| in this object. The object must already + // have a property (stored in the shape tree) with this |id|. + static bool changeProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, PropertyFlags flags, + uint32_t* slotOut); + + static bool changeCustomDataPropAttributes(JSContext* cx, + Handle<NativeObject*> obj, + HandleId id, PropertyFlags flags); + + // Remove the property named by id from this object. + static bool removeProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id); + + static bool freezeOrSealProperties(JSContext* cx, Handle<NativeObject*> obj, + IntegrityLevel level); + + protected: + static bool changeNumFixedSlotsAfterSwap(JSContext* cx, + Handle<NativeObject*> obj, + uint32_t nfixed); + + // For use from JSObject::swap. + [[nodiscard]] bool prepareForSwap(JSContext* cx, + MutableHandleValueVector slotValuesOut); + [[nodiscard]] static bool fixupAfterSwap(JSContext* cx, + Handle<NativeObject*> obj, + gc::AllocKind kind, + HandleValueVector slotValues); + + public: + // Return true if this object has been converted from shared-immutable + // shapes to object-owned dictionary shapes. + bool inDictionaryMode() const { return shape()->isDictionary(); } + + 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); + } + + 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); + } + + // Check requirements on values stored to this object. + MOZ_ALWAYS_INLINE void checkStoredValue(const Value& v) { + MOZ_ASSERT(IsObjectValueInCompartment(v, compartment())); + MOZ_ASSERT(AtomIsMarked(zoneFromAnyThread(), v)); + MOZ_ASSERT_IF(v.isMagic() && v.whyMagic() == JS_ELEMENTS_HOLE, + !denseElementsArePacked()); + } + + 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); + } + + // Returns the GetterSetter for an accessor property. + GetterSetter* getGetterSetter(uint32_t slot) const { + return getSlot(slot).toGCThing()->as<GetterSetter>(); + } + GetterSetter* getGetterSetter(PropertyInfo prop) const { + MOZ_ASSERT(prop.isAccessorProperty()); + return getGetterSetter(prop.slot()); + } + + // Returns the (possibly nullptr) getter or setter object. |prop| and |slot| + // must be (for) an accessor property. + JSObject* getGetter(uint32_t slot) const { + return getGetterSetter(slot)->getter(); + } + JSObject* getGetter(PropertyInfo prop) const { + return getGetterSetter(prop)->getter(); + } + JSObject* getSetter(PropertyInfo prop) const { + return getGetterSetter(prop)->setter(); + } + + // Returns true if the property has a non-nullptr getter or setter object. + // |prop| can be any property. + bool hasGetter(PropertyInfo prop) const { + return prop.isAccessorProperty() && getGetter(prop); + } + bool hasSetter(PropertyInfo prop) const { + return prop.isAccessorProperty() && getSetter(prop); + } + + // If the property has a non-nullptr getter/setter, return it as ObjectValue. + // Else return |undefined|. |prop| must be an accessor property. + Value getGetterValue(PropertyInfo prop) const { + MOZ_ASSERT(prop.isAccessorProperty()); + if (JSObject* getterObj = getGetter(prop)) { + return ObjectValue(*getterObj); + } + return UndefinedValue(); + } + Value getSetterValue(PropertyInfo prop) const { + MOZ_ASSERT(prop.isAccessorProperty()); + if (JSObject* setterObj = getSetter(prop)) { + return ObjectValue(*setterObj); + } + return UndefinedValue(); + } + + [[nodiscard]] bool setUniqueId(JSContext* cx, uint64_t uid); + inline bool hasUniqueId() const { return getSlotsHeader()->hasUniqueId(); } + inline uint64_t uniqueId() const { return getSlotsHeader()->uniqueId(); } + inline uint64_t maybeUniqueId() const { + return getSlotsHeader()->maybeUniqueId(); + } + bool setOrUpdateUniqueId(JSContext* cx, uint64_t uid); + + // MAX_FIXED_SLOTS is the biggest number of fixed slots our GC + // size classes will give an object. + static constexpr uint32_t MAX_FIXED_SLOTS = + JS::shadow::Object::MAX_FIXED_SLOTS; + + private: + void prepareElementRangeForOverwrite(size_t start, size_t end) { + MOZ_ASSERT(end <= getDenseInitializedLength()); + for (size_t i = start; i < end; i++) { + elements_[i].destroy(); + } + } + + /* + * 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)->destroy(); + } + } + + inline void shiftDenseElementsUnchecked(uint32_t count); + + // Like getSlotRef, but optimized for reserved slots. This relies on the fact + // that the first reserved slots (up to MAX_FIXED_SLOTS) are always stored in + // fixed slots. This lets the compiler optimize away the branch below when + // |index| is a constant (after inlining). + // + // Note: objects that may be swapped have less predictable slot layouts + // because they could have been swapped with an object with fewer fixed slots. + // Fortunately, the only native objects that can be swapped are DOM objects + // and these shouldn't end up here (asserted below). + MOZ_ALWAYS_INLINE HeapSlot& getReservedSlotRef(uint32_t index) { + MOZ_ASSERT(index < JSSLOT_FREE(getClass())); + MOZ_ASSERT(slotIsFixed(index) == (index < MAX_FIXED_SLOTS)); + MOZ_ASSERT(!ObjectMayBeSwapped(this)); + return index < MAX_FIXED_SLOTS ? fixedSlots()[index] + : slots_[index - MAX_FIXED_SLOTS]; + } + MOZ_ALWAYS_INLINE const HeapSlot& getReservedSlotRef(uint32_t index) const { + MOZ_ASSERT(index < JSSLOT_FREE(getClass())); + MOZ_ASSERT(slotIsFixed(index) == (index < MAX_FIXED_SLOTS)); + MOZ_ASSERT(!ObjectMayBeSwapped(this)); + return index < MAX_FIXED_SLOTS ? fixedSlots()[index] + : slots_[index - MAX_FIXED_SLOTS]; + } + + public: + MOZ_ALWAYS_INLINE const Value& getReservedSlot(uint32_t index) const { + return getReservedSlotRef(index); + } + MOZ_ALWAYS_INLINE void initReservedSlot(uint32_t index, const Value& v) { + MOZ_ASSERT(getReservedSlot(index).isUndefined()); + checkStoredValue(v); + getReservedSlotRef(index).init(this, HeapSlot::Slot, index, v); + } + MOZ_ALWAYS_INLINE void setReservedSlot(uint32_t index, const Value& v) { + checkStoredValue(v); + getReservedSlotRef(index).set(this, HeapSlot::Slot, 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(slotIsFixed(slot)); + return fixedSlots()[slot]; + } + + const Value& getFixedSlot(uint32_t slot) const { + MOZ_ASSERT(slotIsFixed(slot)); + return fixedSlots()[slot]; + } + + const Value& getDynamicSlot(uint32_t dynamicSlotIndex) const { + MOZ_ASSERT(dynamicSlotIndex < outOfLineNumDynamicSlots()); + return slots_[dynamicSlotIndex]; + } + + void setFixedSlot(uint32_t slot, const Value& value) { + MOZ_ASSERT(slotIsFixed(slot)); + checkStoredValue(value); + fixedSlots()[slot].set(this, HeapSlot::Slot, slot, value); + } + + void setDynamicSlot(uint32_t numFixed, uint32_t slot, const Value& value) { + MOZ_ASSERT(numFixedSlots() == numFixed); + MOZ_ASSERT(slot >= numFixed); + MOZ_ASSERT(slot - numFixed < getSlotsHeader()->capacity()); + checkStoredValue(value); + slots_[slot - numFixed].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); + } + + void initDynamicSlot(uint32_t numFixed, uint32_t slot, const Value& value) { + MOZ_ASSERT(numFixedSlots() == numFixed); + MOZ_ASSERT(slot >= numFixed); + MOZ_ASSERT(slot - numFixed < getSlotsHeader()->capacity()); + checkStoredValue(value); + slots_[slot - numFixed].init(this, HeapSlot::Slot, slot, value); + } + + template <typename T> + T* maybePtrFromReservedSlot(uint32_t slot) const { + Value v = getReservedSlot(slot); + return v.isUndefined() ? nullptr : static_cast<T*>(v.toPrivate()); + } + + /* + * Calculate the number of dynamic slots to allocate to cover the properties + * in an object with the given number of fixed slots and slot span. + */ + static MOZ_ALWAYS_INLINE uint32_t calculateDynamicSlots(uint32_t nfixed, + uint32_t span, + const JSClass* clasp); + static MOZ_ALWAYS_INLINE uint32_t calculateDynamicSlots(SharedShape* shape); + + ObjectSlots* getSlotsHeader() const { return ObjectSlots::fromSlots(slots_); } + + /* 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_); + } + + // Returns a pointer to the first element, including shifted elements. + inline HeapSlot* unshiftedElements() const { + return elements_ - getElementsHeader()->numShiftedElements(); + } + + // Like getElementsHeader, but returns a pointer to the unshifted header. + // This is mainly useful for free()ing dynamic elements: the pointer + // returned here is the one we got from malloc. + void* getUnshiftedElementsHeader() const { + return ObjectElements::fromElements(unshiftedElements()); + } + + uint32_t unshiftedIndex(uint32_t index) const { + return index + getElementsHeader()->numShiftedElements(); + } + + /* Accessors for elements. */ + bool ensureElements(JSContext* cx, uint32_t capacity) { + MOZ_ASSERT(isExtensible()); + if (capacity > getDenseCapacity()) { + return growElements(cx, capacity); + } + return true; + } + + // Try to shift |count| dense elements, see the "Shifted elements" comment. + inline bool tryShiftDenseElements(uint32_t count); + + // Try to make space for |count| dense elements at the start of the array. + bool tryUnshiftDenseElements(uint32_t count); + + // Move the elements header and all shifted elements to the start of the + // allocated elements space, so that numShiftedElements is 0 afterwards. + void moveShiftedElements(); + + // If this object has many shifted elements call 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); + + private: + // Run a post write barrier that encompasses multiple contiguous elements in a + // single step. + inline void elementsRangePostWriteBarrier(uint32_t start, uint32_t count); + + public: + void shrinkCapacityToInitializedLength(JSContext* cx); + + private: + void setDenseInitializedLengthInternal(uint32_t length) { + MOZ_ASSERT(length <= getDenseCapacity()); + 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(uint32_t index, uint32_t extra); + + void setDenseElement(uint32_t index, const Value& val) { + MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() != JS_ELEMENTS_HOLE); + setDenseElementUnchecked(index, val); + } + + void initDenseElement(uint32_t index, const Value& val) { + MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE)); + initDenseElementUnchecked(index, val); + } + + private: + // Note: 'Unchecked' here means we don't assert |val| isn't the hole + // MagicValue. + void initDenseElementUnchecked(uint32_t index, const Value& val) { + MOZ_ASSERT(index < getDenseInitializedLength()); + MOZ_ASSERT(isExtensible()); + checkStoredValue(val); + elements_[index].init(this, HeapSlot::Element, unshiftedIndex(index), val); + } + void setDenseElementUnchecked(uint32_t index, const Value& val) { + MOZ_ASSERT(index < getDenseInitializedLength()); + MOZ_ASSERT(!denseElementsAreFrozen()); + checkStoredValue(val); + elements_[index].set(this, HeapSlot::Element, unshiftedIndex(index), val); + } + + // Mark the dense elements as possibly containing holes. + inline void markDenseElementsNotPacked(); + + public: + inline void initDenseElementHole(uint32_t index); + inline void setDenseElementHole(uint32_t index); + inline void removeDenseElementForSparseIndex(uint32_t index); + + 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); + + // Copy the first `count` dense elements from `src` to `this`, starting at + // `destStart`. The initialized length must already include the new elements. + inline void initDenseElementRange(uint32_t destStart, NativeObject* src, + uint32_t count); + + // Store the Values in the range [begin, end) as elements of this array. + // + // Preconditions: This must be a boring ArrayObject with dense initialized + // length 0: no shifted elements, no frozen elements, no fixed "length", not + // indexed, not inextensible, not copy-on-write. Existing capacity is + // optional. + // + // This runs write barriers but does not update types. `end - begin` must + // return the size of the range, which must be >= 0 and fit in an int32_t. + template <typename Iter> + [[nodiscard]] inline bool initDenseElementsFromRange(JSContext* cx, + Iter begin, Iter end); + + inline void moveDenseElements(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); + + bool denseElementsAreSealed() const { + return getElementsHeader()->isSealed(); + } + bool denseElementsAreFrozen() const { + return hasFlag(ObjectFlag::FrozenElements); + } + + bool denseElementsArePacked() const { + return getElementsHeader()->isPacked(); + } + + void markDenseElementsMaybeInIteration() { + getElementsHeader()->markMaybeInIteration(); + } + + // Return whether the object's dense elements might be in the midst of for-in + // iteration. We rely on this to be able to safely delete or move dense array + // elements without worrying about updating in-progress iterators. + // See bug 690622. + // + // Note that it's fine to return false if this object is on the prototype of + // another object: SuppressDeletedProperty only suppresses properties deleted + // from the iterated object itself. + inline bool denseElementsHaveMaybeInIterationFlag(); + inline bool denseElementsMaybeInIteration(); + + // 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(JSContext* cx, uint32_t index, + uint32_t extra); + + inline DenseElementResult extendDenseElements(JSContext* cx, + uint32_t requiredCapacity, + uint32_t extra); + + /* 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( + JSContext* cx, Handle<NativeObject*> obj); + static bool densifySparseElements(JSContext* cx, Handle<NativeObject*> 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 initFixedElements(gc::AllocKind kind, uint32_t length); + + // Update the elements pointer to use the fixed elements storage. The caller + // is responsible for initializing the elements themselves and setting the + // FIXED flag. + void setFixedElements(uint32_t numShifted = 0) { + MOZ_ASSERT(canHaveNonEmptyElements()); + elements_ = fixedElements() + numShifted; + } + + 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() && !hasFixedElements(); + } + + inline bool hasFixedElements() const { + bool fixed = getElementsHeader()->flags & ObjectElements::FIXED; + MOZ_ASSERT_IF(fixed, unshiftedElements() == fixedElements()); + return fixed; + } + + 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 privatePreWriteBarrier(HeapSlot* pprivate); + + // The methods below are used to store GC things in a reserved slot as + // PrivateValues. This is done to bypass the normal tracing code (debugger + // objects use this to store cross-compartment pointers). + // + // WARNING: make sure you REALLY need this and you know what you're doing + // before using these methods! + void setReservedSlotGCThingAsPrivate(uint32_t slot, gc::Cell* cell) { +#ifdef DEBUG + if (IsMarkedBlack(this)) { + JS::AssertCellIsNotGray(cell); + } +#endif + HeapSlot* pslot = getSlotAddress(slot); + Cell* prev = nullptr; + if (!pslot->isUndefined()) { + prev = static_cast<gc::Cell*>(pslot->toPrivate()); + privatePreWriteBarrier(pslot); + } + setReservedSlotGCThingAsPrivateUnbarriered(slot, cell); + gc::PostWriteBarrierCell(this, prev, cell); + } + void setReservedSlotGCThingAsPrivateUnbarriered(uint32_t slot, + gc::Cell* cell) { + MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass())); + MOZ_ASSERT(cell); + getReservedSlotRef(slot).unbarrieredSet(PrivateValue(cell)); + } + void clearReservedSlotGCThingAsPrivate(uint32_t slot) { + MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass())); + HeapSlot* pslot = &getReservedSlotRef(slot); + if (!pslot->isUndefined()) { + privatePreWriteBarrier(pslot); + pslot->unbarrieredSet(UndefinedValue()); + } + } + + /* Return the allocKind we would use if we were to tenure this object. */ + inline js::gc::AllocKind allocKindForTenure() const; + + // Native objects are never wrappers, so a native object always has a realm + // and global. + JS::Realm* realm() const { return nonCCWRealm(); } + inline js::GlobalObject& global() const; + + TaggedSlotOffset getTaggedSlotOffset(size_t slot) const { + MOZ_ASSERT(slot < slotSpan()); + uint32_t nfixed = numFixedSlots(); + if (slot < nfixed) { + return TaggedSlotOffset(getFixedSlotOffset(slot), + /* isFixedSlot = */ true); + } + return TaggedSlotOffset((slot - nfixed) * sizeof(Value), + /* isFixedSlot = */ false); + } + + /* JIT Accessors */ + 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) { + MOZ_ASSERT(slot < MAX_FIXED_SLOTS); + return sizeof(NativeObject) + slot * sizeof(Value); + } + static constexpr size_t getFixedSlotIndexFromOffset(size_t offset) { + MOZ_ASSERT(offset >= sizeof(NativeObject)); + offset -= sizeof(NativeObject); + MOZ_ASSERT(offset % sizeof(Value) == 0); + MOZ_ASSERT(offset / sizeof(Value) < MAX_FIXED_SLOTS); + return offset / sizeof(Value); + } + static constexpr size_t getDynamicSlotIndexFromOffset(size_t offset) { + MOZ_ASSERT(offset % sizeof(Value) == 0); + return offset / sizeof(Value); + } + static size_t offsetOfSlots() { return offsetof(NativeObject, slots_); } +}; + +inline void NativeObject::privatePreWriteBarrier(HeapSlot* pprivate) { + JS::shadow::Zone* shadowZone = this->shadowZoneFromAnyThread(); + if (shadowZone->needsIncrementalBarrier() && pprivate->get().toPrivate() && + getClass()->hasTrace()) { + getClass()->doTrace(shadowZone->barrierTracer(), this); + } +} + +/*** 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(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, + Handle<JS::PropertyDescriptor> desc, + ObjectOpResult& result); + +extern bool NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, HandleValue value, + unsigned attrs, ObjectOpResult& result); + +/* If the result out-param is omitted, throw on failure. */ + +extern bool NativeDefineAccessorProperty(JSContext* cx, + Handle<NativeObject*> obj, HandleId id, + HandleObject getter, + HandleObject setter, unsigned attrs); + +extern bool NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, HandleValue value, + unsigned attrs); + +extern bool NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj, + PropertyName* name, HandleValue value, + unsigned attrs); + +extern bool NativeHasProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, bool* foundp); + +extern bool NativeGetOwnPropertyDescriptor( + JSContext* cx, Handle<NativeObject*> obj, HandleId id, + MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); + +extern bool NativeGetProperty(JSContext* cx, Handle<NativeObject*> 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, Handle<NativeObject*> obj, + HandleId id, MutableHandleValue vp) { + RootedValue receiver(cx, ObjectValue(*obj)); + return NativeGetProperty(cx, obj, receiver, id, vp); +} + +extern bool NativeGetElement(JSContext* cx, Handle<NativeObject*> obj, + HandleValue receiver, int32_t index, + MutableHandleValue vp); + +bool GetSparseElementHelper(JSContext* cx, Handle<NativeObject*> 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, Handle<NativeObject*> obj, + int32_t int_id, HandleValue v, bool strict); + +/* + * 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 }; + +template <QualifiedBool Qualified> +extern bool NativeSetProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, HandleValue v, HandleValue receiver, + ObjectOpResult& result); + +extern bool NativeSetElement(JSContext* cx, Handle<NativeObject*> obj, + uint32_t index, HandleValue v, + HandleValue receiver, ObjectOpResult& result); + +extern bool NativeDeleteProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, ObjectOpResult& result); + +/*** SpiderMonkey nonstandard internal methods ******************************/ + +template <AllowGC allowGC> +extern bool NativeLookupOwnProperty( + JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyResult* propp); + +/* + * Get a property from `receiver`, after having already done a lookup and found + * the property on a native object `obj`. + * + * `prop` must be present in obj's shape. + */ +extern bool NativeGetExistingProperty(JSContext* cx, HandleObject receiver, + Handle<NativeObject*> obj, HandleId id, + PropertyInfo prop, MutableHandleValue vp); + +/* * */ + +extern bool GetNameBoundInEnvironment(JSContext* cx, HandleObject env, + HandleId id, MutableHandleValue vp); + +} /* namespace js */ + +template <> +inline bool JSObject::is<js::NativeObject>() const { + return shape()->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); + +// Initialize an object's reserved slot with a private value pointing to +// malloc-allocated memory and associate the memory with the object. +// +// This call should be matched with a call to JS::GCContext::free_/delete_ in +// the object's finalizer to free the memory and update the memory accounting. + +inline void InitReservedSlot(NativeObject* obj, uint32_t slot, void* ptr, + size_t nbytes, MemoryUse use) { + AddCellMemory(obj, nbytes, use); + obj->initReservedSlot(slot, PrivateValue(ptr)); +} +template <typename T> +inline void InitReservedSlot(NativeObject* obj, uint32_t slot, T* ptr, + MemoryUse use) { + InitReservedSlot(obj, slot, ptr, sizeof(T), use); +} + +bool AddSlotAndCallAddPropHook(JSContext* cx, Handle<NativeObject*> obj, + HandleValue v, Handle<Shape*> newShape); + +} // namespace js + +#endif /* vm_NativeObject_h */ |