tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

NativeObject.cpp (102003B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "vm/NativeObject-inl.h"
      8 
      9 #include "mozilla/CheckedInt.h"
     10 #include "mozilla/Maybe.h"
     11 
     12 #include <algorithm>
     13 
     14 #include "gc/MaybeRooted.h"
     15 #include "gc/StableCellHasher.h"
     16 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     17 #include "js/friend/StackLimits.h"    // js::AutoCheckRecursionLimit
     18 #include "js/Printer.h"               // js::GenericPrinter
     19 #include "js/Value.h"
     20 #include "vm/EqualityOperations.h"  // js::SameValue
     21 #include "vm/GetterSetter.h"        // js::GetterSetter
     22 #include "vm/Interpreter.h"         // js::CallGetter, js::CallSetter
     23 #include "vm/JSONPrinter.h"         // js::JSONPrinter
     24 #include "vm/PlainObject.h"         // js::PlainObject
     25 #include "vm/TypedArrayObject.h"
     26 #include "vm/Watchtower.h"
     27 #include "gc/Nursery-inl.h"
     28 #include "vm/JSObject-inl.h"
     29 #include "vm/Shape-inl.h"
     30 
     31 using namespace js;
     32 
     33 using mozilla::CheckedInt;
     34 using mozilla::PodCopy;
     35 using mozilla::RoundUpPow2;
     36 
     37 struct EmptyObjectElements {
     38  const ObjectElements emptyElementsHeader;
     39 
     40  // Add an extra (unused) Value to make sure an out-of-bounds index when
     41  // masked (resulting in index 0) accesses valid memory.
     42  const Value val;
     43 
     44 public:
     45  constexpr EmptyObjectElements()
     46      : emptyElementsHeader(0, 0), val(UndefinedValue()) {}
     47  explicit constexpr EmptyObjectElements(ObjectElements::SharedMemory shmem)
     48      : emptyElementsHeader(0, 0, shmem), val(UndefinedValue()) {}
     49 };
     50 
     51 static constexpr EmptyObjectElements emptyElementsHeader;
     52 
     53 /* Objects with no elements share one empty set of elements. */
     54 HeapSlot* const js::emptyObjectElements = reinterpret_cast<HeapSlot*>(
     55    uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
     56 
     57 static constexpr EmptyObjectElements emptyElementsHeaderShared(
     58    ObjectElements::SharedMemory::IsShared);
     59 
     60 /* Objects with no elements share one empty set of elements. */
     61 HeapSlot* const js::emptyObjectElementsShared = reinterpret_cast<HeapSlot*>(
     62    uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
     63 
     64 struct EmptyObjectSlots : public ObjectSlots {
     65  explicit constexpr EmptyObjectSlots(size_t dictionarySlotSpan)
     66      : ObjectSlots(0, dictionarySlotSpan, NoUniqueIdInSharedEmptySlots) {}
     67 };
     68 
     69 static constexpr EmptyObjectSlots emptyObjectSlotsHeaders[17] = {
     70    EmptyObjectSlots(0),  EmptyObjectSlots(1),  EmptyObjectSlots(2),
     71    EmptyObjectSlots(3),  EmptyObjectSlots(4),  EmptyObjectSlots(5),
     72    EmptyObjectSlots(6),  EmptyObjectSlots(7),  EmptyObjectSlots(8),
     73    EmptyObjectSlots(9),  EmptyObjectSlots(10), EmptyObjectSlots(11),
     74    EmptyObjectSlots(12), EmptyObjectSlots(13), EmptyObjectSlots(14),
     75    EmptyObjectSlots(15), EmptyObjectSlots(16)};
     76 
     77 static_assert(std::size(emptyObjectSlotsHeaders) ==
     78              NativeObject::MAX_FIXED_SLOTS + 1);
     79 
     80 MOZ_RUNINIT HeapSlot* const js::emptyObjectSlotsForDictionaryObject[17] = {
     81    emptyObjectSlotsHeaders[0].slots(),  emptyObjectSlotsHeaders[1].slots(),
     82    emptyObjectSlotsHeaders[2].slots(),  emptyObjectSlotsHeaders[3].slots(),
     83    emptyObjectSlotsHeaders[4].slots(),  emptyObjectSlotsHeaders[5].slots(),
     84    emptyObjectSlotsHeaders[6].slots(),  emptyObjectSlotsHeaders[7].slots(),
     85    emptyObjectSlotsHeaders[8].slots(),  emptyObjectSlotsHeaders[9].slots(),
     86    emptyObjectSlotsHeaders[10].slots(), emptyObjectSlotsHeaders[11].slots(),
     87    emptyObjectSlotsHeaders[12].slots(), emptyObjectSlotsHeaders[13].slots(),
     88    emptyObjectSlotsHeaders[14].slots(), emptyObjectSlotsHeaders[15].slots(),
     89    emptyObjectSlotsHeaders[16].slots()};
     90 
     91 static_assert(std::size(emptyObjectSlotsForDictionaryObject) ==
     92              NativeObject::MAX_FIXED_SLOTS + 1);
     93 
     94 MOZ_RUNINIT HeapSlot* const js::emptyObjectSlots =
     95    emptyObjectSlotsForDictionaryObject[0];
     96 
     97 #ifdef DEBUG
     98 
     99 bool NativeObject::canHaveNonEmptyElements() {
    100  return !this->is<TypedArrayObject>();
    101 }
    102 
    103 #endif  // DEBUG
    104 
    105 /* static */
    106 void ObjectElements::PrepareForPreventExtensions(JSContext* cx,
    107                                                 NativeObject* obj) {
    108  if (!obj->hasEmptyElements()) {
    109    obj->shrinkCapacityToInitializedLength(cx);
    110  }
    111 
    112  // shrinkCapacityToInitializedLength ensures there are no shifted elements.
    113  MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
    114 }
    115 
    116 /* static */
    117 void ObjectElements::PreventExtensions(NativeObject* obj) {
    118  MOZ_ASSERT(!obj->isExtensible());
    119  MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
    120  MOZ_ASSERT(obj->getDenseInitializedLength() == obj->getDenseCapacity());
    121 
    122  if (!obj->hasEmptyElements()) {
    123    obj->getElementsHeader()->setNotExtensible();
    124  }
    125 }
    126 
    127 /* static */
    128 bool ObjectElements::FreezeOrSeal(JSContext* cx, Handle<NativeObject*> obj,
    129                                  IntegrityLevel level) {
    130  MOZ_ASSERT_IF(level == IntegrityLevel::Frozen && obj->is<ArrayObject>(),
    131                !obj->as<ArrayObject>().lengthIsWritable());
    132  MOZ_ASSERT(!obj->isExtensible());
    133  MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
    134 
    135  if (obj->hasEmptyElements() || obj->denseElementsAreFrozen()) {
    136    return true;
    137  }
    138 
    139  if (level == IntegrityLevel::Frozen) {
    140    if (!JSObject::setFlag(cx, obj, ObjectFlag::FrozenElements)) {
    141      return false;
    142    }
    143  }
    144 
    145  if (!obj->denseElementsAreSealed()) {
    146    obj->getElementsHeader()->seal();
    147  }
    148 
    149  if (level == IntegrityLevel::Frozen) {
    150    obj->getElementsHeader()->freeze();
    151  }
    152 
    153  return true;
    154 }
    155 
    156 #if defined(DEBUG) || defined(JS_JITSPEW)
    157 
    158 template <typename KnownF, typename UnknownF>
    159 void ForEachObjectElementsFlag(uint16_t flags, KnownF known, UnknownF unknown) {
    160  for (uint16_t i = 1; i; i = i << 1) {
    161    if (!(flags & i)) {
    162      continue;
    163    }
    164    switch (ObjectElements::Flags(flags & i)) {
    165      case ObjectElements::Flags::FIXED:
    166        known("FIXED");
    167        break;
    168      case ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH:
    169        known("NONWRITABLE_ARRAY_LENGTH");
    170        break;
    171      case ObjectElements::Flags::SHARED_MEMORY:
    172        known("SHARED_MEMORY");
    173        break;
    174      case ObjectElements::Flags::NOT_EXTENSIBLE:
    175        known("NOT_EXTENSIBLE");
    176        break;
    177      case ObjectElements::Flags::SEALED:
    178        known("SEALED");
    179        break;
    180      case ObjectElements::Flags::FROZEN:
    181        known("FROZEN");
    182        break;
    183      case ObjectElements::Flags::NON_PACKED:
    184        known("NON_PACKED");
    185        break;
    186      case ObjectElements::Flags::MAYBE_IN_ITERATION:
    187        known("MAYBE_IN_ITERATION");
    188        break;
    189      default:
    190        unknown(i);
    191        break;
    192    }
    193  }
    194 }
    195 
    196 void ObjectElements::dumpStringContent(js::GenericPrinter& out) const {
    197  out.printf("<(js::ObjectElements*)0x%p, flags=[", this);
    198 
    199  bool first = true;
    200  ForEachObjectElementsFlag(
    201      flags,
    202      [&](const char* name) {
    203        if (!first) {
    204          out.put(", ");
    205        }
    206        first = false;
    207 
    208        out.put(name);
    209      },
    210      [&](uint16_t value) {
    211        if (!first) {
    212          out.put(", ");
    213        }
    214        first = false;
    215 
    216        out.printf("Unknown(%04x)", value);
    217      });
    218  out.put("]");
    219 
    220  out.printf(", init=%u, capacity=%u, length=%u>", initializedLength, capacity,
    221             length);
    222 }
    223 #endif
    224 
    225 #ifdef DEBUG
    226 static mozilla::Atomic<bool, mozilla::Relaxed> gShapeConsistencyChecksEnabled(
    227    false);
    228 
    229 /* static */
    230 void js::NativeObject::enableShapeConsistencyChecks() {
    231  gShapeConsistencyChecksEnabled = true;
    232 }
    233 
    234 void js::NativeObject::checkShapeConsistency() {
    235  if (!gShapeConsistencyChecksEnabled) {
    236    return;
    237  }
    238 
    239  MOZ_ASSERT(is<NativeObject>());
    240 
    241  if (PropMap* map = shape()->propMap()) {
    242    map->checkConsistency(this);
    243  } else {
    244    MOZ_ASSERT(shape()->propMapLength() == 0);
    245  }
    246 }
    247 #endif
    248 
    249 #ifdef DEBUG
    250 
    251 bool js::NativeObject::slotInRange(uint32_t slot,
    252                                   SentinelAllowed sentinel) const {
    253  MOZ_ASSERT(!gc::IsForwarded(shape()));
    254  uint32_t capacity = numFixedSlots() + numDynamicSlots();
    255  if (sentinel == SENTINEL_ALLOWED) {
    256    return slot <= capacity;
    257  }
    258  return slot < capacity;
    259 }
    260 
    261 bool js::NativeObject::slotIsFixed(uint32_t slot) const {
    262  // We call numFixedSlotsMaybeForwarded() to allow reading slots of
    263  // associated objects in trace hooks that may be called during a moving GC.
    264  return slot < numFixedSlotsMaybeForwarded();
    265 }
    266 
    267 bool js::NativeObject::isNumFixedSlots(uint32_t nfixed) const {
    268  // We call numFixedSlotsMaybeForwarded() to allow reading slots of
    269  // associated objects in trace hooks that may be called during a moving GC.
    270  return nfixed == numFixedSlotsMaybeForwarded();
    271 }
    272 
    273 uint32_t js::NativeObject::outOfLineNumDynamicSlots() const {
    274  return numDynamicSlots();
    275 }
    276 #endif /* DEBUG */
    277 
    278 mozilla::Maybe<PropertyInfo> js::NativeObject::lookup(JSContext* cx, jsid id) {
    279  MOZ_ASSERT(is<NativeObject>());
    280  uint32_t index;
    281  if (PropMap* map = shape()->lookup(cx, id, &index)) {
    282    return mozilla::Some(map->getPropertyInfo(index));
    283  }
    284  return mozilla::Nothing();
    285 }
    286 
    287 mozilla::Maybe<PropertyInfo> js::NativeObject::lookupPure(jsid id) {
    288  MOZ_ASSERT(is<NativeObject>());
    289  uint32_t index;
    290  if (PropMap* map = shape()->lookupPure(id, &index)) {
    291    return mozilla::Some(map->getPropertyInfo(index));
    292  }
    293  return mozilla::Nothing();
    294 }
    295 
    296 bool NativeObject::setUniqueId(JSRuntime* runtime, uint64_t uid) {
    297  MOZ_ASSERT(!hasUniqueId());
    298  MOZ_ASSERT(!gc::HasUniqueId(this));
    299 
    300  Nursery& nursery = runtime->gc.nursery();
    301  if (!hasDynamicSlots() && !allocateSlots(nursery, 0)) {
    302    return false;
    303  }
    304 
    305  getSlotsHeader()->setUniqueId(uid);
    306  return true;
    307 }
    308 
    309 bool NativeObject::setOrUpdateUniqueId(JSContext* cx, uint64_t uid) {
    310  if (!hasDynamicSlots() && !allocateSlots(cx->nursery(), 0)) {
    311    ReportOutOfMemory(cx);
    312    return false;
    313  }
    314 
    315  getSlotsHeader()->setUniqueId(uid);
    316  return true;
    317 }
    318 
    319 bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity,
    320                             uint32_t newCapacity) {
    321  MOZ_ASSERT(newCapacity > oldCapacity);
    322 
    323  /*
    324   * Slot capacities are determined by the span of allocated objects. Due to
    325   * the limited number of bits to store shape slots, object growth is
    326   * throttled well before the slot capacity can overflow.
    327   */
    328  NativeObject::slotsSizeMustNotOverflow();
    329  MOZ_ASSERT(newCapacity <= MAX_SLOTS_COUNT);
    330 
    331  if (!hasDynamicSlots()) {
    332    if (!allocateSlots(cx->nursery(), newCapacity)) {
    333      ReportOutOfMemory(cx);
    334      return false;
    335    }
    336 
    337    return true;
    338  }
    339 
    340  uint64_t uid = maybeUniqueId();
    341 
    342  uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
    343 
    344  uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
    345 
    346  uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity);
    347 
    348  ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_);
    349  MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity);
    350 
    351  HeapSlot* allocation = ReallocateCellBuffer<HeapSlot>(
    352      cx, this, reinterpret_cast<HeapSlot*>(oldHeaderSlots), oldAllocated,
    353      newAllocated);
    354  if (!allocation) {
    355    return false; /* Leave slots at its old size. */
    356  }
    357 
    358  auto* newHeaderSlots =
    359      new (allocation) ObjectSlots(newCapacity, dictionarySpan, uid);
    360  slots_ = newHeaderSlots->slots();
    361 
    362  Debug_SetSlotRangeToCrashOnTouch(slots_ + oldCapacity,
    363                                   newCapacity - oldCapacity);
    364 
    365  MOZ_ASSERT(hasDynamicSlots());
    366  return true;
    367 }
    368 
    369 bool NativeObject::growSlotsForNewSlot(JSContext* cx, uint32_t numFixed,
    370                                       uint32_t slot) {
    371  MOZ_ASSERT(slotSpan() == slot);
    372  MOZ_ASSERT(shape()->numFixedSlots() == numFixed);
    373  MOZ_ASSERT(slot >= numFixed);
    374 
    375  uint32_t newCapacity = calculateDynamicSlots(numFixed, slot + 1, getClass());
    376 
    377  uint32_t oldCapacity = numDynamicSlots();
    378  MOZ_ASSERT(oldCapacity < newCapacity);
    379 
    380  return growSlots(cx, oldCapacity, newCapacity);
    381 }
    382 
    383 bool NativeObject::allocateInitialSlots(JSContext* cx, uint32_t capacity) {
    384  uint32_t count = ObjectSlots::allocCount(capacity);
    385  HeapSlot* allocation = AllocateCellBuffer<HeapSlot>(cx, this, count);
    386  if (MOZ_UNLIKELY(!allocation)) {
    387    // The new object will be unreachable, but we have to make it safe for
    388    // finalization. It can also be observed with dumpHeap().
    389    // Give it a dummy shape that has no dynamic slots.
    390    setShape(GlobalObject::getEmptyPlainObjectShape(cx));
    391    initEmptyDynamicSlots();
    392    return false;
    393  }
    394 
    395  auto* headerSlots = new (allocation)
    396      ObjectSlots(capacity, 0, ObjectSlots::NoUniqueIdInDynamicSlots);
    397  slots_ = headerSlots->slots();
    398 
    399  Debug_SetSlotRangeToCrashOnTouch(slots_, capacity);
    400 
    401  MOZ_ASSERT(hasDynamicSlots());
    402  return true;
    403 }
    404 
    405 bool NativeObject::allocateSlots(Nursery& nursery, uint32_t newCapacity) {
    406  MOZ_ASSERT(!hasUniqueId());
    407  MOZ_ASSERT(!hasDynamicSlots());
    408 
    409  uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
    410 
    411  uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
    412 
    413  HeapSlot* allocation =
    414      AllocateCellBuffer<HeapSlot>(nursery, zone(), this, newAllocated);
    415  if (!allocation) {
    416    return false;
    417  }
    418 
    419  auto* newHeaderSlots = new (allocation) ObjectSlots(
    420      newCapacity, dictionarySpan, ObjectSlots::NoUniqueIdInDynamicSlots);
    421  slots_ = newHeaderSlots->slots();
    422 
    423  Debug_SetSlotRangeToCrashOnTouch(slots_, newCapacity);
    424 
    425  MOZ_ASSERT(hasDynamicSlots());
    426  return true;
    427 }
    428 
    429 /* static */
    430 bool NativeObject::growSlotsPure(JSContext* cx, NativeObject* obj,
    431                                 uint32_t newCapacity) {
    432  // IC code calls this directly.
    433  AutoUnsafeCallWithABI unsafe;
    434 
    435  if (!obj->growSlots(cx, obj->numDynamicSlots(), newCapacity)) {
    436    cx->recoverFromOutOfMemory();
    437    return false;
    438  }
    439 
    440  return true;
    441 }
    442 
    443 /* static */
    444 bool NativeObject::addDenseElementPure(JSContext* cx, NativeObject* obj) {
    445  // IC code calls this directly.
    446  AutoUnsafeCallWithABI unsafe;
    447 
    448  MOZ_ASSERT(obj->isExtensible());
    449  MOZ_ASSERT(!obj->isIndexed());
    450  MOZ_ASSERT(!obj->is<TypedArrayObject>());
    451  MOZ_ASSERT_IF(obj->is<ArrayObject>(),
    452                obj->as<ArrayObject>().lengthIsWritable());
    453 
    454  // growElements will report OOM also if the number of dense elements will
    455  // exceed MAX_DENSE_ELEMENTS_COUNT. See goodElementsAllocationAmount.
    456  uint32_t oldCapacity = obj->getDenseCapacity();
    457  if (MOZ_UNLIKELY(!obj->growElements(cx, oldCapacity + 1))) {
    458    cx->recoverFromOutOfMemory();
    459    return false;
    460  }
    461 
    462  MOZ_ASSERT(obj->getDenseCapacity() > oldCapacity);
    463  MOZ_ASSERT(obj->getDenseCapacity() <= MAX_DENSE_ELEMENTS_COUNT);
    464  return true;
    465 }
    466 
    467 void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity,
    468                               uint32_t newCapacity) {
    469  MOZ_ASSERT(hasDynamicSlots());
    470  MOZ_ASSERT(newCapacity < oldCapacity);
    471  MOZ_ASSERT(oldCapacity == getSlotsHeader()->capacity());
    472 
    473  ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_);
    474  MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity);
    475 
    476  uint64_t uid = maybeUniqueId();
    477 
    478  uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity);
    479 
    480  if (newCapacity == 0 && uid == 0) {
    481    if (gc::IsBufferAlloc(oldHeaderSlots)) {
    482      gc::FreeBuffer(zone(), oldHeaderSlots);
    483    }
    484    // dictionarySlotSpan is initialized to the correct value by the callers.
    485    setEmptyDynamicSlots(0);
    486    return;
    487  }
    488 
    489  MOZ_ASSERT_IF(!is<ArrayObject>() && !hasUniqueId(),
    490                newCapacity >= SLOT_CAPACITY_MIN);
    491 
    492  uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
    493 
    494  uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
    495 
    496  HeapSlot* allocation = ReallocateCellBuffer<HeapSlot>(
    497      cx, this, reinterpret_cast<HeapSlot*>(oldHeaderSlots), oldAllocated,
    498      newAllocated);
    499  if (!allocation) {
    500    // It's possible for realloc to fail when shrinking an allocation. In this
    501    // case we continue using the original allocation but still update the
    502    // capacity to the new requested capacity, which is smaller than the actual
    503    // capacity.
    504    cx->recoverFromOutOfMemory();
    505    allocation = reinterpret_cast<HeapSlot*>(getSlotsHeader());
    506  }
    507 
    508  auto* newHeaderSlots =
    509      new (allocation) ObjectSlots(newCapacity, dictionarySpan, uid);
    510  slots_ = newHeaderSlots->slots();
    511 }
    512 
    513 void NativeObject::initFixedElements(gc::AllocKind kind, uint32_t length) {
    514  uint32_t capacity =
    515      gc::GetGCKindSlots(kind) - ObjectElements::VALUES_PER_HEADER;
    516 
    517  setFixedElements();
    518  new (getElementsHeader()) ObjectElements(capacity, length);
    519  getElementsHeader()->flags |= ObjectElements::FIXED;
    520 
    521  MOZ_ASSERT(hasFixedElements());
    522 }
    523 
    524 bool NativeObject::willBeSparseElements(uint32_t requiredCapacity,
    525                                        uint32_t newElementsHint) {
    526  MOZ_ASSERT(is<NativeObject>());
    527  MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
    528 
    529  uint32_t cap = getDenseCapacity();
    530  MOZ_ASSERT(requiredCapacity >= cap);
    531 
    532  if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT) {
    533    return true;
    534  }
    535 
    536  uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
    537  if (newElementsHint >= minimalDenseCount) {
    538    return false;
    539  }
    540  minimalDenseCount -= newElementsHint;
    541 
    542  if (minimalDenseCount > cap) {
    543    return true;
    544  }
    545 
    546  uint32_t initLen = getDenseInitializedLength();
    547  if (denseElementsArePacked()) {
    548    return minimalDenseCount > initLen;
    549  }
    550 
    551  const Value* elems = getDenseElements();
    552  for (uint32_t i = 0; i < initLen; i++) {
    553    if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) {
    554      return false;
    555    }
    556  }
    557  return true;
    558 }
    559 
    560 /* static */
    561 DenseElementResult NativeObject::maybeDensifySparseElements(
    562    JSContext* cx, Handle<NativeObject*> obj) {
    563  /*
    564   * Wait until after the object goes into dictionary mode, which must happen
    565   * when sparsely packing any array with more than MIN_SPARSE_INDEX elements
    566   * (see PropertyTree::MAX_HEIGHT).
    567   */
    568  if (!obj->inDictionaryMode()) {
    569    return DenseElementResult::Incomplete;
    570  }
    571 
    572  /*
    573   * Only measure the number of indexed properties every log(n) times when
    574   * populating the object.
    575   */
    576  uint32_t slotSpan = obj->slotSpan();
    577  if (slotSpan != RoundUpPow2(slotSpan)) {
    578    return DenseElementResult::Incomplete;
    579  }
    580 
    581  /* Watch for conditions under which an object's elements cannot be dense. */
    582  if (!obj->isExtensible()) {
    583    return DenseElementResult::Incomplete;
    584  }
    585 
    586  /*
    587   * The indexes in the object need to be sufficiently dense before they can
    588   * be converted to dense mode.
    589   */
    590  uint32_t numDenseElements = 0;
    591  uint32_t newInitializedLength = 0;
    592 
    593  for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
    594    uint32_t index;
    595    if (!IdIsIndex(iter->key(), &index)) {
    596      continue;
    597    }
    598    if (iter->flags() != PropertyFlags::defaultDataPropFlags) {
    599      // For simplicity, only densify the object if all indexed properties can
    600      // be converted to dense elements.
    601      return DenseElementResult::Incomplete;
    602    }
    603    MOZ_ASSERT(iter->isDataProperty());
    604    numDenseElements++;
    605    newInitializedLength = std::max(newInitializedLength, index + 1);
    606  }
    607 
    608  if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) {
    609    return DenseElementResult::Incomplete;
    610  }
    611 
    612  if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT) {
    613    return DenseElementResult::Incomplete;
    614  }
    615 
    616  /*
    617   * This object meets all necessary restrictions, convert all indexed
    618   * properties into dense elements.
    619   */
    620 
    621  if (newInitializedLength > obj->getDenseCapacity()) {
    622    if (!obj->growElements(cx, newInitializedLength)) {
    623      return DenseElementResult::Failure;
    624    }
    625  }
    626 
    627  obj->ensureDenseInitializedLength(newInitializedLength, 0);
    628 
    629  if (obj->compartment()->objectMaybeInIteration(obj)) {
    630    // Mark the densified elements as maybe-in-iteration. See also the comment
    631    // in GetIterator.
    632    obj->markDenseElementsMaybeInIteration();
    633  }
    634 
    635  if (!NativeObject::densifySparseElements(cx, obj)) {
    636    return DenseElementResult::Failure;
    637  }
    638 
    639  return DenseElementResult::Success;
    640 }
    641 
    642 void NativeObject::moveShiftedElements() {
    643  MOZ_ASSERT(isExtensible());
    644 
    645  ObjectElements* header = getElementsHeader();
    646  uint32_t numShifted = header->numShiftedElements();
    647  MOZ_ASSERT(numShifted > 0);
    648 
    649  uint32_t initLength = header->initializedLength;
    650 
    651  ObjectElements* newHeader =
    652      static_cast<ObjectElements*>(getUnshiftedElementsHeader());
    653  memmove(newHeader, header, sizeof(ObjectElements));
    654 
    655  newHeader->clearShiftedElements();
    656  newHeader->capacity += numShifted;
    657  elements_ = newHeader->elements();
    658 
    659  // To move the elements, temporarily update initializedLength to include
    660  // the shifted elements.
    661  newHeader->initializedLength += numShifted;
    662 
    663  // Move the elements. Initialize to |undefined| to ensure pre-barriers
    664  // don't see garbage.
    665  for (size_t i = 0; i < numShifted; i++) {
    666    initDenseElement(i, UndefinedValue());
    667  }
    668  moveDenseElements(0, numShifted, initLength);
    669 
    670  // Restore the initialized length. We use setDenseInitializedLength to
    671  // make sure prepareElementRangeForOverwrite is called on the shifted
    672  // elements.
    673  setDenseInitializedLength(initLength);
    674 }
    675 
    676 void NativeObject::maybeMoveShiftedElements() {
    677  MOZ_ASSERT(isExtensible());
    678 
    679  ObjectElements* header = getElementsHeader();
    680  MOZ_ASSERT(header->numShiftedElements() > 0);
    681 
    682  // Move the elements if less than a third of the allocated space is in use.
    683  if (header->capacity < header->numAllocatedElements() / 3) {
    684    moveShiftedElements();
    685  }
    686 }
    687 
    688 bool NativeObject::tryUnshiftDenseElements(uint32_t count) {
    689  MOZ_ASSERT(isExtensible());
    690  MOZ_ASSERT(count > 0);
    691 
    692  ObjectElements* header = getElementsHeader();
    693  uint32_t numShifted = header->numShiftedElements();
    694 
    695  if (count > numShifted) {
    696    // We need more elements than are easily available. Try to make space
    697    // for more elements than we need (and shift the remaining ones) so
    698    // that unshifting more elements later will be fast.
    699 
    700    // Don't bother reserving elements if the number of elements is small.
    701    // Note that there's no technical reason for using this particular
    702    // limit.
    703    if (header->initializedLength <= 10 ||
    704        header->hasNonwritableArrayLength() ||
    705        MOZ_UNLIKELY(count > ObjectElements::MaxShiftedElements)) {
    706      return false;
    707    }
    708 
    709    MOZ_ASSERT(header->capacity >= header->initializedLength);
    710    uint32_t unusedCapacity = header->capacity - header->initializedLength;
    711 
    712    // Determine toShift, the number of extra elements we want to make
    713    // available.
    714    uint32_t toShift = count - numShifted;
    715    MOZ_ASSERT(toShift <= ObjectElements::MaxShiftedElements,
    716               "count <= MaxShiftedElements so toShift <= MaxShiftedElements");
    717 
    718    // Give up if we need to allocate more elements.
    719    if (toShift > unusedCapacity) {
    720      return false;
    721    }
    722 
    723    // Move more elements than we need, so that other unshift calls will be
    724    // fast. We just have to make sure we don't exceed unusedCapacity.
    725    toShift = std::min(toShift + unusedCapacity / 2, unusedCapacity);
    726 
    727    // Ensure |numShifted + toShift| does not exceed MaxShiftedElements.
    728    if (numShifted + toShift > ObjectElements::MaxShiftedElements) {
    729      toShift = ObjectElements::MaxShiftedElements - numShifted;
    730    }
    731 
    732    MOZ_ASSERT(count <= numShifted + toShift);
    733    MOZ_ASSERT(numShifted + toShift <= ObjectElements::MaxShiftedElements);
    734    MOZ_ASSERT(toShift <= unusedCapacity);
    735 
    736    // Now move/unshift the elements.
    737    uint32_t initLen = header->initializedLength;
    738    setDenseInitializedLength(initLen + toShift);
    739    for (uint32_t i = 0; i < toShift; i++) {
    740      initDenseElement(initLen + i, UndefinedValue());
    741    }
    742    moveDenseElements(toShift, 0, initLen);
    743 
    744    // Shift the elements we just prepended.
    745    shiftDenseElementsUnchecked(toShift);
    746 
    747    // We can now fall-through to the fast path below.
    748    header = getElementsHeader();
    749    MOZ_ASSERT(header->numShiftedElements() == numShifted + toShift);
    750 
    751    numShifted = header->numShiftedElements();
    752    MOZ_ASSERT(count <= numShifted);
    753  }
    754 
    755  elements_ -= count;
    756  ObjectElements* newHeader = getElementsHeader();
    757  memmove(newHeader, header, sizeof(ObjectElements));
    758 
    759  newHeader->unshiftShiftedElements(count);
    760 
    761  // Initialize to |undefined| to ensure pre-barriers don't see garbage.
    762  for (uint32_t i = 0; i < count; i++) {
    763    initDenseElement(i, UndefinedValue());
    764  }
    765 
    766  return true;
    767 }
    768 
    769 // Given a requested capacity (in elements) and (potentially) the length of an
    770 // array for which elements are being allocated, compute an actual allocation
    771 // amount (in elements).  (Allocation amounts include space for an
    772 // ObjectElements instance, so a return value of |N| implies
    773 // |N - ObjectElements::VALUES_PER_HEADER| usable elements.)
    774 //
    775 // The requested/actual allocation distinction is meant to:
    776 //
    777 //   * preserve amortized O(N) time to add N elements;
    778 //   * minimize the number of unused elements beyond an array's length, and
    779 //   * provide at least ELEMENT_CAPACITY_MIN elements no matter what (so adding
    780 //     the first several elements to small arrays only needs one allocation).
    781 /* static */
    782 bool NativeObject::goodElementsAllocationAmount(JSContext* cx,
    783                                                uint32_t reqCapacity,
    784                                                uint32_t length,
    785                                                uint32_t* goodAmount) {
    786  if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) {
    787    ReportOutOfMemory(cx);
    788    return false;
    789  }
    790 
    791  uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
    792 
    793  // Handle "small" requests primarily by doubling.
    794  const uint32_t Mebi = 1 << 20;
    795  if (reqAllocated < Mebi) {
    796    uint32_t amount =
    797        gc::GetGoodPower2ElementCount(reqAllocated, sizeof(Value));
    798 
    799    // If |amount| would be 2/3 or more of the array's length, adjust
    800    // it (up or down) to be equal to the array's length.  This avoids
    801    // allocating excess elements that aren't likely to be needed, either
    802    // in this resizing or a subsequent one.  The 2/3 factor is chosen so
    803    // that exceptional resizings will at most triple the capacity, as
    804    // opposed to the usual doubling.
    805    uint32_t goodCapacity = amount - ObjectElements::VALUES_PER_HEADER;
    806    if (length >= reqCapacity && goodCapacity > (length / 3) * 2) {
    807      amount = gc::GetGoodElementCount(
    808          length + ObjectElements::VALUES_PER_HEADER, sizeof(Value));
    809    }
    810 
    811    const size_t AmountMin =
    812        ELEMENT_CAPACITY_MIN + ObjectElements::VALUES_PER_HEADER;
    813 
    814    // Check this size doesn't waste any space in the allocation.
    815    MOZ_ASSERT(AmountMin == gc::GetGoodElementCount(AmountMin, sizeof(Value)));
    816 
    817    if (amount < AmountMin) {
    818      amount = AmountMin;
    819    }
    820 
    821    *goodAmount = amount;
    822 
    823    return true;
    824  }
    825 
    826  // The almost-doubling above wastes a lot of space for larger bucket sizes.
    827  // For large amounts, switch to bucket sizes that obey this formula:
    828  //
    829  //   count(n+1) = Math.ceil(count(n) * 1.125)
    830  //
    831  // where |count(n)| is the size of the nth bucket, measured in 2**20 slots.
    832  // These bucket sizes still preserve amortized O(N) time to add N elements,
    833  // just with a larger constant factor.
    834  //
    835  // The bucket size table below was generated with this JavaScript (and
    836  // manual reformatting):
    837  //
    838  //   for (let n = 1, i = 0; i < 34; i++) {
    839  //     print('0x' + (n * (1 << 20)).toString(16) + ', ');
    840  //     n = Math.ceil(n * 1.125);
    841  //   }
    842  static constexpr uint32_t BigBuckets[] = {
    843      0x100000,  0x200000,  0x300000,  0x400000,  0x500000,  0x600000,
    844      0x700000,  0x800000,  0x900000,  0xb00000,  0xd00000,  0xf00000,
    845      0x1100000, 0x1400000, 0x1700000, 0x1a00000, 0x1e00000, 0x2200000,
    846      0x2700000, 0x2c00000, 0x3200000, 0x3900000, 0x4100000, 0x4a00000,
    847      0x5400000, 0x5f00000, 0x6b00000, 0x7900000, 0x8900000, 0x9b00000,
    848      0xaf00000, 0xc500000, 0xde00000, 0xfa00000};
    849  static_assert(BigBuckets[std::size(BigBuckets) - 1] <=
    850                MAX_DENSE_ELEMENTS_ALLOCATION);
    851 
    852  // We will allocate these in large buffers so account for the header size
    853  // required there.
    854  static_assert(sizeof(Value) * Mebi >= gc::ChunkSize);
    855 
    856  // Pick the first bucket that'll fit |reqAllocated|.
    857  for (uint32_t b : BigBuckets) {
    858    if (b >= reqAllocated) {
    859      MOZ_ASSERT(b == gc::GetGoodElementCount(b, sizeof(Value)));
    860      *goodAmount = b;
    861      return true;
    862    }
    863  }
    864 
    865  // Otherwise, return the maximum bucket size.
    866  *goodAmount = MAX_DENSE_ELEMENTS_ALLOCATION;
    867  return true;
    868 }
    869 
    870 bool NativeObject::growElements(JSContext* cx, uint32_t reqCapacity) {
    871  MOZ_ASSERT(isExtensible());
    872  MOZ_ASSERT(canHaveNonEmptyElements());
    873 
    874  // If there are shifted elements, consider moving them first. If we don't
    875  // move them here, the code below will include the shifted elements in the
    876  // resize.
    877  uint32_t numShifted = getElementsHeader()->numShiftedElements();
    878  if (numShifted > 0) {
    879    // If the number of elements is small, it's cheaper to just move them as
    880    // it may avoid a malloc/realloc. Note that there's no technical reason
    881    // for using this particular value, but it works well in real-world use
    882    // cases.
    883    static const size_t MaxElementsToMoveEagerly = 20;
    884 
    885    if (getElementsHeader()->initializedLength <= MaxElementsToMoveEagerly) {
    886      moveShiftedElements();
    887    } else {
    888      maybeMoveShiftedElements();
    889    }
    890    if (getDenseCapacity() >= reqCapacity) {
    891      return true;
    892    }
    893    // moveShiftedElements() may have changed the number of shifted elements;
    894    // update `numShifted` accordingly.
    895    numShifted = getElementsHeader()->numShiftedElements();
    896 
    897    // If |reqCapacity + numShifted| overflows, we just move all shifted
    898    // elements to avoid the problem.
    899    CheckedInt<uint32_t> checkedReqCapacity(reqCapacity);
    900    checkedReqCapacity += numShifted;
    901    if (MOZ_UNLIKELY(!checkedReqCapacity.isValid())) {
    902      moveShiftedElements();
    903      numShifted = 0;
    904    }
    905  }
    906 
    907  uint32_t oldCapacity = getDenseCapacity();
    908  MOZ_ASSERT(oldCapacity < reqCapacity);
    909 
    910  uint32_t newAllocated = 0;
    911  if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) {
    912    // Preserve the |capacity <= length| invariant for arrays with
    913    // non-writable length.  See also js::ArraySetLength which initially
    914    // enforces this requirement.
    915    MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length());
    916    // Adding to reqCapacity must not overflow uint32_t.
    917    MOZ_ASSERT(reqCapacity <= MAX_DENSE_ELEMENTS_COUNT);
    918 
    919    // Then, add the header and shifted elements sizes to the new capacity
    920    // to get the overall amount to allocate.
    921    newAllocated = reqCapacity + numShifted + ObjectElements::VALUES_PER_HEADER;
    922  } else {
    923    // For arrays with writable length, and all non-Array objects, call
    924    // `NativeObject::goodElementsAllocationAmount()` to determine the
    925    // amount to allocate from the the requested capacity and existing length.
    926    uint32_t length = is<ArrayObject>() ? as<ArrayObject>().length() : 0;
    927    if (!goodElementsAllocationAmount(cx, reqCapacity + numShifted, length,
    928                                      &newAllocated)) {
    929      return false;
    930    }
    931  }
    932 
    933  // newAllocated now contains the size of the buffer we need to allocate;
    934  // subtract off the header and shifted elements size to get the new capacity
    935  uint32_t newCapacity =
    936      newAllocated - ObjectElements::VALUES_PER_HEADER - numShifted;
    937  // If the new capacity isn't strictly greater than the old capacity, then this
    938  // method shouldn't have been called; if the new capacity doesn't satisfy
    939  // what was requested, then one of the calculations above must have been
    940  // wrong.
    941  MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity);
    942 
    943  // If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become
    944  // sparse.
    945  MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
    946 
    947  uint32_t initlen = getDenseInitializedLength();
    948 
    949  HeapSlot* oldHeaderSlots =
    950      reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader());
    951  HeapSlot* newHeaderSlots;
    952  uint32_t oldAllocated = 0;
    953  if (hasDynamicElements()) {
    954    // If the object has dynamic elements, then we might be able to resize the
    955    // buffer in-place.
    956 
    957    // First, check that adding to oldCapacity won't overflow uint32_t
    958    MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
    959    // Then, add the header and shifted elements sizes to get the overall size
    960    // of the existing buffer
    961    oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted;
    962 
    963    // Finally, try to resize the buffer.
    964    newHeaderSlots = ReallocateCellBuffer<HeapSlot>(cx, this, oldHeaderSlots,
    965                                                    oldAllocated, newAllocated);
    966    if (!newHeaderSlots) {
    967      return false;  // If the resizing failed, then we leave elements at its
    968                     // old size.
    969    }
    970  } else {
    971    // If the object has fixed elements, then we always need to allocate a new
    972    // buffer, because if we've reached this code, then the requested capacity
    973    // is greater than the existing inline space available within the object
    974    newHeaderSlots = AllocateCellBuffer<HeapSlot>(cx, this, newAllocated);
    975    if (!newHeaderSlots) {
    976      return false;  // Leave elements at its old size.
    977    }
    978 
    979    // Copy the initialized elements into the new buffer,
    980    PodCopy(newHeaderSlots, oldHeaderSlots,
    981            ObjectElements::VALUES_PER_HEADER + initlen + numShifted);
    982  }
    983 
    984  ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
    985  // Update the elements pointer to point to the new elements buffer.
    986  elements_ = newheader->elements() + numShifted;
    987 
    988  // Clear the "fixed elements" flag, because if this code has been reached,
    989  // this object now has dynamic elements.
    990  getElementsHeader()->flags &= ~ObjectElements::FIXED;
    991  getElementsHeader()->capacity = newCapacity;
    992 
    993  // Poison the uninitialized portion of the new elements buffer.
    994  Debug_SetSlotRangeToCrashOnTouch(elements_ + initlen, newCapacity - initlen);
    995 
    996  return true;
    997 }
    998 
    999 void NativeObject::shrinkElements(JSContext* cx, uint32_t reqCapacity) {
   1000  MOZ_ASSERT(canHaveNonEmptyElements());
   1001  MOZ_ASSERT(reqCapacity >= getDenseInitializedLength());
   1002 
   1003  if (!hasDynamicElements()) {
   1004    return;
   1005  }
   1006 
   1007  // If we have shifted elements, consider moving them.
   1008  uint32_t numShifted = getElementsHeader()->numShiftedElements();
   1009  if (numShifted > 0) {
   1010    maybeMoveShiftedElements();
   1011    numShifted = getElementsHeader()->numShiftedElements();
   1012  }
   1013 
   1014  uint32_t oldCapacity = getDenseCapacity();
   1015  MOZ_ASSERT(reqCapacity < oldCapacity);
   1016 
   1017  uint32_t newAllocated = 0;
   1018  MOZ_ALWAYS_TRUE(goodElementsAllocationAmount(cx, reqCapacity + numShifted, 0,
   1019                                               &newAllocated));
   1020  MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
   1021 
   1022  uint32_t oldAllocated =
   1023      oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted;
   1024  if (newAllocated == oldAllocated) {
   1025    return;  // Leave elements at its old size.
   1026  }
   1027 
   1028  MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER);
   1029  uint32_t newCapacity =
   1030      newAllocated - ObjectElements::VALUES_PER_HEADER - numShifted;
   1031  MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
   1032 
   1033  HeapSlot* oldHeaderSlots =
   1034      reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader());
   1035  HeapSlot* newHeaderSlots = ReallocateCellBuffer<HeapSlot>(
   1036      cx, this, oldHeaderSlots, oldAllocated, newAllocated);
   1037  if (!newHeaderSlots) {
   1038    cx->recoverFromOutOfMemory();
   1039    return;  // Leave elements at its old size.
   1040  }
   1041 
   1042  ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
   1043  elements_ = newheader->elements() + numShifted;
   1044  getElementsHeader()->capacity = newCapacity;
   1045 }
   1046 
   1047 void NativeObject::shrinkCapacityToInitializedLength(JSContext* cx) {
   1048  // When an array's length becomes non-writable, writes to indexes greater
   1049  // greater than or equal to the length don't change the array.  We handle this
   1050  // with a check for non-writable length in most places. But in JIT code every
   1051  // check counts -- so we piggyback the check on the already-required range
   1052  // check for |index < capacity| by making capacity of arrays with non-writable
   1053  // length never exceed the length. This mechanism is also used when an object
   1054  // becomes non-extensible.
   1055 
   1056  if (getElementsHeader()->numShiftedElements() > 0) {
   1057    moveShiftedElements();
   1058  }
   1059 
   1060  ObjectElements* header = getElementsHeader();
   1061  uint32_t len = header->initializedLength;
   1062  MOZ_ASSERT(header->capacity >= len);
   1063  if (header->capacity == len) {
   1064    return;
   1065  }
   1066 
   1067  shrinkElements(cx, len);
   1068 
   1069  getElementsHeader()->capacity = len;
   1070 }
   1071 
   1072 /* static */
   1073 bool NativeObject::allocDictionarySlot(JSContext* cx, Handle<NativeObject*> obj,
   1074                                       uint32_t* slotp) {
   1075  MOZ_ASSERT(obj->inDictionaryMode());
   1076 
   1077  uint32_t slotSpan = obj->slotSpan();
   1078  MOZ_ASSERT(slotSpan >= JSSLOT_FREE(obj->getClass()));
   1079 
   1080  // Try to pull a free slot from the slot-number free list.
   1081  DictionaryPropMap* map = obj->dictionaryShape()->propMap();
   1082  uint32_t last = map->freeList();
   1083  if (last != SHAPE_INVALID_SLOT) {
   1084 #ifdef DEBUG
   1085    MOZ_ASSERT(last < slotSpan);
   1086    uint32_t next = obj->getSlot(last).toPrivateUint32();
   1087    MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slotSpan);
   1088 #endif
   1089    *slotp = last;
   1090    const Value& vref = obj->getSlot(last);
   1091    map->setFreeList(vref.toPrivateUint32());
   1092    obj->setSlot(last, UndefinedValue());
   1093    return true;
   1094  }
   1095 
   1096  if (MOZ_UNLIKELY(slotSpan >= SHAPE_MAXIMUM_SLOT)) {
   1097    ReportOutOfMemory(cx);
   1098    return false;
   1099  }
   1100 
   1101  *slotp = slotSpan;
   1102 
   1103  uint32_t numFixed = obj->numFixedSlots();
   1104  if (slotSpan < numFixed) {
   1105    obj->initFixedSlot(slotSpan, UndefinedValue());
   1106    obj->setDictionaryModeSlotSpan(slotSpan + 1);
   1107    return true;
   1108  }
   1109 
   1110  uint32_t dynamicSlotIndex = slotSpan - numFixed;
   1111  if (dynamicSlotIndex >= obj->numDynamicSlots()) {
   1112    if (MOZ_UNLIKELY(!obj->growSlotsForNewSlot(cx, numFixed, slotSpan))) {
   1113      return false;
   1114    }
   1115  }
   1116  obj->initDynamicSlot(numFixed, slotSpan, UndefinedValue());
   1117  obj->setDictionaryModeSlotSpan(slotSpan + 1);
   1118  return true;
   1119 }
   1120 
   1121 void NativeObject::freeDictionarySlot(uint32_t slot) {
   1122  MOZ_ASSERT(inDictionaryMode());
   1123  MOZ_ASSERT(slot < slotSpan());
   1124 
   1125  DictionaryPropMap* map = dictionaryShape()->propMap();
   1126  uint32_t last = map->freeList();
   1127 
   1128  // Can't afford to check the whole free list, but let's check the head.
   1129  MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot);
   1130 
   1131  // Place all freed slots other than reserved slots (bug 595230) on the
   1132  // dictionary's free list.
   1133  if (JSSLOT_FREE(getClass()) <= slot) {
   1134    MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan());
   1135    setSlot(slot, PrivateUint32Value(last));
   1136    map->setFreeList(slot);
   1137  } else {
   1138    setSlot(slot, UndefinedValue());
   1139  }
   1140 }
   1141 
   1142 template <AllowGC allowGC>
   1143 bool js::NativeLookupOwnProperty(
   1144    JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
   1145    typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyResult* propp) {
   1146  return NativeLookupOwnPropertyInline<allowGC>(cx, obj, id, propp);
   1147 }
   1148 
   1149 template bool js::NativeLookupOwnProperty<CanGC>(JSContext* cx,
   1150                                                 Handle<NativeObject*> obj,
   1151                                                 HandleId id,
   1152                                                 PropertyResult* propp);
   1153 
   1154 template bool js::NativeLookupOwnProperty<NoGC>(JSContext* cx,
   1155                                                NativeObject* const& obj,
   1156                                                const jsid& id,
   1157                                                PropertyResult* propp);
   1158 
   1159 /*** [[DefineOwnProperty]] **************************************************/
   1160 
   1161 static bool CallJSAddPropertyOp(JSContext* cx, JSAddPropertyOp op,
   1162                                HandleObject obj, HandleId id, HandleValue v) {
   1163  AutoCheckRecursionLimit recursion(cx);
   1164  if (!recursion.check(cx)) {
   1165    return false;
   1166  }
   1167 
   1168  cx->check(obj, id, v);
   1169  return op(cx, obj, id, v);
   1170 }
   1171 
   1172 static MOZ_ALWAYS_INLINE bool CallAddPropertyHook(JSContext* cx,
   1173                                                  Handle<NativeObject*> obj,
   1174                                                  HandleId id,
   1175                                                  HandleValue value) {
   1176  JSAddPropertyOp addProperty = obj->getClass()->getAddProperty();
   1177  if (MOZ_UNLIKELY(addProperty)) {
   1178    if (!CallJSAddPropertyOp(cx, addProperty, obj, id, value)) {
   1179      NativeObject::removeProperty(cx, obj, id);
   1180      return false;
   1181    }
   1182  }
   1183  if (MOZ_UNLIKELY(obj->hasUnpreservedWrapper())) {
   1184    JS::Value objectWrapperSlot =
   1185        JS::GetReservedSlot(obj, JS_OBJECT_WRAPPER_SLOT);
   1186    if (objectWrapperSlot.isUndefined() || !objectWrapperSlot.toPrivate()) {
   1187      return true;
   1188    }
   1189 
   1190    MOZ_ALWAYS_TRUE(MaybePreserveDOMWrapper(cx, obj));
   1191    return JSObject::setFlag(cx, obj, ObjectFlag::HasPreservedWrapper);
   1192  }
   1193  return true;
   1194 }
   1195 
   1196 static MOZ_ALWAYS_INLINE bool CallAddPropertyHookDense(
   1197    JSContext* cx, Handle<NativeObject*> obj, uint32_t index,
   1198    HandleValue value) {
   1199  // Inline addProperty for array objects.
   1200  if (obj->is<ArrayObject>()) {
   1201    ArrayObject* arr = &obj->as<ArrayObject>();
   1202    uint32_t length = arr->length();
   1203    if (index >= length) {
   1204      arr->setLength(cx, index + 1);
   1205    }
   1206    return true;
   1207  }
   1208 
   1209  JSAddPropertyOp addProperty = obj->getClass()->getAddProperty();
   1210  if (MOZ_UNLIKELY(addProperty)) {
   1211    RootedId id(cx, PropertyKey::Int(index));
   1212    if (!CallJSAddPropertyOp(cx, addProperty, obj, id, value)) {
   1213      obj->setDenseElementHole(index);
   1214      return false;
   1215    }
   1216  }
   1217 
   1218  if (MOZ_UNLIKELY(obj->hasUnpreservedWrapper())) {
   1219    JS::Value objectWrapperSlot =
   1220        JS::GetReservedSlot(obj, JS_OBJECT_WRAPPER_SLOT);
   1221    if (objectWrapperSlot.isUndefined() || !objectWrapperSlot.toPrivate()) {
   1222      return true;
   1223    }
   1224 
   1225    if (objectWrapperSlot.isUndefined() || !objectWrapperSlot.toPrivate()) {
   1226      return true;
   1227    }
   1228 
   1229    MOZ_ALWAYS_TRUE(MaybePreserveDOMWrapper(cx, obj));
   1230    return JSObject::setFlag(cx, obj, ObjectFlag::HasPreservedWrapper);
   1231  }
   1232  return true;
   1233 }
   1234 
   1235 /**
   1236 * Determines whether a write to the given element on |arr| should fail
   1237 * because |arr| has a non-writable length, and writing that element would
   1238 * increase the length of the array.
   1239 */
   1240 static bool WouldDefinePastNonwritableLength(ArrayObject* arr, uint32_t index) {
   1241  return !arr->lengthIsWritable() && index >= arr->length();
   1242 }
   1243 
   1244 static bool CheckForNonFunctionGetterSetter(JSContext* cx,
   1245                                            Handle<GetterSetter*> gs,
   1246                                            Handle<NativeObject*> obj) {
   1247  bool nonFunctionGetter = gs->getter() && !gs->getter()->is<JSFunction>();
   1248  bool nonFunctionSetter = gs->setter() && !gs->setter()->is<JSFunction>();
   1249  if (MOZ_UNLIKELY(nonFunctionGetter || nonFunctionSetter)) {
   1250    return JSObject::setHasNonFunctionAccessor(cx, obj);
   1251  }
   1252  return true;
   1253 }
   1254 
   1255 static bool ChangeProperty(JSContext* cx, Handle<NativeObject*> obj,
   1256                           HandleId id, HandleObject getter,
   1257                           HandleObject setter, PropertyFlags flags,
   1258                           PropertyResult* existing, uint32_t* slotOut) {
   1259  MOZ_ASSERT(existing);
   1260 
   1261  Rooted<GetterSetter*> gs(cx);
   1262 
   1263  // If we're redefining a getter/setter property but the getter and setter
   1264  // objects are still the same, use the existing GetterSetter.
   1265  if (existing->isNativeProperty()) {
   1266    PropertyInfo prop = existing->propertyInfo();
   1267    if (prop.isAccessorProperty()) {
   1268      GetterSetter* current = obj->getGetterSetter(prop);
   1269      if (current->getter() == getter && current->setter() == setter) {
   1270        gs = current;
   1271      }
   1272    }
   1273  }
   1274 
   1275  if (!gs) {
   1276    gs = GetterSetter::create(cx, obj, getter, setter);
   1277    if (!gs) {
   1278      return false;
   1279    }
   1280    if (!CheckForNonFunctionGetterSetter(cx, gs, obj)) {
   1281      return false;
   1282    }
   1283  }
   1284 
   1285  if (existing->isNativeProperty()) {
   1286    Rooted<Value> value(cx, PrivateGCThingValue(gs));
   1287    if (!NativeObject::changeProperty(cx, obj, id, flags, slotOut)) {
   1288      return false;
   1289    }
   1290    Watchtower::watchPropertyValueChange<AllowGC::CanGC>(
   1291        cx, obj, id, value, existing->propertyInfo());
   1292    obj->setSlot(*slotOut, value);
   1293    return true;
   1294  }
   1295 
   1296  if (!NativeObject::addProperty(cx, obj, id, flags, slotOut)) {
   1297    return false;
   1298  }
   1299  obj->initSlot(*slotOut, PrivateGCThingValue(gs));
   1300  return true;
   1301 }
   1302 
   1303 static PropertyFlags ComputePropertyFlags(const PropertyDescriptor& desc) {
   1304  desc.assertComplete();
   1305 
   1306  PropertyFlags flags;
   1307  flags.setFlag(PropertyFlag::Configurable, desc.configurable());
   1308  flags.setFlag(PropertyFlag::Enumerable, desc.enumerable());
   1309 
   1310  if (desc.isDataDescriptor()) {
   1311    flags.setFlag(PropertyFlag::Writable, desc.writable());
   1312  } else {
   1313    MOZ_ASSERT(desc.isAccessorDescriptor());
   1314    flags.setFlag(PropertyFlag::AccessorProperty);
   1315  }
   1316 
   1317  return flags;
   1318 }
   1319 
   1320 // Whether we're adding a new property or changing an existing property (this
   1321 // can be either a property stored in the shape tree or a dense element).
   1322 enum class IsAddOrChange { Add, Change };
   1323 
   1324 template <IsAddOrChange AddOrChange>
   1325 static MOZ_ALWAYS_INLINE bool AddOrChangeProperty(
   1326    JSContext* cx, Handle<NativeObject*> obj, HandleId id,
   1327    Handle<PropertyDescriptor> desc, PropertyResult* existing = nullptr) {
   1328  desc.assertComplete();
   1329 
   1330 #ifdef DEBUG
   1331  if constexpr (AddOrChange == IsAddOrChange::Add) {
   1332    MOZ_ASSERT(existing == nullptr);
   1333    MOZ_ASSERT(!obj->containsPure(id));
   1334  } else {
   1335    static_assert(AddOrChange == IsAddOrChange::Change);
   1336    MOZ_ASSERT(existing);
   1337    MOZ_ASSERT(existing->isNativeProperty() || existing->isDenseElement());
   1338  }
   1339 #endif
   1340 
   1341  // Use dense storage for indexed properties where possible: when we have an
   1342  // integer key with default property attributes and are either adding a new
   1343  // property or changing a dense element.
   1344  PropertyFlags flags = ComputePropertyFlags(desc);
   1345  if (id.isInt() && flags == PropertyFlags::defaultDataPropFlags &&
   1346      (AddOrChange == IsAddOrChange::Add || existing->isDenseElement())) {
   1347    MOZ_ASSERT(!desc.isAccessorDescriptor());
   1348    MOZ_ASSERT(!obj->is<TypedArrayObject>());
   1349    uint32_t index = id.toInt();
   1350    DenseElementResult edResult = obj->ensureDenseElements(cx, index, 1);
   1351    if (edResult == DenseElementResult::Failure) {
   1352      return false;
   1353    }
   1354    if (edResult == DenseElementResult::Success) {
   1355      obj->setDenseElement(index, desc.value());
   1356      if (!CallAddPropertyHookDense(cx, obj, index, desc.value())) {
   1357        return false;
   1358      }
   1359      return true;
   1360    }
   1361  }
   1362 
   1363  uint32_t slot;
   1364  if constexpr (AddOrChange == IsAddOrChange::Add) {
   1365    if (desc.isAccessorDescriptor()) {
   1366      Rooted<GetterSetter*> gs(
   1367          cx, GetterSetter::create(cx, obj, desc.getter(), desc.setter()));
   1368      if (!gs) {
   1369        return false;
   1370      }
   1371      if (!CheckForNonFunctionGetterSetter(cx, gs, obj)) {
   1372        return false;
   1373      }
   1374 
   1375      if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
   1376        return false;
   1377      }
   1378      obj->initSlot(slot, PrivateGCThingValue(gs));
   1379    } else {
   1380      if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
   1381        return false;
   1382      }
   1383      obj->initSlot(slot, desc.value());
   1384    }
   1385  } else {
   1386    if (desc.isAccessorDescriptor()) {
   1387      if (!ChangeProperty(cx, obj, id, desc.getter(), desc.setter(), flags,
   1388                          existing, &slot)) {
   1389        return false;
   1390      }
   1391    } else {
   1392      if (existing->isNativeProperty()) {
   1393        if (!NativeObject::changeProperty(cx, obj, id, flags, &slot)) {
   1394          return false;
   1395        }
   1396        Watchtower::watchPropertyValueChange<AllowGC::CanGC>(
   1397            cx, obj, id, desc.value(), existing->propertyInfo());
   1398        obj->setSlot(slot, desc.value());
   1399      } else {
   1400        if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
   1401          return false;
   1402        }
   1403        obj->initSlot(slot, desc.value());
   1404      }
   1405    }
   1406  }
   1407 
   1408  MOZ_ASSERT(slot < obj->slotSpan());
   1409 
   1410  // Clear any existing dense index after adding a sparse indexed property,
   1411  // and investigate converting the object to dense indexes.
   1412  if (id.isInt()) {
   1413    uint32_t index = id.toInt();
   1414    if constexpr (AddOrChange == IsAddOrChange::Add) {
   1415      MOZ_ASSERT(!obj->containsDenseElement(index));
   1416    } else {
   1417      obj->removeDenseElementForSparseIndex(index);
   1418    }
   1419    // Only try to densify sparse elements if the property we just added/changed
   1420    // is in the last slot. This avoids a perf cliff in pathological cases: in
   1421    // maybeDensifySparseElements we densify if the slot span is a power-of-two,
   1422    // but if we get slots from the free list, the slot span will stay the same
   1423    // until the free list is empty. This means we'd get quadratic behavior by
   1424    // trying to densify for each sparse element we add. See bug 1782487.
   1425    if (slot == obj->slotSpan() - 1) {
   1426      DenseElementResult edResult =
   1427          NativeObject::maybeDensifySparseElements(cx, obj);
   1428      if (edResult == DenseElementResult::Failure) {
   1429        return false;
   1430      }
   1431      if (edResult == DenseElementResult::Success) {
   1432        MOZ_ASSERT(!desc.isAccessorDescriptor());
   1433        return CallAddPropertyHookDense(cx, obj, index, desc.value());
   1434      }
   1435    }
   1436  }
   1437 
   1438  if (desc.isDataDescriptor()) {
   1439    return CallAddPropertyHook(cx, obj, id, desc.value());
   1440  }
   1441 
   1442  return CallAddPropertyHook(cx, obj, id, UndefinedHandleValue);
   1443 }
   1444 
   1445 // Versions of AddOrChangeProperty optimized for adding a plain data property.
   1446 // This function doesn't handle integer ids as we may have to store them in
   1447 // dense elements.
   1448 static MOZ_ALWAYS_INLINE bool AddDataProperty(JSContext* cx,
   1449                                              Handle<NativeObject*> obj,
   1450                                              HandleId id, HandleValue v) {
   1451  MOZ_ASSERT(!id.isInt());
   1452 
   1453  uint32_t slot;
   1454  if (!NativeObject::addProperty(cx, obj, id,
   1455                                 PropertyFlags::defaultDataPropFlags, &slot)) {
   1456    return false;
   1457  }
   1458 
   1459  obj->initSlot(slot, v);
   1460 
   1461  return CallAddPropertyHook(cx, obj, id, v);
   1462 }
   1463 
   1464 bool js::AddSlotAndCallAddPropHook(JSContext* cx, Handle<NativeObject*> obj,
   1465                                   HandleValue v, Handle<Shape*> newShape) {
   1466  MOZ_ASSERT(newShape->asShared().lastProperty().isDataProperty());
   1467 
   1468  RootedId id(cx, newShape->asShared().lastProperty().key());
   1469  MOZ_ASSERT(!id.isInt());
   1470 
   1471  bool hasUnpreservedWrapper = obj->hasUnpreservedWrapper();
   1472 
   1473  uint32_t slot = newShape->asShared().lastProperty().slot();
   1474  if (!obj->setShapeAndAddNewSlot(cx, &newShape->asShared(), slot)) {
   1475    return false;
   1476  }
   1477  obj->initSlot(slot, v);
   1478 
   1479  if (MOZ_UNLIKELY(hasUnpreservedWrapper)) {
   1480    MaybePreserveDOMWrapper(cx, obj);
   1481    MOZ_ASSERT(!obj->hasUnpreservedWrapper());
   1482  }
   1483 
   1484  return CallAddPropertyHook(cx, obj, id, v);
   1485 }
   1486 
   1487 static bool IsAccessorDescriptor(const PropertyResult& prop) {
   1488  if (prop.isNativeProperty()) {
   1489    return prop.propertyInfo().isAccessorProperty();
   1490  }
   1491 
   1492  MOZ_ASSERT(prop.isDenseElement() || prop.isTypedArrayElement());
   1493  return false;
   1494 }
   1495 
   1496 static bool IsDataDescriptor(const PropertyResult& prop) {
   1497  return !IsAccessorDescriptor(prop);
   1498 }
   1499 
   1500 static bool GetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
   1501                                  MutableHandleValue vp);
   1502 
   1503 static bool GetExistingDataProperty(JSContext* cx, Handle<NativeObject*> obj,
   1504                                    HandleId id, const PropertyResult& prop,
   1505                                    MutableHandleValue vp) {
   1506  if (prop.isDenseElement()) {
   1507    vp.set(obj->getDenseElement(prop.denseElementIndex()));
   1508    return true;
   1509  }
   1510  if (prop.isTypedArrayElement()) {
   1511    size_t idx = prop.typedArrayElementIndex();
   1512    return obj->as<TypedArrayObject>().getElement<CanGC>(cx, idx, vp);
   1513  }
   1514 
   1515  PropertyInfo propInfo = prop.propertyInfo();
   1516  if (propInfo.isDataProperty()) {
   1517    vp.set(obj->getSlot(propInfo.slot()));
   1518    return true;
   1519  }
   1520 
   1521  MOZ_RELEASE_ASSERT(propInfo.isCustomDataProperty());
   1522  return GetCustomDataProperty(cx, obj, id, vp);
   1523 }
   1524 
   1525 /*
   1526 * If desc is redundant with an existing own property obj[id], then set
   1527 * |*redundant = true| and return true.
   1528 */
   1529 static bool DefinePropertyIsRedundant(JSContext* cx, Handle<NativeObject*> obj,
   1530                                      HandleId id, const PropertyResult& prop,
   1531                                      JS::PropertyAttributes attrs,
   1532                                      Handle<PropertyDescriptor> desc,
   1533                                      bool* redundant) {
   1534  *redundant = false;
   1535 
   1536  if (desc.hasConfigurable() && desc.configurable() != attrs.configurable()) {
   1537    return true;
   1538  }
   1539  if (desc.hasEnumerable() && desc.enumerable() != attrs.enumerable()) {
   1540    return true;
   1541  }
   1542  if (desc.isDataDescriptor()) {
   1543    if (IsAccessorDescriptor(prop)) {
   1544      return true;
   1545    }
   1546    if (desc.hasWritable() && desc.writable() != attrs.writable()) {
   1547      return true;
   1548    }
   1549    if (desc.hasValue()) {
   1550      // Get the current value of the existing property.
   1551      RootedValue currentValue(cx);
   1552      if (!GetExistingDataProperty(cx, obj, id, prop, &currentValue)) {
   1553        return false;
   1554      }
   1555 
   1556      // Don't call SameValue here to ensure we properly update distinct
   1557      // NaN values.
   1558      if (desc.value() != currentValue) {
   1559        return true;
   1560      }
   1561    }
   1562 
   1563    // Check for custom data properties for ArrayObject/ArgumentsObject.
   1564    // PropertyDescriptor can't represent these properties so they're never
   1565    // redundant.
   1566    if (prop.isNativeProperty() && prop.propertyInfo().isCustomDataProperty()) {
   1567      return true;
   1568    }
   1569  } else if (desc.isAccessorDescriptor()) {
   1570    if (!prop.isNativeProperty()) {
   1571      return true;
   1572    }
   1573    PropertyInfo propInfo = prop.propertyInfo();
   1574    if (desc.hasGetter() && (!propInfo.isAccessorProperty() ||
   1575                             desc.getter() != obj->getGetter(propInfo))) {
   1576      return true;
   1577    }
   1578    if (desc.hasSetter() && (!propInfo.isAccessorProperty() ||
   1579                             desc.setter() != obj->getSetter(propInfo))) {
   1580      return true;
   1581    }
   1582  }
   1583 
   1584  *redundant = true;
   1585  return true;
   1586 }
   1587 
   1588 bool js::NativeDefineProperty(JSContext* cx, Handle<NativeObject*> obj,
   1589                              HandleId id, Handle<PropertyDescriptor> desc_,
   1590                              ObjectOpResult& result) {
   1591  desc_.assertValid();
   1592 
   1593  // Section numbers and step numbers below refer to ES2025, draft rev
   1594  // ac21460fedf4b926520b06c9820bdbebad596a8b.
   1595  //
   1596  // This function aims to implement 10.1.6 [[DefineOwnProperty]] as well as
   1597  // the [[DefineOwnProperty]] methods described in 10.4.2.1 (arrays), 10.4.4.2
   1598  // (arguments), and 10.4.5.3 (typed arrays).
   1599 
   1600  // Dispense with custom behavior of exotic native objects first.
   1601  if (obj->is<ArrayObject>()) {
   1602    // 10.4.2.1 step 1. Redefining an array's length is very special.
   1603    auto arr = HandleObject(obj).as<ArrayObject>();
   1604    if (id == NameToId(cx->names().length)) {
   1605      // 10.1.6.3 ValidateAndApplyPropertyDescriptor, step 5.c.
   1606      if (desc_.isAccessorDescriptor()) {
   1607        return result.fail(JSMSG_CANT_REDEFINE_PROP);
   1608      }
   1609 
   1610      return ArraySetLength(cx, arr, id, desc_, result);
   1611    }
   1612 
   1613    // 10.4.2.1 step 2. Don't extend a fixed-length array.
   1614    uint32_t index;
   1615    if (IdIsIndex(id, &index)) {
   1616      if (WouldDefinePastNonwritableLength(arr, index)) {
   1617        return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
   1618      }
   1619    }
   1620  } else if (obj->is<TypedArrayObject>()) {
   1621    // 10.4.5.3 step 1. Indexed properties of typed arrays are special.
   1622    if (mozilla::Maybe<uint64_t> index = ToTypedArrayIndex(id)) {
   1623      auto tobj = HandleObject(obj).as<TypedArrayObject>();
   1624      return DefineTypedArrayElement(cx, tobj, index.value(), desc_, result);
   1625    }
   1626  } else if (obj->is<ArgumentsObject>() && !desc_.resolving()) {
   1627    auto argsobj = HandleObject(obj).as<ArgumentsObject>();
   1628    if (id.isAtom(cx->names().length)) {
   1629      // Either we are resolving the .length property on this object,
   1630      // or redefining it. In the latter case only, we must reify the
   1631      // property.
   1632      if (!ArgumentsObject::reifyLength(cx, argsobj)) {
   1633        return false;
   1634      }
   1635    } else if (id.isAtom(cx->names().callee) &&
   1636               obj->is<MappedArgumentsObject>()) {
   1637      // Do same thing as .length for .callee on MappedArgumentsObject.
   1638      auto mapped = HandleObject(argsobj).as<MappedArgumentsObject>();
   1639      if (!MappedArgumentsObject::reifyCallee(cx, mapped)) {
   1640        return false;
   1641      }
   1642    } else if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
   1643      // Do same thing as .length for [@@iterator].
   1644      if (!ArgumentsObject::reifyIterator(cx, argsobj)) {
   1645        return false;
   1646      }
   1647    } else if (id.isInt()) {
   1648      argsobj->markElementOverridden();
   1649    }
   1650  }
   1651 
   1652  // 10.1.6.1 OrdinaryDefineOwnProperty step 1.
   1653  PropertyResult prop;
   1654  if (desc_.resolving()) {
   1655    // We are being called from a resolve or enumerate hook to reify a
   1656    // lazily-resolved property. To avoid reentering the resolve hook and
   1657    // recursing forever, skip the resolve hook when doing this lookup.
   1658    if (!NativeLookupOwnPropertyNoResolve(cx, obj, id, &prop)) {
   1659      return false;
   1660    }
   1661  } else {
   1662    if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
   1663      return false;
   1664    }
   1665  }
   1666  MOZ_ASSERT(!prop.isTypedArrayElement());
   1667 
   1668  // From this point, the step numbers refer to
   1669  // 10.1.6.3, ValidateAndApplyPropertyDescriptor.
   1670  // Step 1 is a redundant assertion.
   1671 
   1672  // Filling in desc: Here we make a copy of the desc_ argument. We will turn
   1673  // it into a complete descriptor before updating obj. The spec algorithm
   1674  // does not explicitly do this, but the end result is the same. Search for
   1675  // "fill in" below for places where the filling-in actually occurs.
   1676  Rooted<PropertyDescriptor> desc(cx, desc_);
   1677 
   1678  // Step 2.
   1679  if (prop.isNotFound()) {
   1680    // Note: We are sharing the property definition machinery with private
   1681    //       fields. Private fields may be added to non-extensible objects.
   1682    if (!obj->isExtensible() && !id.isPrivateName()) {
   1683      return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
   1684    }
   1685 
   1686    // Fill in missing desc fields with defaults.
   1687    CompletePropertyDescriptor(&desc);
   1688 
   1689    if (!AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc)) {
   1690      return false;
   1691    }
   1692    return result.succeed();
   1693  }
   1694 
   1695  // Step 3 is an assertion.
   1696 
   1697  // Step 4 and 5 (partially). Prop might not actually have a real shape, e.g.
   1698  // in the case of dense array elements, GetPropertyAttributes is used to
   1699  // paper-over that difference.
   1700  JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop);
   1701  bool redundant;
   1702  if (!DefinePropertyIsRedundant(cx, obj, id, prop, attrs, desc, &redundant)) {
   1703    return false;
   1704  }
   1705  if (redundant) {
   1706    return result.succeed();
   1707  }
   1708 
   1709  // Step 5.
   1710  if (!attrs.configurable()) {
   1711    // Step 5.a.
   1712    if (desc.hasConfigurable() && desc.configurable()) {
   1713      return result.fail(JSMSG_CANT_REDEFINE_PROP);
   1714    }
   1715 
   1716    // Step 5.b.
   1717    if (desc.hasEnumerable() && desc.enumerable() != attrs.enumerable()) {
   1718      return result.fail(JSMSG_CANT_REDEFINE_PROP);
   1719    }
   1720 
   1721    MOZ_ASSERT(
   1722        !desc.isGenericDescriptor(),
   1723        "redundant or conflicting generic property descriptor already handled");
   1724 
   1725    // Steps 5.c-d.
   1726    //
   1727    // If this is an existing accessor property and the property definition is
   1728    // non-redundant, this must be an attempt to change an accessor function or
   1729    // a redefinition to a data property. Both operations are invalid.
   1730    //
   1731    // If this is an existing data property and the incoming property descriptor
   1732    // is an accessor property descriptor, this is an invalid redefinition to an
   1733    // accessor property.
   1734    if (IsAccessorDescriptor(prop) || desc.isAccessorDescriptor()) {
   1735      return result.fail(JSMSG_CANT_REDEFINE_PROP);
   1736    }
   1737 
   1738    // Step 5.e.
   1739    if (!attrs.writable()) {
   1740      // Step 5.e.i.
   1741      if (desc.hasWritable() && desc.writable()) {
   1742        return result.fail(JSMSG_CANT_REDEFINE_PROP);
   1743      }
   1744 
   1745      // Step 5.e.ii.
   1746      if (desc.hasValue()) {
   1747        RootedValue currentValue(cx);
   1748        if (!GetExistingDataProperty(cx, obj, id, prop, &currentValue)) {
   1749          return false;
   1750        }
   1751 
   1752        bool same;
   1753        if (!SameValue(cx, desc.value(), currentValue, &same)) {
   1754          return false;
   1755        }
   1756        if (!same) {
   1757          return result.fail(JSMSG_CANT_REDEFINE_PROP);
   1758        }
   1759      }
   1760 
   1761      return result.succeed();
   1762    }
   1763  }
   1764 
   1765  // Fill in desc.[[Configurable]] and desc.[[Enumerable]] if missing.
   1766  if (!desc.hasConfigurable()) {
   1767    desc.setConfigurable(attrs.configurable());
   1768  }
   1769  if (!desc.hasEnumerable()) {
   1770    desc.setEnumerable(attrs.enumerable());
   1771  }
   1772 
   1773  // Step 6.
   1774  if (desc.isDataDescriptor()) {
   1775    // Fill in desc.[[Value]] and desc.[[Writable]].
   1776    if (IsDataDescriptor(prop)) {
   1777      if (!desc.hasValue()) {
   1778        RootedValue currentValue(cx);
   1779        if (!GetExistingDataProperty(cx, obj, id, prop, &currentValue)) {
   1780          return false;
   1781        }
   1782 
   1783        desc.setValue(currentValue);
   1784      }
   1785      if (!desc.hasWritable()) {
   1786        desc.setWritable(attrs.writable());
   1787      }
   1788    } else {
   1789      if (!desc.hasValue()) {
   1790        desc.setValue(UndefinedHandleValue);
   1791      }
   1792      if (!desc.hasWritable()) {
   1793        desc.setWritable(false);
   1794      }
   1795    }
   1796  } else if (desc.isAccessorDescriptor()) {
   1797    // Fill in desc.[[Get]] and desc.[[Set]] from shape.
   1798    if (IsAccessorDescriptor(prop)) {
   1799      PropertyInfo propInfo = prop.propertyInfo();
   1800      MOZ_ASSERT(propInfo.isAccessorProperty());
   1801      MOZ_ASSERT(desc.isAccessorDescriptor());
   1802 
   1803      if (!desc.hasGetter()) {
   1804        desc.setGetter(obj->getGetter(propInfo));
   1805      }
   1806      if (!desc.hasSetter()) {
   1807        desc.setSetter(obj->getSetter(propInfo));
   1808      }
   1809    } else {
   1810      if (!desc.hasGetter()) {
   1811        desc.setGetter(nullptr);
   1812      }
   1813      if (!desc.hasSetter()) {
   1814        desc.setSetter(nullptr);
   1815      }
   1816    }
   1817  } else {
   1818    MOZ_ASSERT(desc.isGenericDescriptor());
   1819 
   1820    // Fill in desc. A generic descriptor has none of these fields, so copy
   1821    // everything from shape.
   1822    MOZ_ASSERT(!desc.hasValue());
   1823    MOZ_ASSERT(!desc.hasWritable());
   1824    MOZ_ASSERT(!desc.hasGetter());
   1825    MOZ_ASSERT(!desc.hasSetter());
   1826    if (IsDataDescriptor(prop)) {
   1827      RootedValue currentValue(cx);
   1828      if (!GetExistingDataProperty(cx, obj, id, prop, &currentValue)) {
   1829        return false;
   1830      }
   1831      desc.setValue(currentValue);
   1832      desc.setWritable(attrs.writable());
   1833    } else {
   1834      PropertyInfo propInfo = prop.propertyInfo();
   1835      desc.setGetter(obj->getGetter(propInfo));
   1836      desc.setSetter(obj->getSetter(propInfo));
   1837    }
   1838  }
   1839  desc.assertComplete();
   1840 
   1841  if (!AddOrChangeProperty<IsAddOrChange::Change>(cx, obj, id, desc, &prop)) {
   1842    return false;
   1843  }
   1844 
   1845  // Step 7.
   1846  return result.succeed();
   1847 }
   1848 
   1849 bool js::NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj,
   1850                                  HandleId id, HandleValue value,
   1851                                  unsigned attrs, ObjectOpResult& result) {
   1852  Rooted<PropertyDescriptor> desc(cx, PropertyDescriptor::Data(value, attrs));
   1853  return NativeDefineProperty(cx, obj, id, desc, result);
   1854 }
   1855 
   1856 bool js::NativeDefineAccessorProperty(JSContext* cx, Handle<NativeObject*> obj,
   1857                                      HandleId id, HandleObject getter,
   1858                                      HandleObject setter, unsigned attrs) {
   1859  Rooted<PropertyDescriptor> desc(
   1860      cx, PropertyDescriptor::Accessor(
   1861              getter ? mozilla::Some(getter) : mozilla::Nothing(),
   1862              setter ? mozilla::Some(setter) : mozilla::Nothing(), attrs));
   1863 
   1864  ObjectOpResult result;
   1865  if (!NativeDefineProperty(cx, obj, id, desc, result)) {
   1866    return false;
   1867  }
   1868 
   1869  if (!result) {
   1870    // Off-thread callers should not get here: they must call this
   1871    // function only with known-valid arguments. Populating a new
   1872    // PlainObject with configurable properties is fine.
   1873    result.reportError(cx, obj, id);
   1874    return false;
   1875  }
   1876 
   1877  return true;
   1878 }
   1879 
   1880 bool js::NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj,
   1881                                  HandleId id, HandleValue value,
   1882                                  unsigned attrs) {
   1883  ObjectOpResult result;
   1884  if (!NativeDefineDataProperty(cx, obj, id, value, attrs, result)) {
   1885    return false;
   1886  }
   1887  if (!result) {
   1888    // Off-thread callers should not get here: they must call this
   1889    // function only with known-valid arguments. Populating a new
   1890    // PlainObject with configurable properties is fine.
   1891    result.reportError(cx, obj, id);
   1892    return false;
   1893  }
   1894  return true;
   1895 }
   1896 
   1897 bool js::NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj,
   1898                                  PropertyName* name, HandleValue value,
   1899                                  unsigned attrs) {
   1900  RootedId id(cx, NameToId(name));
   1901  return NativeDefineDataProperty(cx, obj, id, value, attrs);
   1902 }
   1903 
   1904 static bool DefineNonexistentProperty(JSContext* cx, Handle<NativeObject*> obj,
   1905                                      HandleId id, HandleValue v,
   1906                                      ObjectOpResult& result) {
   1907  // Optimized NativeDefineProperty() version for known absent properties.
   1908 
   1909  // Dispense with custom behavior of exotic native objects first.
   1910  if (obj->is<ArrayObject>()) {
   1911    // Array's length property is non-configurable, so we shouldn't
   1912    // encounter it in this function.
   1913    MOZ_ASSERT(id != NameToId(cx->names().length));
   1914 
   1915    // 10.4.2.1 step 2. Don't extend a fixed-length array.
   1916    uint32_t index;
   1917    if (IdIsIndex(id, &index)) {
   1918      if (WouldDefinePastNonwritableLength(&obj->as<ArrayObject>(), index)) {
   1919        return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
   1920      }
   1921    }
   1922  } else if (obj->is<ArgumentsObject>()) {
   1923    // If this method is called with either |length| or |@@iterator|, the
   1924    // property was previously deleted and hence should already be marked
   1925    // as overridden.
   1926    MOZ_ASSERT_IF(id.isAtom(cx->names().length),
   1927                  obj->as<ArgumentsObject>().hasOverriddenLength());
   1928    MOZ_ASSERT_IF(id.isWellKnownSymbol(JS::SymbolCode::iterator),
   1929                  obj->as<ArgumentsObject>().hasOverriddenIterator());
   1930 
   1931    // We still need to mark any element properties as overridden.
   1932    if (id.isInt()) {
   1933      obj->as<ArgumentsObject>().markElementOverridden();
   1934    }
   1935  }
   1936 
   1937  // Indexed properties of typed arrays are handled by the caller.
   1938  MOZ_ASSERT_IF(obj->is<TypedArrayObject>(), ToTypedArrayIndex(id).isNothing());
   1939 
   1940 #ifdef DEBUG
   1941  PropertyResult prop;
   1942  if (!NativeLookupOwnPropertyNoResolve(cx, obj, id, &prop)) {
   1943    return false;
   1944  }
   1945  MOZ_ASSERT(prop.isNotFound(), "didn't expect to find an existing property");
   1946 #endif
   1947 
   1948  // 10.1.6.3, ValidateAndApplyPropertyDescriptor.
   1949  // Step 1 is a redundant assertion, step 3 and later don't apply here.
   1950 
   1951  // Step 2.
   1952  if (!obj->isExtensible()) {
   1953    return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
   1954  }
   1955 
   1956  if (id.isInt()) {
   1957    // This might be a dense element. Use AddOrChangeProperty as it knows
   1958    // how to deal with that.
   1959    Rooted<PropertyDescriptor> desc(
   1960        cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
   1961                                         JS::PropertyAttribute::Enumerable,
   1962                                         JS::PropertyAttribute::Writable}));
   1963    if (!AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc)) {
   1964      return false;
   1965    }
   1966  } else {
   1967    if (!AddDataProperty(cx, obj, id, v)) {
   1968      return false;
   1969    }
   1970  }
   1971 
   1972  return result.succeed();
   1973 }
   1974 
   1975 bool js::AddOrUpdateSparseElementHelper(JSContext* cx,
   1976                                        Handle<NativeObject*> obj,
   1977                                        int32_t int_id, HandleValue v,
   1978                                        bool strict) {
   1979  MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<PlainObject>());
   1980 
   1981  // This helper doesn't handle the case where the index is a dense element.
   1982  MOZ_ASSERT(int_id >= 0);
   1983  MOZ_ASSERT(!obj->containsDenseElement(int_id));
   1984 
   1985  MOZ_ASSERT(PropertyKey::fitsInInt(int_id));
   1986  RootedId id(cx, PropertyKey::Int(int_id));
   1987 
   1988  // First decide if this is an add or an update. Because the IC guards have
   1989  // already ensured this exists exterior to the dense array range, and the
   1990  // prototype checks have ensured there are no indexes on the prototype, we
   1991  // can use the shape lineage to find the element if it exists:
   1992  uint32_t index;
   1993  PropMap* map = obj->shape()->lookup(cx, id, &index);
   1994 
   1995  // If we didn't find the property, we're on the add path: delegate to
   1996  // AddOrChangeProperty. This will add either a sparse element or a dense
   1997  // element.
   1998  if (map == nullptr) {
   1999    Rooted<PropertyDescriptor> desc(
   2000        cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
   2001                                         JS::PropertyAttribute::Enumerable,
   2002                                         JS::PropertyAttribute::Writable}));
   2003    return AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc);
   2004  }
   2005 
   2006  // At this point we're updating a property: See SetExistingProperty.
   2007  PropertyInfo prop = map->getPropertyInfo(index);
   2008  if (prop.isDataProperty() && prop.writable()) {
   2009    obj->setSlot(prop.slot(), v);
   2010    return true;
   2011  }
   2012 
   2013  // We don't know exactly what this object looks like, hit the slowpath.
   2014  RootedValue receiver(cx, ObjectValue(*obj));
   2015  JS::ObjectOpResult result;
   2016  return SetProperty(cx, obj, id, v, receiver, result) &&
   2017         result.checkStrictModeError(cx, obj, id, strict);
   2018 }
   2019 
   2020 /*** [[HasProperty]] ********************************************************/
   2021 
   2022 /**
   2023 * 10.1.7.1 OrdinaryHasProperty ( O, P )
   2024 *
   2025 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b
   2026 */
   2027 bool js::NativeHasProperty(JSContext* cx, Handle<NativeObject*> obj,
   2028                           HandleId id, bool* foundp) {
   2029  Rooted<NativeObject*> pobj(cx, obj);
   2030  PropertyResult prop;
   2031 
   2032  // This loop isn't explicit in the spec algorithm. See the comment on step
   2033  // 4.a. below.
   2034  for (;;) {
   2035    // Step 1.
   2036    if (!NativeLookupOwnPropertyInline<CanGC>(cx, pobj, id, &prop)) {
   2037      return false;
   2038    }
   2039 
   2040    // Step 2.
   2041    if (prop.isFound()) {
   2042      *foundp = true;
   2043      return true;
   2044    }
   2045 
   2046    // Step 3.
   2047    JSObject* proto = pobj->staticPrototype();
   2048 
   2049    // Step 5. (Reordered)
   2050    // As a side-effect of NativeLookupOwnPropertyInline, we may determine that
   2051    // a property is not found and the proto chain should not be searched. This
   2052    // can occur for:
   2053    //  - Out-of-range numeric properties of a TypedArrayObject
   2054    //  - Recursive resolve hooks (which is expected when they try to set the
   2055    //    property being resolved).
   2056    if (!proto || prop.shouldIgnoreProtoChain()) {
   2057      *foundp = false;
   2058      return true;
   2059    }
   2060 
   2061    // Step 4.a. If the prototype is also native, this step is a recursive tail
   2062    // call, and we don't need to go through all the plumbing of HasProperty;
   2063    // the top of the loop is where we're going to end up anyway. But if |proto|
   2064    // is non-native, that optimization would be incorrect.
   2065    if (!proto->is<NativeObject>()) {
   2066      RootedObject protoRoot(cx, proto);
   2067      return HasProperty(cx, protoRoot, id, foundp);
   2068    }
   2069 
   2070    pobj = &proto->as<NativeObject>();
   2071  }
   2072 }
   2073 
   2074 /*** [[GetOwnPropertyDescriptor]] *******************************************/
   2075 
   2076 bool js::NativeGetOwnPropertyDescriptor(
   2077    JSContext* cx, Handle<NativeObject*> obj, HandleId id,
   2078    MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
   2079  PropertyResult prop;
   2080  if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
   2081    return false;
   2082  }
   2083  if (prop.isNotFound()) {
   2084    desc.reset();
   2085    return true;
   2086  }
   2087 
   2088  if (prop.isNativeProperty() && prop.propertyInfo().isAccessorProperty()) {
   2089    PropertyInfo propInfo = prop.propertyInfo();
   2090    desc.set(mozilla::Some(PropertyDescriptor::Accessor(
   2091        obj->getGetter(propInfo), obj->getSetter(propInfo),
   2092        propInfo.propAttributes())));
   2093    return true;
   2094  }
   2095 
   2096  RootedValue value(cx);
   2097  if (!GetExistingDataProperty(cx, obj, id, prop, &value)) {
   2098    return false;
   2099  }
   2100 
   2101  JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop);
   2102  desc.set(mozilla::Some(PropertyDescriptor::Data(value, attrs)));
   2103  return true;
   2104 }
   2105 
   2106 /*** [[Get]] ****************************************************************/
   2107 
   2108 static bool GetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
   2109                                  MutableHandleValue vp) {
   2110  cx->check(obj, id, vp);
   2111 
   2112  const JSClass* clasp = obj->getClass();
   2113  if (clasp == &ArrayObject::class_) {
   2114    if (!ArrayLengthGetter(cx, obj, id, vp)) {
   2115      return false;
   2116    }
   2117  } else if (clasp == &MappedArgumentsObject::class_) {
   2118    if (!MappedArgGetter(cx, obj, id, vp)) {
   2119      return false;
   2120    }
   2121  } else {
   2122    MOZ_RELEASE_ASSERT(clasp == &UnmappedArgumentsObject::class_);
   2123    if (!UnmappedArgGetter(cx, obj, id, vp)) {
   2124      return false;
   2125    }
   2126  }
   2127 
   2128  cx->check(vp);
   2129  return true;
   2130 }
   2131 
   2132 static inline bool CallGetter(JSContext* cx, Handle<NativeObject*> obj,
   2133                              HandleValue receiver, HandleId id,
   2134                              PropertyInfo prop, MutableHandleValue vp) {
   2135  MOZ_ASSERT(!prop.isDataProperty());
   2136 
   2137  if (prop.isAccessorProperty()) {
   2138    RootedValue getter(cx, obj->getGetterValue(prop));
   2139    return js::CallGetter(cx, receiver, getter, vp);
   2140  }
   2141 
   2142  MOZ_ASSERT(prop.isCustomDataProperty());
   2143 
   2144  return GetCustomDataProperty(cx, obj, id, vp);
   2145 }
   2146 
   2147 template <AllowGC allowGC>
   2148 static MOZ_ALWAYS_INLINE bool GetExistingProperty(
   2149    JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType receiver,
   2150    typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
   2151    typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyInfo prop,
   2152    typename MaybeRooted<Value, allowGC>::MutableHandleType vp) {
   2153  if (prop.isDataProperty()) {
   2154    vp.set(obj->getSlot(prop.slot()));
   2155    return true;
   2156  }
   2157 
   2158  vp.setUndefined();
   2159 
   2160  if (!prop.isCustomDataProperty() && !obj->hasGetter(prop)) {
   2161    return true;
   2162  }
   2163 
   2164  if constexpr (!allowGC) {
   2165    return false;
   2166  } else {
   2167    return CallGetter(cx, obj, receiver, id, prop, vp);
   2168  }
   2169 }
   2170 
   2171 bool js::NativeGetExistingProperty(JSContext* cx, HandleObject receiver,
   2172                                   Handle<NativeObject*> obj, HandleId id,
   2173                                   PropertyInfo prop, MutableHandleValue vp) {
   2174  RootedValue receiverValue(cx, ObjectValue(*receiver));
   2175  return GetExistingProperty<CanGC>(cx, receiverValue, obj, id, prop, vp);
   2176 }
   2177 
   2178 enum IsNameLookup { NotNameLookup = false, NameLookup = true };
   2179 
   2180 /*
   2181 * Finish getting the property `receiver[id]` after looking at every object on
   2182 * the prototype chain and not finding any such property.
   2183 *
   2184 * Per the spec, this should just set the result to `undefined` and call it a
   2185 * day. However this function also runs when we're evaluating an
   2186 * expression that's an Identifier (that is, an unqualified name lookup),
   2187 * so we need to figure out if that's what's happening and throw
   2188 * a ReferenceError if so.
   2189 */
   2190 template <AllowGC allowGC>
   2191 static bool GetNonexistentProperty(
   2192    JSContext* cx, typename MaybeRooted<jsid, allowGC>::HandleType id,
   2193    IsNameLookup nameLookup,
   2194    typename MaybeRooted<Value, allowGC>::MutableHandleType vp) {
   2195  // If we are doing a name lookup, this is a ReferenceError.
   2196  if (nameLookup) {
   2197    if constexpr (allowGC == AllowGC::CanGC) {
   2198      ReportIsNotDefined(cx, id);
   2199    }
   2200    return false;
   2201  }
   2202 
   2203  // Otherwise, just return |undefined|.
   2204  vp.setUndefined();
   2205  return true;
   2206 }
   2207 
   2208 template <AllowGC allowGC>
   2209 static inline bool GeneralizedGetProperty(
   2210    JSContext* cx, typename MaybeRooted<JSObject*, allowGC>::HandleType obj,
   2211    typename MaybeRooted<jsid, allowGC>::HandleType id,
   2212    typename MaybeRooted<Value, allowGC>::HandleType receiver,
   2213    IsNameLookup nameLookup,
   2214    typename MaybeRooted<Value, allowGC>::MutableHandleType vp) {
   2215  MOZ_ASSERT(obj->getOpsGetProperty());
   2216 
   2217  if constexpr (allowGC == AllowGC::CanGC) {
   2218    AutoCheckRecursionLimit recursion(cx);
   2219    if (!recursion.check(cx)) {
   2220      return false;
   2221    }
   2222    if (nameLookup) {
   2223      // When nameLookup is true, GeneralizedGetProperty implements 9.1.1.2.6
   2224      // GetBindingValue, ES2025 rev ac21460fedf4b926520b06c9820bdbebad596a8b,
   2225      // with step 2 (the call to HasProperty) and step 4 (the call to Get)
   2226      // fused so that only a single lookup is needed.
   2227      //
   2228      // If we get here, we've reached a non-native object. Fall back on the
   2229      // algorithm as specified, with two separate lookups. (Note that we
   2230      // throw ReferenceErrors regardless of strictness, technically a bug.)
   2231 
   2232      bool found;
   2233      if (!HasProperty(cx, obj, id, &found)) {
   2234        return false;
   2235      }
   2236      if (!found) {
   2237        ReportIsNotDefined(cx, id);
   2238        return false;
   2239      }
   2240    }
   2241 
   2242    return GetProperty(cx, obj, receiver, id, vp);
   2243  } else {
   2244    return false;
   2245  }
   2246 }
   2247 
   2248 bool js::GetSparseElementHelper(JSContext* cx, Handle<NativeObject*> obj,
   2249                                int32_t int_id, MutableHandleValue result) {
   2250  MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<PlainObject>());
   2251 
   2252  // This helper doesn't handle the case where the index is a dense element.
   2253  MOZ_ASSERT(int_id >= 0);
   2254  MOZ_ASSERT(!obj->containsDenseElement(int_id));
   2255 
   2256  // Indexed properties can not exist on the prototype chain.
   2257  MOZ_ASSERT(!PrototypeMayHaveIndexedProperties(obj));
   2258 
   2259  MOZ_ASSERT(PropertyKey::fitsInInt(int_id));
   2260  RootedId id(cx, PropertyKey::Int(int_id));
   2261 
   2262  uint32_t index;
   2263  PropMap* map = obj->shape()->lookup(cx, id, &index);
   2264  if (!map) {
   2265    // Property not found, return directly.
   2266    result.setUndefined();
   2267    return true;
   2268  }
   2269 
   2270  PropertyInfo prop = map->getPropertyInfo(index);
   2271  RootedValue receiver(cx, ObjectValue(*obj));
   2272  return GetExistingProperty<CanGC>(cx, receiver, obj, id, prop, result);
   2273 }
   2274 
   2275 /**
   2276 * 10.1.8.1 OrdinaryGet ( O, P, Receiver )
   2277 *
   2278 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b
   2279 */
   2280 template <AllowGC allowGC>
   2281 static MOZ_ALWAYS_INLINE bool NativeGetPropertyInline(
   2282    JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
   2283    typename MaybeRooted<Value, allowGC>::HandleType receiver,
   2284    typename MaybeRooted<jsid, allowGC>::HandleType id, IsNameLookup nameLookup,
   2285    typename MaybeRooted<Value, allowGC>::MutableHandleType vp) {
   2286  typename MaybeRooted<NativeObject*, allowGC>::RootType pobj(cx, obj);
   2287  PropertyResult prop;
   2288 
   2289  // This loop isn't explicit in the spec algorithm. See the comment on step
   2290  // 2.c below.
   2291  for (;;) {
   2292    // Step 1.
   2293    if (!NativeLookupOwnPropertyInline<allowGC>(cx, pobj, id, &prop)) {
   2294      return false;
   2295    }
   2296 
   2297    if (prop.isFound()) {
   2298      // Steps 3-7. Special case for dense elements because
   2299      // GetExistingProperty doesn't support those.
   2300      if (prop.isDenseElement()) {
   2301        vp.set(pobj->getDenseElement(prop.denseElementIndex()));
   2302        return true;
   2303      }
   2304      if (prop.isTypedArrayElement()) {
   2305        size_t idx = prop.typedArrayElementIndex();
   2306        auto* tarr = &pobj->template as<TypedArrayObject>();
   2307        return tarr->template getElement<allowGC>(cx, idx, vp);
   2308      }
   2309 
   2310      return GetExistingProperty<allowGC>(cx, receiver, pobj, id,
   2311                                          prop.propertyInfo(), vp);
   2312    }
   2313 
   2314    // Step 2.a.
   2315    JSObject* proto = pobj->staticPrototype();
   2316 
   2317    // Step 2.b. The spec algorithm simply returns undefined if proto is
   2318    // null, but see the comment on GetNonexistentProperty.
   2319    if (!proto || prop.shouldIgnoreProtoChain()) {
   2320      return GetNonexistentProperty<allowGC>(cx, id, nameLookup, vp);
   2321    }
   2322 
   2323    // Step 2. If the prototype is also native, this step is a recursive tail
   2324    // call, and we don't need to go through all the plumbing of GetProperty;
   2325    // the top of the loop is where we're going to end up anyway. But if |proto|
   2326    // is non-native, that optimization would be incorrect.
   2327    if (proto->getOpsGetProperty()) {
   2328      typename MaybeRooted<JSObject*, allowGC>::RootType protoRoot(cx, proto);
   2329      return GeneralizedGetProperty<allowGC>(cx, protoRoot, id, receiver,
   2330                                             nameLookup, vp);
   2331    }
   2332 
   2333    pobj = &proto->as<NativeObject>();
   2334  }
   2335 }
   2336 
   2337 bool js::NativeGetProperty(JSContext* cx, Handle<NativeObject*> obj,
   2338                           HandleValue receiver, HandleId id,
   2339                           MutableHandleValue vp) {
   2340  return NativeGetPropertyInline<CanGC>(cx, obj, receiver, id, NotNameLookup,
   2341                                        vp);
   2342 }
   2343 
   2344 bool js::NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj,
   2345                               const Value& receiver, jsid id, Value* vp) {
   2346  AutoAssertNoPendingException noexc(cx);
   2347  return NativeGetPropertyInline<NoGC>(cx, obj, receiver, id, NotNameLookup,
   2348                                       vp);
   2349 }
   2350 
   2351 bool js::NativeGetElement(JSContext* cx, Handle<NativeObject*> obj,
   2352                          HandleValue receiver, int32_t index,
   2353                          MutableHandleValue vp) {
   2354  RootedId id(cx);
   2355 
   2356  if (MOZ_LIKELY(index >= 0)) {
   2357    if (!IndexToId(cx, uint32_t(index), &id)) {
   2358      return false;
   2359    }
   2360  } else {
   2361    RootedValue indexVal(cx, Int32Value(index));
   2362    if (!PrimitiveValueToId<CanGC>(cx, indexVal, &id)) {
   2363      return false;
   2364    }
   2365  }
   2366  return NativeGetProperty(cx, obj, receiver, id, vp);
   2367 }
   2368 
   2369 bool js::GetNameBoundInEnvironment(JSContext* cx, HandleObject envArg,
   2370                                   HandleId id, MutableHandleValue vp) {
   2371  // Manually unwrap 'with' environments to prevent looking up @@unscopables
   2372  // twice.
   2373  //
   2374  // This is unfortunate because internally, the engine does not distinguish
   2375  // HasProperty from HasBinding: both are implemented as a HasPropertyOp
   2376  // hook on a WithEnvironmentObject.
   2377  //
   2378  // In the case of attempting to get the value of a binding already looked up
   2379  // via JSOp::BindName or JSOp::BindUnqualifiedName, calling HasProperty on the
   2380  // WithEnvironmentObject is equivalent to calling HasBinding a second time.
   2381  // This results in the incorrect behavior of performing the @@unscopables
   2382  // check again.
   2383  RootedObject env(cx, MaybeUnwrapWithEnvironment(envArg));
   2384  RootedValue receiver(cx, ObjectValue(*env));
   2385  if (env->getOpsGetProperty()) {
   2386    return GeneralizedGetProperty<CanGC>(cx, env, id, receiver, NameLookup, vp);
   2387  }
   2388  return NativeGetPropertyInline<CanGC>(cx, env.as<NativeObject>(), receiver,
   2389                                        id, NameLookup, vp);
   2390 }
   2391 
   2392 /*** [[Set]] ****************************************************************/
   2393 
   2394 static bool SetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
   2395                                  HandleValue v, ObjectOpResult& result) {
   2396  cx->check(obj, id, v);
   2397 
   2398  const JSClass* clasp = obj->getClass();
   2399  if (clasp == &ArrayObject::class_) {
   2400    return ArrayLengthSetter(cx, obj, id, v, result);
   2401  }
   2402  if (clasp == &MappedArgumentsObject::class_) {
   2403    return MappedArgSetter(cx, obj, id, v, result);
   2404  }
   2405  MOZ_RELEASE_ASSERT(clasp == &UnmappedArgumentsObject::class_);
   2406  return UnmappedArgSetter(cx, obj, id, v, result);
   2407 }
   2408 
   2409 static bool MaybeReportUndeclaredVarAssignment(JSContext* cx, HandleId id) {
   2410  {
   2411    jsbytecode* pc;
   2412    JSScript* script =
   2413        cx->currentScript(&pc, JSContext::AllowCrossRealm::Allow);
   2414    if (!script) {
   2415      return true;
   2416    }
   2417 
   2418    if (!IsStrictSetPC(pc)) {
   2419      return true;
   2420    }
   2421  }
   2422 
   2423  UniqueChars bytes =
   2424      IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier);
   2425  if (!bytes) {
   2426    return false;
   2427  }
   2428  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNDECLARED_VAR,
   2429                           bytes.get());
   2430  return false;
   2431 }
   2432 
   2433 /*
   2434 * Finish assignment to a shapeful data property of a native object obj. This
   2435 * conforms to no standard and there is a lot of legacy baggage here.
   2436 */
   2437 static bool NativeSetExistingDataProperty(JSContext* cx,
   2438                                          Handle<NativeObject*> obj,
   2439                                          HandleId id, PropertyInfo prop,
   2440                                          HandleValue v,
   2441                                          ObjectOpResult& result) {
   2442  MOZ_ASSERT(obj->is<NativeObject>());
   2443  MOZ_ASSERT(prop.isDataDescriptor());
   2444 
   2445  Watchtower::watchPropertyValueChange<AllowGC::CanGC>(cx, obj, id, v, prop);
   2446 
   2447  if (prop.isDataProperty()) {
   2448    // The common path. Standard data property.
   2449    obj->setSlot(prop.slot(), v);
   2450    return result.succeed();
   2451  }
   2452 
   2453  MOZ_ASSERT(prop.isCustomDataProperty());
   2454  MOZ_ASSERT(!obj->is<WithEnvironmentObject>());  // See bug 1128681.
   2455 
   2456  return SetCustomDataProperty(cx, obj, id, v, result);
   2457 }
   2458 
   2459 /**
   2460 * 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
   2461 *
   2462 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b
   2463 *
   2464 * When a [[Set]] operation finds no existing property with the given id
   2465 * or finds a writable data property on the prototype chain, we end up here.
   2466 * Finish the [[Set]] by defining a new property on receiver.
   2467 *
   2468 * This implements steps 2.b-e, but it is really old code and there are a few
   2469 * barnacles.
   2470 */
   2471 static bool SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v,
   2472                                  HandleValue receiverValue,
   2473                                  ObjectOpResult& result) {
   2474  // Step 2.b.
   2475  if (!receiverValue.isObject()) {
   2476    return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
   2477  }
   2478  RootedObject receiver(cx, &receiverValue.toObject());
   2479 
   2480  bool existing;
   2481  {
   2482    // Step 2.c.
   2483    Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
   2484    if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc)) {
   2485      return false;
   2486    }
   2487 
   2488    existing = desc.isSome();
   2489 
   2490    // Step 2.d.
   2491    if (existing) {
   2492      // Step 2.d.i.
   2493      if (desc->isAccessorDescriptor()) {
   2494        return result.fail(JSMSG_OVERWRITING_ACCESSOR);
   2495      }
   2496 
   2497      // Step 2.d.ii.
   2498      if (!desc->writable()) {
   2499        return result.fail(JSMSG_READ_ONLY);
   2500      }
   2501    }
   2502  }
   2503 
   2504  // Steps 2.d.iii-iv. and 2.e.i. Define the new data property.
   2505  Rooted<PropertyDescriptor> desc(cx);
   2506  if (existing) {
   2507    desc = PropertyDescriptor::Empty();
   2508    desc.setValue(v);
   2509  } else {
   2510    desc = PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
   2511                                        JS::PropertyAttribute::Enumerable,
   2512                                        JS::PropertyAttribute::Writable});
   2513  }
   2514  return DefineProperty(cx, receiver, id, desc, result);
   2515 }
   2516 
   2517 enum class TypedArrayOutOfRange : bool { No, Yes };
   2518 
   2519 /**
   2520 * 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
   2521 * 10.4.5.5 [[Set]] ( P, V, Receiver )
   2522 *
   2523 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b
   2524 *
   2525 * Implement "the rest of" assignment to a property when no property
   2526 * receiver[id] was found anywhere on the prototype chain.
   2527 */
   2528 template <QualifiedBool IsQualified>
   2529 static bool SetNonexistentProperty(JSContext* cx, Handle<NativeObject*> obj,
   2530                                   Handle<NativeObject*> pobj, HandleId id,
   2531                                   HandleValue v, HandleValue receiver,
   2532                                   TypedArrayOutOfRange typedArrayOutOfRange,
   2533                                   ObjectOpResult& result) {
   2534  if (!IsQualified && receiver.isObject() &&
   2535      receiver.toObject().isUnqualifiedVarObj()) {
   2536    if (!MaybeReportUndeclaredVarAssignment(cx, id)) {
   2537      return false;
   2538    }
   2539  }
   2540 
   2541  // Unqualified access may also need to take this code path, but it's currently
   2542  // hard to tell, because with-environment objects call SetProperty<Qualified>,
   2543  // which seems a bit dubious. And other environment objects probably don't
   2544  // have typed array objects in the middle of their prototype chain, so we
   2545  // can't really test this code path without first fixing with-environments.
   2546  if constexpr (IsQualified) {
   2547    // Indexed properties of typed arrays are special.
   2548    if (typedArrayOutOfRange == TypedArrayOutOfRange::Yes) {
   2549      MOZ_ASSERT(pobj->is<TypedArrayObject>(),
   2550                 "typed array out-of-range reported by non-typed array?");
   2551      MOZ_ASSERT(pobj == obj || !obj->is<TypedArrayObject>(),
   2552                 "prototype chain not traversed for typed array indices");
   2553 
   2554      auto tobj = HandleObject(pobj).as<TypedArrayObject>();
   2555 
   2556      // Additional step from Immutable ArrayBuffer proposal.
   2557      if (tobj->is<ImmutableTypedArrayObject>()) {
   2558        return result.fail(JSMSG_ARRAYBUFFER_IMMUTABLE);
   2559      }
   2560 
   2561      // 10.4.5.6 [[Set]], step 1.b.i.
   2562      if (receiver.isObject() && pobj == &receiver.toObject()) {
   2563        mozilla::Maybe<uint64_t> index = ToTypedArrayIndex(id);
   2564        MOZ_ASSERT(index, "typed array out-of-range reported by non-index?");
   2565 
   2566        return SetTypedArrayElement(cx, tobj, *index, v, result);
   2567      }
   2568 
   2569      // 10.4.5.5, step 1.b.ii.
   2570      return result.succeed();
   2571    }
   2572  }
   2573 
   2574  // Pure optimization for the common case. There's no point performing the
   2575  // lookup in step 2.c again, as our caller just did it for us.
   2576  if (IsQualified && receiver.isObject() && obj == &receiver.toObject()) {
   2577    // Ensure that a custom GetOwnPropertyOp, if present, doesn't
   2578    // introduce additional properties which weren't previously found by
   2579    // LookupOwnProperty.
   2580 #ifdef DEBUG
   2581    if (GetOwnPropertyOp op = obj->getOpsGetOwnPropertyDescriptor()) {
   2582      Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
   2583      if (!op(cx, obj, id, &desc)) {
   2584        return false;
   2585      }
   2586      MOZ_ASSERT(desc.isNothing());
   2587    }
   2588 #endif
   2589 
   2590    // Step 2.e. Define the new data property.
   2591    if (DefinePropertyOp op = obj->getOpsDefineProperty()) {
   2592      Rooted<PropertyDescriptor> desc(
   2593          cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
   2594                                           JS::PropertyAttribute::Enumerable,
   2595                                           JS::PropertyAttribute::Writable}));
   2596      return op(cx, obj, id, desc, result);
   2597    }
   2598 
   2599    return DefineNonexistentProperty(cx, obj, id, v, result);
   2600  }
   2601 
   2602  return SetPropertyByDefining(cx, id, v, receiver, result);
   2603 }
   2604 
   2605 // Set an existing own property obj[index] that's a dense element.
   2606 static bool SetDenseElement(JSContext* cx, Handle<NativeObject*> obj,
   2607                            uint32_t index, HandleValue v,
   2608                            ObjectOpResult& result) {
   2609  MOZ_ASSERT(!obj->is<TypedArrayObject>());
   2610  MOZ_ASSERT(obj->containsDenseElement(index));
   2611 
   2612  obj->setDenseElement(index, v);
   2613  return result.succeed();
   2614 }
   2615 
   2616 /**
   2617 * 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
   2618 *
   2619 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b
   2620 *
   2621 * Finish the assignment `receiver[id] = v` when an existing property |prop|
   2622 * has been found on a native object |pobj|.
   2623 *
   2624 * It is necessary to pass both id and prop because prop could be an implicit
   2625 * dense or typed array element (i.e. not actually a pointer to a Shape).
   2626 */
   2627 static bool SetExistingProperty(JSContext* cx, HandleId id, HandleValue v,
   2628                                HandleValue receiver,
   2629                                Handle<NativeObject*> pobj,
   2630                                const PropertyResult& prop,
   2631                                ObjectOpResult& result) {
   2632  // Step 1. (Performed in caller)
   2633 
   2634  // Step 2 for dense elements.
   2635  if (prop.isDenseElement()) {
   2636    // Step 2.a.
   2637    if (pobj->denseElementsAreFrozen()) {
   2638      return result.fail(JSMSG_READ_ONLY);
   2639    }
   2640 
   2641    // Pure optimization for the common case:
   2642    if (receiver.isObject() && pobj == &receiver.toObject()) {
   2643      return SetDenseElement(cx, pobj, prop.denseElementIndex(), v, result);
   2644    }
   2645 
   2646    // Steps 2.b-e.
   2647    return SetPropertyByDefining(cx, id, v, receiver, result);
   2648  }
   2649 
   2650  // 10.4.5.6 [[Set]], step 1.b and 10.1.9.2, step 2 for typed array elements.
   2651  if (prop.isTypedArrayElement()) {
   2652    auto tobj = HandleObject(pobj).as<TypedArrayObject>();
   2653 
   2654    // Step 2.a.
   2655    //
   2656    // Typed arrays don't have dense elements.
   2657    MOZ_ASSERT(!tobj->denseElementsAreFrozen());
   2658 
   2659    // Additional step from Immutable ArrayBuffer proposal.
   2660    if (tobj->is<ImmutableTypedArrayObject>()) {
   2661      return result.fail(JSMSG_ARRAYBUFFER_IMMUTABLE);
   2662    }
   2663 
   2664    // 10.4.5.6 [[Set]], step 1.b.i.
   2665    if (receiver.isObject() && pobj == &receiver.toObject()) {
   2666      size_t idx = prop.typedArrayElementIndex();
   2667      return SetTypedArrayElement(cx, tobj, idx, v, result);
   2668    }
   2669 
   2670    // 10.4.5.6 [[Set]], step 1.b.ii.
   2671    //
   2672    // Implemented in SetNonexistentProperty.
   2673 
   2674    // 10.4.5.6 [[Set]], step 2.
   2675    // 10.1.9.2, steps 2.b-e.
   2676    return SetPropertyByDefining(cx, id, v, receiver, result);
   2677  }
   2678 
   2679  // Step 2 for all other properties.
   2680  PropertyInfo propInfo = prop.propertyInfo();
   2681  if (propInfo.isDataDescriptor()) {
   2682    // Step 2.a.
   2683    if (!propInfo.writable()) {
   2684      return result.fail(JSMSG_READ_ONLY);
   2685    }
   2686 
   2687    // steps 2.c-d.
   2688    if (receiver.isObject() && pobj == &receiver.toObject()) {
   2689      // Pure optimization for the common case. There's no point performing
   2690      // the lookup in step 2.c again, as our caller just did it for us. The
   2691      // result is |shapeProp|.
   2692 
   2693      // Steps 2.d.iii-iv.
   2694      return NativeSetExistingDataProperty(cx, pobj, id, propInfo, v, result);
   2695    }
   2696 
   2697    // Shadow pobj[id] by defining a new data property receiver[id].
   2698    // Delegate everything to SetPropertyByDefining.
   2699    return SetPropertyByDefining(cx, id, v, receiver, result);
   2700  }
   2701 
   2702  // Step 3.
   2703  MOZ_ASSERT(propInfo.isAccessorProperty());
   2704 
   2705  // Step 4.
   2706  JSObject* setterObject = pobj->getSetter(propInfo);
   2707 
   2708  // Step 5.
   2709  if (!setterObject) {
   2710    return result.fail(JSMSG_GETTER_ONLY);
   2711  }
   2712 
   2713  // Step 6.
   2714  RootedValue setter(cx, ObjectValue(*setterObject));
   2715  if (!js::CallSetter(cx, receiver, setter, v)) {
   2716    return false;
   2717  }
   2718 
   2719  // Step 7.
   2720  return result.succeed();
   2721 }
   2722 
   2723 /**
   2724 * 10.1.9 [[Set]] ( P, V, Receiver )
   2725 * 10.1.9.1 OrdinarySet ( O, P, V, Receiver )
   2726 * 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
   2727 *
   2728 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b
   2729 */
   2730 template <QualifiedBool IsQualified>
   2731 bool js::NativeSetProperty(JSContext* cx, Handle<NativeObject*> obj,
   2732                           HandleId id, HandleValue v, HandleValue receiver,
   2733                           ObjectOpResult& result) {
   2734  // We substitute our own names for these names used in the spec:
   2735  // O -> pobj, P -> id, ownDesc -> prop.
   2736  PropertyResult prop;
   2737  Rooted<NativeObject*> pobj(cx, obj);
   2738 
   2739  // This loop isn't explicit in the spec algorithm. See the comment on step
   2740  // 1.b.i below. (There's a very similar loop in the NativeGetProperty
   2741  // implementation, but unfortunately not similar enough to common up.)
   2742  for (;;) {
   2743    // OrdinarySet, step 1.
   2744    if (!NativeLookupOwnPropertyInline<CanGC>(cx, pobj, id, &prop)) {
   2745      return false;
   2746    }
   2747 
   2748    // OrdinarySetWithOwnDescriptor, steps 2-3.
   2749    if (prop.isFound()) {
   2750      return SetExistingProperty(cx, id, v, receiver, pobj, prop, result);
   2751    }
   2752 
   2753    // OrdinarySetWithOwnDescriptor, steps 1.a-b.
   2754    // As a side-effect of NativeLookupOwnPropertyInline, we may determine that
   2755    // a property is not found and the proto chain should not be searched. This
   2756    // can occur for:
   2757    //  - Out-of-range numeric properties of a TypedArrayObject
   2758    //  - Recursive resolve hooks (which is expected when they try to set the
   2759    //    property being resolved).
   2760    JSObject* proto = pobj->staticPrototype();
   2761    if (!proto || prop.shouldIgnoreProtoChain()) {
   2762      // OrdinarySetWithOwnDescriptor, step 1.c.i (and step 2).
   2763      return SetNonexistentProperty<IsQualified>(
   2764          cx, obj, pobj, id, v, receiver,
   2765          TypedArrayOutOfRange{prop.isTypedArrayOutOfRange()}, result);
   2766    }
   2767 
   2768    // Step 1.b.i. If the prototype is also native, this step is a recursive
   2769    // tail call, and we don't need to go through all the plumbing of
   2770    // SetProperty; the top of the loop is where we're going to end up anyway.
   2771    // But if |proto| is non-native, that optimization would be incorrect.
   2772    if (!proto->is<NativeObject>()) {
   2773      // Unqualified assignments are not specified to go through [[Set]]
   2774      // at all, but they do go through this function. So check for
   2775      // unqualified assignment to a nonexistent global (a strict error).
   2776      RootedObject protoRoot(cx, proto);
   2777      if (!IsQualified) {
   2778        bool found;
   2779        if (!HasProperty(cx, protoRoot, id, &found)) {
   2780          return false;
   2781        }
   2782        if (!found) {
   2783          return SetNonexistentProperty<IsQualified>(
   2784              cx, obj, pobj, id, v, receiver, TypedArrayOutOfRange::No, result);
   2785        }
   2786      }
   2787 
   2788      return SetProperty(cx, protoRoot, id, v, receiver, result);
   2789    }
   2790    pobj = &proto->as<NativeObject>();
   2791  }
   2792 }
   2793 
   2794 template bool js::NativeSetProperty<Qualified>(JSContext* cx,
   2795                                               Handle<NativeObject*> obj,
   2796                                               HandleId id, HandleValue value,
   2797                                               HandleValue receiver,
   2798                                               ObjectOpResult& result);
   2799 
   2800 template bool js::NativeSetProperty<Unqualified>(JSContext* cx,
   2801                                                 Handle<NativeObject*> obj,
   2802                                                 HandleId id, HandleValue value,
   2803                                                 HandleValue receiver,
   2804                                                 ObjectOpResult& result);
   2805 
   2806 bool js::NativeSetElement(JSContext* cx, Handle<NativeObject*> obj,
   2807                          uint32_t index, HandleValue v, HandleValue receiver,
   2808                          ObjectOpResult& result) {
   2809  RootedId id(cx);
   2810  if (!IndexToId(cx, index, &id)) {
   2811    return false;
   2812  }
   2813  return NativeSetProperty<Qualified>(cx, obj, id, v, receiver, result);
   2814 }
   2815 
   2816 /*** [[Delete]] *************************************************************/
   2817 
   2818 static bool CallJSDeletePropertyOp(JSContext* cx, JSDeletePropertyOp op,
   2819                                   HandleObject receiver, HandleId id,
   2820                                   ObjectOpResult& result) {
   2821  AutoCheckRecursionLimit recursion(cx);
   2822  if (!recursion.check(cx)) {
   2823    return false;
   2824  }
   2825 
   2826  cx->check(receiver, id);
   2827  if (op) {
   2828    return op(cx, receiver, id, result);
   2829  }
   2830  return result.succeed();
   2831 }
   2832 
   2833 /**
   2834 * 10.1.10.1 OrdinaryDelete ( O, P )
   2835 *
   2836 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b
   2837 */
   2838 bool js::NativeDeleteProperty(JSContext* cx, Handle<NativeObject*> obj,
   2839                              HandleId id, ObjectOpResult& result) {
   2840  // Step 1.
   2841  PropertyResult prop;
   2842  if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
   2843    return false;
   2844  }
   2845 
   2846  // Step 2.
   2847  if (prop.isNotFound()) {
   2848    // If no property call the class's delProperty hook, passing succeeded
   2849    // as the result parameter. This always succeeds when there is no hook.
   2850    return CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj,
   2851                                  id, result);
   2852  }
   2853 
   2854  // Step 4. (Reordered) Non-configurable property.
   2855  if (!GetPropertyAttributes(obj, prop).configurable()) {
   2856    return result.failCantDelete();
   2857  }
   2858 
   2859  // Typed array elements are configurable, but can't be deleted.
   2860  if (prop.isTypedArrayElement()) {
   2861    return result.failCantDelete();
   2862  }
   2863 
   2864  if (!CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id,
   2865                              result)) {
   2866    return false;
   2867  }
   2868  if (!result) {
   2869    return true;
   2870  }
   2871 
   2872  // Step 3.
   2873  if (prop.isDenseElement()) {
   2874    obj->setDenseElementHole(prop.denseElementIndex());
   2875  } else {
   2876    if (!NativeObject::removeProperty(cx, obj, id)) {
   2877      return false;
   2878    }
   2879  }
   2880 
   2881  return SuppressDeletedProperty(cx, obj, id);
   2882 }
   2883 
   2884 bool js::CopyDataPropertiesNative(JSContext* cx, Handle<PlainObject*> target,
   2885                                  Handle<NativeObject*> from,
   2886                                  Handle<PlainObject*> excludedItems,
   2887                                  bool* optimized) {
   2888  *optimized = false;
   2889 
   2890  // Don't use the fast path if |from| may have extra indexed or lazy
   2891  // properties.
   2892  if (from->getDenseInitializedLength() > 0 || from->isIndexed() ||
   2893      from->is<TypedArrayObject>() || from->getClass()->getNewEnumerate() ||
   2894      from->getClass()->getEnumerate()) {
   2895    return true;
   2896  }
   2897 
   2898  // Collect all enumerable data properties.
   2899  Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx));
   2900 
   2901  Rooted<NativeShape*> fromShape(cx, from->shape());
   2902  for (ShapePropertyIter<NoGC> iter(fromShape); !iter.done(); iter++) {
   2903    jsid id = iter->key();
   2904    MOZ_ASSERT(!id.isInt());
   2905 
   2906    if (!iter->enumerable()) {
   2907      continue;
   2908    }
   2909    if (excludedItems && excludedItems->contains(cx, id)) {
   2910      continue;
   2911    }
   2912 
   2913    // Don't use the fast path if |from| contains non-data properties.
   2914    //
   2915    // This enables two optimizations:
   2916    // 1. We don't need to handle the case when accessors modify |from|.
   2917    // 2. String and symbol properties can be added in one go.
   2918    if (!iter->isDataProperty()) {
   2919      return true;
   2920    }
   2921 
   2922    if (!props.append(*iter)) {
   2923      return false;
   2924    }
   2925  }
   2926 
   2927  *optimized = true;
   2928 
   2929  // If |target| contains no own properties, we can directly call
   2930  // AddDataPropertyNonPrototype.
   2931  const bool targetHadNoOwnProperties = target->empty();
   2932 
   2933  RootedId key(cx);
   2934  RootedValue value(cx);
   2935  for (size_t i = props.length(); i > 0; i--) {
   2936    PropertyInfoWithKey prop = props[i - 1];
   2937    MOZ_ASSERT(prop.isDataProperty());
   2938    MOZ_ASSERT(prop.enumerable());
   2939 
   2940    key = prop.key();
   2941    MOZ_ASSERT(!key.isInt());
   2942 
   2943    MOZ_ASSERT(from->is<NativeObject>());
   2944    MOZ_ASSERT(from->shape() == fromShape);
   2945 
   2946    value = from->getSlot(prop.slot());
   2947    if (targetHadNoOwnProperties) {
   2948      MOZ_ASSERT(!target->containsPure(key),
   2949                 "didn't expect to find an existing property");
   2950 
   2951      if (!AddDataPropertyToPlainObject(cx, target, key, value)) {
   2952        return false;
   2953      }
   2954    } else {
   2955      if (!NativeDefineDataProperty(cx, target, key, value, JSPROP_ENUMERATE)) {
   2956        return false;
   2957      }
   2958    }
   2959  }
   2960 
   2961  return true;
   2962 }