Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  NativeObject.h   Sprache: C

 
/* -*- 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/BufferAllocator.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/JSAtomUtils.h"  // AtomIsMarked
#include "vm/JSObject.h"
#include "vm/Shape.h"
#include "vm/StringType.h"

namespace js {

class JS_PUBLIC_API GenericPrinter;
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;

/**
 * 10.4.2.4 ArraySetLength ( A, Desc )
 *
 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b
 *
 * |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;

  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; }

  void setNotExtensible() {
    MOZ_ASSERT(!isNotExtensible());
    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();
  }

  bool hasNonwritableArrayLength() const {
    return flags & NONWRITABLE_ARRAY_LENGTH;
  }

  bool maybeInIteration() { return flags & MAYBE_IN_ITERATION; }

  bool isNotExtensible() { return flags & NOT_EXTENSIBLE; }

  // This is enough slots to store an object of this class. See the static
  // assertion below.
  static const size_t VALUES_PER_HEADER = 2;

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dumpStringContent(js::GenericPrinter& out) const;
#endif
};

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 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);
  }
};

enum class CanReuseShape {
  // The Shape can be reused. This implies CanReusePropMap.
  CanReuseShape,

  // Only the PropMap can be reused.
  CanReusePropMap,

  // Neither the PropMap nor Shape can be reused.
  NoReuse,
};

/*
 * [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 CanReuseShape
  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->isDictionary() || newShape->isDictionary()) {
      return CanReuseShape::NoReuse;
    }
    // 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 CanReuseShape::NoReuse;
    }
    MOZ_ASSERT(newShape->hasObjectFlag(ObjectFlag::HasEnumerable));
    if (newShape->objectFlags() != ObjectFlags({ObjectFlag::HasEnumerable})) {
      return CanReuseShape::NoReuse;
    }
    // If the number of fixed slots or the BaseShape is different, we can't
    // reuse the Shape but we can still reuse the PropMap.
    if (oldShape->numFixedSlots() != newShape->numFixedSlots() ||
        oldShape->base() != newShape->base()) {
      return CanReuseShape::CanReusePropMap;
    }
    MOZ_ASSERT(oldShape->getObjectClass() == newShape->getObjectClass());
    MOZ_ASSERT(oldShape->proto() == newShape->proto());
    MOZ_ASSERT(oldShape->realm() == newShape->realm());
    return CanReuseShape::CanReuseShape;
  }

  // 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;
  }

  static inline NativeObject* create(JSContext* cx, gc::AllocKind kind,
                                     gc::Heap heap, Handle<SharedShape*> shape,
                                     gc::AllocSite* site = nullptr);

  template <typename T>
  static inline T* create(JSContext* cx, gc::AllocKind kind, gc::Heap heap,
                          Handle<SharedShape*> shape,
                          gc::AllocSite* site = nullptr) {
    NativeObject* nobj = create(cx, kind, heap, shape, site);
    return nobj ? &nobj->as<T>() : 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 = 5;

  /*
   * Minimum size for dynamically allocated elements in normal Objects.
   */

  static const uint32_t ELEMENT_CAPACITY_MIN = 5;

  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(Nursery& nursery, 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, JSObject* other,
                                    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* getSlotsUnchecked() { return slots_; }

  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(JSRuntime* runtime, 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());
  }

  // Returns the address of a reserved fixed slot that stores a T* as
  // PrivateValue. Be very careful when using this because the object might be
  // moved in memory!
  template <typename T>
  T** addressOfFixedSlotPrivatePtr(size_t slot) {
    MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass()));
    MOZ_ASSERT(slotIsFixed(slot));
    MOZ_ASSERT(getReservedSlot(slot).isDouble());
    void* addr = &getFixedSlotRef(slot);
    return reinterpret_cast<T**>(addr);
  }

  /*
   * 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 -
      gc::LargeBufferHeaderSize / sizeof(Value);

  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(JSLinearString** 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 {
    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());
    }
  }

  // This is equivalent to |setReservedSlot(slot, PrivateValue(v))| but it
  // avoids GC barriers. Use this only when storing a private value in a
  // reserved slot that never holds a GC thing.
  void setReservedSlotPrivateUnbarriered(uint32_t slot, void* v) {
    MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass()));
    MOZ_ASSERT(getReservedSlot(slot).isUndefined() ||
               getReservedSlot(slot).isDouble());
    getReservedSlotRef(slot).unbarrieredSet(PrivateValue(v));
  }

  // Like setReservedSlotPrivateUnbarriered but for PrivateUint32Value.
  void setReservedSlotPrivateUint32Unbarriered(uint32_t slot, uint32_t u) {
    MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass()));
    MOZ_ASSERT(getReservedSlot(slot).isUndefined() ||
               getReservedSlot(slot).isInt32());
    getReservedSlotRef(slot).unbarrieredSet(PrivateUint32Value(u));
  }

  /* 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();
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=91 H=94 G=92

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge