tor-browser

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

Shape.cpp (56405B)


      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/Shape-inl.h"
      8 
      9 #include "gc/HashUtil.h"
     10 #include "js/friend/WindowProxy.h"  // js::IsWindow
     11 #include "js/HashTable.h"
     12 #include "js/Printer.h"  // js::GenericPrinter, js::Fprinter
     13 #include "js/UniquePtr.h"
     14 #include "vm/JSObject.h"
     15 #include "vm/JSONPrinter.h"  // js::JSONPrinter
     16 #include "vm/ShapeZone.h"
     17 #include "vm/Watchtower.h"
     18 
     19 #include "gc/StableCellHasher-inl.h"
     20 #include "vm/JSContext-inl.h"
     21 #include "vm/JSObject-inl.h"
     22 #include "vm/NativeObject-inl.h"
     23 
     24 using namespace js;
     25 
     26 /* static */
     27 bool Shape::replaceShape(JSContext* cx, HandleObject obj,
     28                         ObjectFlags objectFlags, TaggedProto proto,
     29                         uint32_t nfixed) {
     30  Shape* newShape;
     31  switch (obj->shape()->kind()) {
     32    case Kind::Shared: {
     33      Handle<NativeObject*> nobj = obj.as<NativeObject>();
     34      if (nobj->shape()->propMap()) {
     35        Rooted<BaseShape*> base(cx, obj->shape()->base());
     36        if (proto != base->proto()) {
     37          Rooted<TaggedProto> protoRoot(cx, proto);
     38          base = BaseShape::get(cx, base->clasp(), base->realm(), protoRoot);
     39          if (!base) {
     40            return false;
     41          }
     42        }
     43        Rooted<SharedPropMap*> map(cx, nobj->sharedShape()->propMap());
     44        uint32_t mapLength = nobj->shape()->propMapLength();
     45        newShape = SharedShape::getPropMapShape(cx, base, nfixed, map,
     46                                                mapLength, objectFlags);
     47      } else {
     48        newShape = SharedShape::getInitialShape(
     49            cx, obj->shape()->getObjectClass(), obj->shape()->realm(), proto,
     50            nfixed, objectFlags);
     51      }
     52      break;
     53    }
     54    case Kind::Dictionary: {
     55      Handle<NativeObject*> nobj = obj.as<NativeObject>();
     56 
     57      Rooted<BaseShape*> base(cx, nobj->shape()->base());
     58      if (proto != base->proto()) {
     59        Rooted<TaggedProto> protoRoot(cx, proto);
     60        base = BaseShape::get(cx, nobj->getClass(), nobj->realm(), protoRoot);
     61        if (!base) {
     62          return false;
     63        }
     64      }
     65 
     66      Rooted<DictionaryPropMap*> map(cx, nobj->dictionaryShape()->propMap());
     67      uint32_t mapLength = nobj->shape()->propMapLength();
     68      newShape =
     69          DictionaryShape::new_(cx, base, objectFlags, nfixed, map, mapLength);
     70      break;
     71    }
     72    case Kind::Proxy:
     73      MOZ_ASSERT(nfixed == 0);
     74      newShape =
     75          ProxyShape::getShape(cx, obj->shape()->getObjectClass(),
     76                               obj->shape()->realm(), proto, objectFlags);
     77      break;
     78    case Kind::WasmGC:
     79      MOZ_ASSERT(nfixed == 0);
     80      const wasm::RecGroup* recGroup = obj->shape()->asWasmGC().recGroup();
     81      newShape = WasmGCShape::getShape(cx, obj->shape()->getObjectClass(),
     82                                       obj->shape()->realm(), proto, recGroup,
     83                                       objectFlags);
     84      break;
     85  }
     86  if (!newShape) {
     87    return false;
     88  }
     89 
     90  obj->setShape(newShape);
     91  return true;
     92 }
     93 
     94 /* static */
     95 bool js::NativeObject::toDictionaryMode(JSContext* cx,
     96                                        Handle<NativeObject*> obj) {
     97  MOZ_ASSERT(!obj->inDictionaryMode());
     98  MOZ_ASSERT(cx->isInsideCurrentCompartment(obj));
     99 
    100  RootedTuple<NativeShape*, SharedPropMap*, DictionaryPropMap*, BaseShape*>
    101      roots(cx);
    102 
    103  RootedField<NativeShape*> shape(roots, obj->shape());
    104  uint32_t span = obj->slotSpan();
    105 
    106  uint32_t mapLength = shape->propMapLength();
    107 
    108  // Clone the shared property map to an unshared dictionary map.
    109  RootedField<DictionaryPropMap*> dictMap(roots);
    110 
    111  if (mapLength > 0) {
    112    RootedField<SharedPropMap*> map(roots, shape->propMap()->asShared());
    113    dictMap = SharedPropMap::toDictionaryMap(cx, map, mapLength);
    114  } else {
    115    // Create an empty dictMap
    116    dictMap = DictionaryPropMap::createEmpty(cx);
    117  }
    118 
    119  if (!dictMap) {
    120    return false;
    121  }
    122 
    123  // Allocate and use a new dictionary shape.
    124  RootedField<BaseShape*> base(roots, shape->base());
    125  shape = DictionaryShape::new_(cx, base, shape->objectFlags(),
    126                                shape->numFixedSlots(), dictMap, mapLength);
    127  if (!shape) {
    128    return false;
    129  }
    130 
    131  obj->setShape(shape);
    132 
    133  MOZ_ASSERT(obj->inDictionaryMode());
    134  obj->setDictionaryModeSlotSpan(span);
    135 
    136  return true;
    137 }
    138 
    139 bool JSObject::reshapeForTeleporting(JSContext* cx, JS::HandleObject obj) {
    140  MOZ_ASSERT(obj->isUsedAsPrototype());
    141  MOZ_ASSERT(obj->hasStaticPrototype(),
    142             "teleporting as a concept is only applicable to static "
    143             "(not dynamically-computed) prototypes");
    144  MOZ_ASSERT(obj->is<NativeObject>());
    145 
    146  Handle<NativeObject*> nobj = obj.as<NativeObject>();
    147  if (!nobj->inDictionaryMode()) {
    148    return NativeObject::toDictionaryMode(cx, nobj);
    149  }
    150 
    151  return NativeObject::generateNewDictionaryShape(cx, nobj);
    152 }
    153 
    154 namespace js {
    155 
    156 class MOZ_RAII AutoCheckShapeConsistency {
    157 #ifdef DEBUG
    158  Handle<NativeObject*> obj_;
    159 #endif
    160 
    161 public:
    162  explicit AutoCheckShapeConsistency(Handle<NativeObject*> obj)
    163 #ifdef DEBUG
    164      : obj_(obj)
    165 #endif
    166  {
    167  }
    168 
    169 #ifdef DEBUG
    170  ~AutoCheckShapeConsistency() { obj_->checkShapeConsistency(); }
    171 #endif
    172 };
    173 
    174 }  // namespace js
    175 
    176 /* static */ MOZ_ALWAYS_INLINE bool
    177 NativeObject::maybeConvertToDictionaryForAdd(JSContext* cx,
    178                                             Handle<NativeObject*> obj) {
    179  if (obj->inDictionaryMode()) {
    180    return true;
    181  }
    182  SharedPropMap* map = obj->sharedShape()->propMap();
    183  if (!map) {
    184    return true;
    185  }
    186  if (MOZ_LIKELY(!map->shouldConvertToDictionaryForAdd())) {
    187    return true;
    188  }
    189  return toDictionaryMode(cx, obj);
    190 }
    191 
    192 static void AssertValidCustomDataProp(NativeObject* obj, PropertyFlags flags) {
    193  // We only support custom data properties on ArrayObject and ArgumentsObject.
    194  // The mechanism is deprecated so we don't want to add new uses.
    195  MOZ_ASSERT(flags.isCustomDataProperty());
    196  MOZ_ASSERT(!flags.isAccessorProperty());
    197  MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<ArgumentsObject>());
    198 }
    199 
    200 /* static */
    201 bool NativeObject::addCustomDataProperty(JSContext* cx,
    202                                         Handle<NativeObject*> obj, HandleId id,
    203                                         PropertyFlags flags) {
    204  MOZ_ASSERT(!id.isVoid());
    205  MOZ_ASSERT(!id.isPrivateName());
    206  MOZ_ASSERT(!obj->containsPure(id));
    207 
    208  AutoCheckShapeConsistency check(obj);
    209  AssertValidCustomDataProp(obj, flags);
    210 
    211  if (!Watchtower::watchPropertyAdd(cx, obj, id)) {
    212    return false;
    213  }
    214 
    215  if (!maybeConvertToDictionaryForAdd(cx, obj)) {
    216    return false;
    217  }
    218 
    219  ObjectFlags objectFlags = obj->shape()->objectFlags();
    220  const JSClass* clasp = obj->shape()->getObjectClass();
    221 
    222  if (obj->inDictionaryMode()) {
    223    // First generate a new dictionary shape so that the map can be mutated
    224    // without having to worry about OOM conditions.
    225    if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
    226      return false;
    227    }
    228 
    229    Rooted<DictionaryPropMap*> map(cx, obj->dictionaryShape()->propMap());
    230    uint32_t mapLength = obj->shape()->propMapLength();
    231    if (!DictionaryPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags,
    232                                        SHAPE_INVALID_SLOT, &objectFlags)) {
    233      return false;
    234    }
    235 
    236    obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength);
    237    return true;
    238  }
    239 
    240  Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap());
    241  uint32_t mapLength = obj->shape()->propMapLength();
    242  if (!SharedPropMap::addCustomDataProperty(cx, clasp, &map, &mapLength, id,
    243                                            flags, &objectFlags)) {
    244    return false;
    245  }
    246 
    247  Shape* shape = SharedShape::getPropMapShape(cx, obj->shape()->base(),
    248                                              obj->shape()->numFixedSlots(),
    249                                              map, mapLength, objectFlags);
    250  if (!shape) {
    251    return false;
    252  }
    253 
    254  obj->setShape(shape);
    255  return true;
    256 }
    257 
    258 static ShapeSetForAdd* MakeShapeSetForAdd(SharedShape* shape1,
    259                                          SharedShape* shape2) {
    260  MOZ_ASSERT(shape1 != shape2);
    261  MOZ_ASSERT(shape1->propMapLength() == shape2->propMapLength());
    262 
    263  auto hash = MakeUnique<ShapeSetForAdd>();
    264  if (!hash || !hash->reserve(2)) {
    265    return nullptr;
    266  }
    267 
    268  PropertyInfoWithKey prop = shape1->lastProperty();
    269  hash->putNewInfallible(ShapeForAddHasher::Lookup(prop.key(), prop.flags()),
    270                         shape1);
    271 
    272  prop = shape2->lastProperty();
    273  hash->putNewInfallible(ShapeForAddHasher::Lookup(prop.key(), prop.flags()),
    274                         shape2);
    275 
    276  return hash.release();
    277 }
    278 
    279 static MOZ_ALWAYS_INLINE SharedShape* LookupShapeForAdd(Shape* shape,
    280                                                        PropertyKey key,
    281                                                        PropertyFlags flags,
    282                                                        uint32_t* slot) {
    283  ShapeCachePtr cache = shape->cache();
    284 
    285  if (cache.isSingleShapeForAdd()) {
    286    SharedShape* newShape = cache.toSingleShapeForAdd();
    287    if (newShape->lastPropertyMatchesForAdd(key, flags, slot)) {
    288      return newShape;
    289    }
    290    return nullptr;
    291  }
    292 
    293  if (cache.isShapeSetForAdd()) {
    294    ShapeSetForAdd* set = cache.toShapeSetForAdd();
    295    ShapeForAddHasher::Lookup lookup(key, flags);
    296    if (auto p = set->lookup(lookup)) {
    297      SharedShape* newShape = *p;
    298      *slot = newShape->lastProperty().slot();
    299      return newShape;
    300    }
    301    return nullptr;
    302  }
    303 
    304  MOZ_ASSERT(!cache.isForAdd());
    305  return nullptr;
    306 }
    307 
    308 // Add shapes with a non-None ShapeCachePtr to the shapesWithCache list so that
    309 // these caches can be discarded on GC.
    310 static bool RegisterShapeCache(JSContext* cx, Shape* shape) {
    311  ShapeCachePtr cache = shape->cache();
    312  if (!cache.isNone()) {
    313    // Already registered this shape.
    314    return true;
    315  }
    316  return cx->zone()->shapeZone().shapesWithCache.append(shape);
    317 }
    318 
    319 /* static */
    320 bool NativeObject::addProperty(JSContext* cx, Handle<NativeObject*> obj,
    321                               HandleId id, PropertyFlags flags,
    322                               uint32_t* slot) {
    323  AutoCheckShapeConsistency check(obj);
    324  MOZ_ASSERT(!flags.isCustomDataProperty(),
    325             "Use addCustomDataProperty for custom data properties");
    326 
    327  // The object must not contain a property named |id|. The object must be
    328  // extensible, but allow private fields and sparsifying dense elements.
    329  MOZ_ASSERT(!id.isVoid());
    330  MOZ_ASSERT(!obj->containsPure(id));
    331  MOZ_ASSERT_IF(!id.isPrivateName(),
    332                obj->isExtensible() ||
    333                    (id.isInt() && obj->containsDenseElement(id.toInt())));
    334  if (!Watchtower::watchPropertyAdd(cx, obj, id)) {
    335    return false;
    336  }
    337 
    338  if (!maybeConvertToDictionaryForAdd(cx, obj)) {
    339    return false;
    340  }
    341 
    342  if (auto* shape = LookupShapeForAdd(obj->shape(), id, flags, slot)) {
    343    return obj->setShapeAndAddNewSlot(cx, shape, *slot);
    344  }
    345 
    346  if (obj->inDictionaryMode()) {
    347    // First generate a new dictionary shape so that the map and shape can be
    348    // mutated without having to worry about OOM conditions.
    349    if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
    350      return false;
    351    }
    352    if (!allocDictionarySlot(cx, obj, slot)) {
    353      return false;
    354    }
    355 
    356    ObjectFlags objectFlags = obj->shape()->objectFlags();
    357    const JSClass* clasp = obj->shape()->getObjectClass();
    358 
    359    Rooted<DictionaryPropMap*> map(cx, obj->shape()->propMap()->asDictionary());
    360    uint32_t mapLength = obj->shape()->propMapLength();
    361    if (!DictionaryPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags,
    362                                        *slot, &objectFlags)) {
    363      return false;
    364    }
    365 
    366    obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength);
    367    return true;
    368  }
    369 
    370  ObjectFlags objectFlags = obj->shape()->objectFlags();
    371  const JSClass* clasp = obj->shape()->getObjectClass();
    372 
    373  Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap());
    374  uint32_t mapLength = obj->shape()->propMapLength();
    375 
    376  if (!SharedPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags,
    377                                  &objectFlags, slot)) {
    378    return false;
    379  }
    380 
    381  bool allocatedNewShape;
    382  SharedShape* newShape = SharedShape::getPropMapShape(
    383      cx, obj->shape()->base(), obj->shape()->numFixedSlots(), map, mapLength,
    384      objectFlags, &allocatedNewShape);
    385  if (!newShape) {
    386    return false;
    387  }
    388 
    389  Shape* oldShape = obj->shape();
    390  if (!obj->setShapeAndAddNewSlot(cx, newShape, *slot)) {
    391    return false;
    392  }
    393 
    394  // Add the new shape to the old shape's shape cache, to optimize this shape
    395  // transition. Don't do this if we just allocated a new shape, because that
    396  // suggests this may not be a hot transition that would benefit from the
    397  // cache.
    398 
    399  if (allocatedNewShape) {
    400    return true;
    401  }
    402 
    403  if (!RegisterShapeCache(cx, oldShape)) {
    404    // Ignore OOM, the cache is just an optimization.
    405    return true;
    406  }
    407 
    408  ShapeCachePtr& cache = oldShape->cacheRef();
    409  if (!cache.isForAdd()) {
    410    cache.setSingleShapeForAdd(newShape);
    411  } else if (cache.isSingleShapeForAdd()) {
    412    SharedShape* prevShape = cache.toSingleShapeForAdd();
    413    if (ShapeSetForAdd* set = MakeShapeSetForAdd(prevShape, newShape)) {
    414      cache.setShapeSetForAdd(set);
    415      AddCellMemory(oldShape, sizeof(ShapeSetForAdd),
    416                    MemoryUse::ShapeSetForAdd);
    417    }
    418  } else {
    419    ShapeForAddHasher::Lookup lookup(id, flags);
    420    (void)cache.toShapeSetForAdd()->putNew(lookup, newShape);
    421  }
    422 
    423  return true;
    424 }
    425 
    426 void Shape::maybeCacheIterator(JSContext* cx, PropertyIteratorObject* iter) {
    427  if (!cache().isNone() && !cache().isIterator()) {
    428    // If we're already caching other shape data, skip caching the iterator.
    429    return;
    430  }
    431  if (MOZ_UNLIKELY(!RegisterShapeCache(cx, this))) {
    432    // Ignore OOM. The cache is just an optimization.
    433    return;
    434  }
    435  cacheRef().setIterator(iter);
    436 }
    437 
    438 /* static */
    439 bool NativeObject::addPropertyInReservedSlot(JSContext* cx,
    440                                             Handle<NativeObject*> obj,
    441                                             HandleId id, uint32_t slot,
    442                                             PropertyFlags flags) {
    443  AutoCheckShapeConsistency check(obj);
    444  MOZ_ASSERT(!flags.isCustomDataProperty(),
    445             "Use addCustomDataProperty for custom data properties");
    446 
    447  // The slot must be a reserved slot.
    448  MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(obj->getClass()));
    449 
    450  // The object must not contain a property named |id| and must be extensible.
    451  MOZ_ASSERT(!id.isVoid());
    452  MOZ_ASSERT(!obj->containsPure(id));
    453  MOZ_ASSERT(!id.isPrivateName());
    454  MOZ_ASSERT(obj->isExtensible());
    455 
    456  // The object must not be in dictionary mode. This simplifies the code below.
    457  MOZ_ASSERT(!obj->inDictionaryMode());
    458 
    459  // We don't need to call Watchtower::watchPropertyAdd here because this isn't
    460  // used for any watched objects.
    461  MOZ_ASSERT(!Watchtower::watchesPropertyAdd(obj));
    462 
    463  ObjectFlags objectFlags = obj->shape()->objectFlags();
    464  const JSClass* clasp = obj->shape()->getObjectClass();
    465 
    466  Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap());
    467  uint32_t mapLength = obj->shape()->propMapLength();
    468  if (!SharedPropMap::addPropertyInReservedSlot(cx, clasp, &map, &mapLength, id,
    469                                                flags, slot, &objectFlags)) {
    470    return false;
    471  }
    472 
    473  Shape* shape = SharedShape::getPropMapShape(cx, obj->shape()->base(),
    474                                              obj->shape()->numFixedSlots(),
    475                                              map, mapLength, objectFlags);
    476  if (!shape) {
    477    return false;
    478  }
    479  obj->setShape(shape);
    480 
    481  MOZ_ASSERT(obj->getLastProperty().slot() == slot);
    482  return true;
    483 }
    484 
    485 /*
    486 * Assert some invariants that should hold when changing properties. It's the
    487 * responsibility of the callers to ensure these hold.
    488 */
    489 static void AssertCanChangeFlags(PropertyInfo prop, PropertyFlags flags) {
    490 #ifdef DEBUG
    491  if (prop.configurable()) {
    492    return;
    493  }
    494 
    495  // A non-configurable property must stay non-configurable.
    496  MOZ_ASSERT(!flags.configurable());
    497 
    498  // Reject attempts to turn a non-configurable data property into an accessor
    499  // or custom data property.
    500  MOZ_ASSERT_IF(prop.isDataProperty(), flags.isDataProperty());
    501 
    502  // Reject attempts to turn a non-configurable accessor property into a data
    503  // property or custom data property.
    504  MOZ_ASSERT_IF(prop.isAccessorProperty(), flags.isAccessorProperty());
    505 #endif
    506 }
    507 
    508 static void AssertValidArrayIndex(NativeObject* obj, jsid id) {
    509 #ifdef DEBUG
    510  if (obj->is<ArrayObject>()) {
    511    ArrayObject* arr = &obj->as<ArrayObject>();
    512    uint32_t index;
    513    if (IdIsIndex(id, &index)) {
    514      MOZ_ASSERT(index < arr->length() || arr->lengthIsWritable());
    515    }
    516  }
    517 #endif
    518 }
    519 
    520 /* static */
    521 bool NativeObject::changeProperty(JSContext* cx, Handle<NativeObject*> obj,
    522                                  HandleId id, PropertyFlags flags,
    523                                  uint32_t* slotOut) {
    524  MOZ_ASSERT(!id.isVoid());
    525 
    526  AutoCheckShapeConsistency check(obj);
    527  AssertValidArrayIndex(obj, id);
    528  MOZ_ASSERT(!flags.isCustomDataProperty(),
    529             "Use changeCustomDataPropAttributes for custom data properties");
    530 
    531  Rooted<PropMap*> map(cx, obj->shape()->propMap());
    532  uint32_t mapLength = obj->shape()->propMapLength();
    533 
    534  uint32_t propIndex;
    535  Rooted<PropMap*> propMap(cx, map->lookup(cx, mapLength, id, &propIndex));
    536  MOZ_ASSERT(propMap);
    537 
    538  ObjectFlags objectFlags = obj->shape()->objectFlags();
    539 
    540  PropertyInfo oldProp = propMap->getPropertyInfo(propIndex);
    541  AssertCanChangeFlags(oldProp, flags);
    542 
    543  if (oldProp.flags() != flags) {
    544    if (!Watchtower::watchPropertyFlagsChange(cx, obj, id, oldProp, flags)) {
    545      return false;
    546    }
    547  }
    548 
    549  if (oldProp.isAccessorProperty()) {
    550    objectFlags.setFlag(ObjectFlag::HadGetterSetterChange);
    551  }
    552 
    553  // If the property flags are not changing, the only thing we have to do is
    554  // update the object flags.
    555  if (oldProp.flags() == flags) {
    556    *slotOut = oldProp.slot();
    557    if (objectFlags == obj->shape()->objectFlags()) {
    558      return true;
    559    }
    560    return Shape::replaceShape(cx, obj, objectFlags, obj->shape()->proto(),
    561                               obj->shape()->numFixedSlots());
    562  }
    563 
    564  const JSClass* clasp = obj->shape()->getObjectClass();
    565 
    566  // Rebuilding the new SharedPropMap is linear in the number of properties.
    567  // To avoid going quadratic when changing every property in a large object,
    568  // we cap the number of properties we will re-add. As a fallback, we can
    569  // transition to dictionary mode, which lets us change properties in constant
    570  // time.
    571  const uint32_t MaxCopiedMaps = 4;
    572  bool hasReasonableGap =
    573      map->isShared() && map->asShared()->numPreviousMaps() -
    574                                 propMap->asShared()->numPreviousMaps() <=
    575                             MaxCopiedMaps;
    576 
    577  bool isLast = propMap == map && propIndex == mapLength - 1;
    578  bool nonLastCustomProperty = oldProp.isCustomDataProperty() && !isLast;
    579  if (map->isShared() && !nonLastCustomProperty && hasReasonableGap) {
    580    // To change a property, we get the previous propmap and then call
    581    // addProperty to re-add the changed property with the new flags. If it
    582    // is not the last property, we have to re-add all the following
    583    // properties.
    584    MOZ_ASSERT_IF(isLast, obj->getLastProperty().key() == id);
    585 
    586    // "Remove" the changed property.
    587    Rooted<SharedPropMap*> resultMap(cx, propMap->asShared());
    588    uint32_t resultMapLength = propIndex + 1;
    589    SharedPropMap::getPrevious(&resultMap, &resultMapLength);
    590 
    591    // Re-add the property with the new flags.
    592    if (MOZ_LIKELY(oldProp.hasSlot())) {
    593      *slotOut = oldProp.slot();
    594      if (!SharedPropMap::addPropertyWithKnownSlot(cx, clasp, &resultMap,
    595                                                   &resultMapLength, id, flags,
    596                                                   *slotOut, &objectFlags)) {
    597        return false;
    598      }
    599    } else {
    600      if (!SharedPropMap::addProperty(cx, clasp, &resultMap, &resultMapLength,
    601                                      id, flags, &objectFlags, slotOut)) {
    602        return false;
    603      }
    604    }
    605 
    606    // Build a new SharedPropMap by re-adding each unchanged property.
    607    if (!isLast) {
    608      Rooted<PropertyKey> key(cx);
    609 
    610      SharedPropMapAndIndex startAfter(propMap->asShared(), propIndex);
    611      SharedPropMapAndIndex end(map->asShared(), mapLength - 1);
    612      for (SharedPropMapIter iter(cx, startAfter, end); !iter.done();
    613           iter.next()) {
    614        key = iter.key();
    615        PropertyInfo prop = iter.prop();
    616        PropertyFlags flags = prop.flags();
    617        if (prop.isCustomDataProperty()) {
    618          if (!SharedPropMap::addCustomDataProperty(cx, clasp, &resultMap,
    619                                                    &resultMapLength, key,
    620                                                    flags, &objectFlags)) {
    621            return false;
    622          }
    623        } else {
    624          if (!SharedPropMap::addPropertyWithKnownSlot(
    625                  cx, clasp, &resultMap, &resultMapLength, key, flags,
    626                  prop.slot(), &objectFlags)) {
    627            return false;
    628          }
    629        }
    630      }
    631    }
    632    MOZ_ASSERT(resultMapLength == mapLength);
    633 
    634    SharedShape* newShape = SharedShape::getPropMapShape(
    635        cx, obj->shape()->base(), obj->shape()->numFixedSlots(), resultMap,
    636        resultMapLength, objectFlags);
    637    if (!newShape) {
    638      return false;
    639    }
    640 
    641    if (MOZ_LIKELY(oldProp.hasSlot())) {
    642      MOZ_ASSERT(obj->sharedShape()->slotSpan() == newShape->slotSpan());
    643      obj->setShape(newShape);
    644      return true;
    645    }
    646    return obj->setShapeAndAddNewSlot(cx, newShape, *slotOut);
    647  }
    648 
    649  if (map->isShared()) {
    650    // We gave up on trying to keep a shared map, either because of
    651    // custom data properties or because we would have to copy too
    652    // many properties. Switch to dictionary mode and relookup
    653    // pointers for the new dictionary map.
    654    if (!NativeObject::toDictionaryMode(cx, obj)) {
    655      return false;
    656    }
    657    map = obj->shape()->propMap();
    658    propMap = map->lookup(cx, mapLength, id, &propIndex);
    659    MOZ_ASSERT(propMap);
    660  } else {
    661    if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
    662      return false;
    663    }
    664  }
    665 
    666  // The object has a new dictionary shape (see toDictionaryMode and
    667  // generateNewDictionaryShape calls above), so we can mutate the map and shape
    668  // in place.
    669 
    670  MOZ_ASSERT(map->isDictionary());
    671  MOZ_ASSERT(propMap->isDictionary());
    672 
    673  uint32_t slot = oldProp.hasSlot() ? oldProp.slot() : SHAPE_INVALID_SLOT;
    674  if (slot == SHAPE_INVALID_SLOT) {
    675    if (!allocDictionarySlot(cx, obj, &slot)) {
    676      return false;
    677    }
    678  }
    679 
    680  propMap->asDictionary()->changeProperty(cx, clasp, propIndex, flags, slot,
    681                                          &objectFlags);
    682  obj->dictionaryShape()->setObjectFlagsOfNewShape(objectFlags);
    683 
    684  *slotOut = slot;
    685  return true;
    686 }
    687 
    688 /* static */
    689 bool NativeObject::changeCustomDataPropAttributes(JSContext* cx,
    690                                                  Handle<NativeObject*> obj,
    691                                                  HandleId id,
    692                                                  PropertyFlags flags) {
    693  MOZ_ASSERT(!id.isVoid());
    694 
    695  AutoCheckShapeConsistency check(obj);
    696  AssertValidArrayIndex(obj, id);
    697  AssertValidCustomDataProp(obj, flags);
    698 
    699  Rooted<PropMap*> map(cx, obj->shape()->propMap());
    700  uint32_t mapLength = obj->shape()->propMapLength();
    701 
    702  uint32_t propIndex;
    703  Rooted<PropMap*> propMap(cx, map->lookup(cx, mapLength, id, &propIndex));
    704  MOZ_ASSERT(propMap);
    705 
    706  PropertyInfo oldProp = propMap->getPropertyInfo(propIndex);
    707  MOZ_ASSERT(oldProp.isCustomDataProperty());
    708  AssertCanChangeFlags(oldProp, flags);
    709 
    710  // If the property flags are not changing, we're done.
    711  if (oldProp.flags() == flags) {
    712    return true;
    713  }
    714 
    715  if (!Watchtower::watchPropertyFlagsChange(cx, obj, id, oldProp, flags)) {
    716    return false;
    717  }
    718 
    719  const JSClass* clasp = obj->shape()->getObjectClass();
    720  ObjectFlags objectFlags = obj->shape()->objectFlags();
    721 
    722  if (map->isShared()) {
    723    // Fast path for changing the last property in a SharedPropMap. Call
    724    // getPrevious to "remove" the last property and then call
    725    // addCustomDataProperty to re-add the last property with the new flags.
    726    if (propMap == map && propIndex == mapLength - 1) {
    727      MOZ_ASSERT(obj->getLastProperty().key() == id);
    728 
    729      Rooted<SharedPropMap*> sharedMap(cx, map->asShared());
    730      SharedPropMap::getPrevious(&sharedMap, &mapLength);
    731 
    732      if (!SharedPropMap::addCustomDataProperty(
    733              cx, clasp, &sharedMap, &mapLength, id, flags, &objectFlags)) {
    734        return false;
    735      }
    736 
    737      Shape* newShape = SharedShape::getPropMapShape(
    738          cx, obj->shape()->base(), obj->shape()->numFixedSlots(), sharedMap,
    739          mapLength, objectFlags);
    740      if (!newShape) {
    741        return false;
    742      }
    743      obj->setShape(newShape);
    744      return true;
    745    }
    746 
    747    // Changing a non-last property. Switch to dictionary mode and relookup
    748    // pointers for the new dictionary map.
    749    if (!NativeObject::toDictionaryMode(cx, obj)) {
    750      return false;
    751    }
    752    map = obj->shape()->propMap();
    753    propMap = map->lookup(cx, mapLength, id, &propIndex);
    754    MOZ_ASSERT(propMap);
    755  } else {
    756    if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
    757      return false;
    758    }
    759  }
    760 
    761  // The object has a new dictionary shape (see toDictionaryMode and
    762  // generateNewDictionaryShape calls above), so we can mutate the map and shape
    763  // in place.
    764 
    765  MOZ_ASSERT(map->isDictionary());
    766  MOZ_ASSERT(propMap->isDictionary());
    767 
    768  propMap->asDictionary()->changePropertyFlags(cx, clasp, propIndex, flags,
    769                                               &objectFlags);
    770  obj->dictionaryShape()->setObjectFlagsOfNewShape(objectFlags);
    771  return true;
    772 }
    773 
    774 void NativeObject::maybeFreeDictionaryPropSlots(JSContext* cx,
    775                                                DictionaryPropMap* map,
    776                                                uint32_t mapLength) {
    777  // We can free all non-reserved slots if there are no properties left. We also
    778  // handle the case where there's a single slotless property, to support arrays
    779  // (array.length is a custom data property).
    780 
    781  MOZ_ASSERT(dictionaryShape()->propMap() == map);
    782  MOZ_ASSERT(shape()->propMapLength() == mapLength);
    783 
    784  if (mapLength > 1 || map->previous()) {
    785    return;
    786  }
    787  if (mapLength == 1 && map->getPropertyInfo(0).hasSlot()) {
    788    return;
    789  }
    790 
    791  uint32_t oldSpan = dictionaryModeSlotSpan();
    792  uint32_t newSpan = JSCLASS_RESERVED_SLOTS(getClass());
    793  if (oldSpan == newSpan) {
    794    return;
    795  }
    796 
    797  MOZ_ASSERT(newSpan < oldSpan);
    798 
    799  // Trigger write barriers on the old slots before reallocating.
    800  prepareSlotRangeForOverwrite(newSpan, oldSpan);
    801  invalidateSlotRange(newSpan, oldSpan);
    802 
    803  uint32_t oldCapacity = numDynamicSlots();
    804  uint32_t newCapacity =
    805      calculateDynamicSlots(numFixedSlots(), newSpan, getClass());
    806  if (newCapacity < oldCapacity) {
    807    shrinkSlots(cx, oldCapacity, newCapacity);
    808  }
    809 
    810  setDictionaryModeSlotSpan(newSpan);
    811  map->setFreeList(SHAPE_INVALID_SLOT);
    812 }
    813 
    814 void NativeObject::setShapeAndRemoveLastSlot(JSContext* cx,
    815                                             SharedShape* newShape,
    816                                             uint32_t slot) {
    817  MOZ_ASSERT(!inDictionaryMode());
    818  MOZ_ASSERT(newShape->isShared());
    819  MOZ_ASSERT(newShape->slotSpan() == slot);
    820 
    821  uint32_t numFixed = newShape->numFixedSlots();
    822  if (slot < numFixed) {
    823    setFixedSlot(slot, UndefinedValue());
    824  } else {
    825    setDynamicSlot(numFixed, slot, UndefinedValue());
    826    uint32_t oldCapacity = numDynamicSlots();
    827    uint32_t newCapacity = calculateDynamicSlots(numFixed, slot, getClass());
    828    MOZ_ASSERT(newCapacity <= oldCapacity);
    829    if (newCapacity < oldCapacity) {
    830      shrinkSlots(cx, oldCapacity, newCapacity);
    831    }
    832  }
    833 
    834  setShape(newShape);
    835 }
    836 
    837 /* static */
    838 bool NativeObject::removeProperty(JSContext* cx, Handle<NativeObject*> obj,
    839                                  HandleId id) {
    840  AutoCheckShapeConsistency check(obj);
    841 
    842  Rooted<PropMap*> map(cx, obj->shape()->propMap());
    843  uint32_t mapLength = obj->shape()->propMapLength();
    844 
    845  AutoKeepPropMapTables keep(cx);
    846  PropMapTable* table;
    847  PropMapTable::Ptr ptr;
    848  Rooted<PropMap*> propMap(cx);
    849  uint32_t propIndex;
    850  if (!PropMap::lookupForRemove(cx, map, mapLength, id, keep, propMap.address(),
    851                                &propIndex, &table, &ptr)) {
    852    return false;
    853  }
    854 
    855  if (!propMap) {
    856    return true;
    857  }
    858 
    859  PropertyInfo prop = propMap->getPropertyInfo(propIndex);
    860  bool wasTrackedObjectFuseProp = false;
    861  if (!Watchtower::watchPropertyRemove(cx, obj, id, prop,
    862                                       &wasTrackedObjectFuseProp)) {
    863    return false;
    864  }
    865 
    866  // If we're removing an accessor property, ensure the HadGetterSetterChange
    867  // object flag is set. This is necessary because the slot holding the
    868  // GetterSetter can be changed indirectly by removing the property and then
    869  // adding it back with a different GetterSetter value but the same shape.
    870  if (prop.isAccessorProperty() && !obj->hadGetterSetterChange()) {
    871    if (!NativeObject::setHadGetterSetterChange(cx, obj)) {
    872      return false;
    873    }
    874  }
    875 
    876  if (map->isShared()) {
    877    // Fast path for removing the last property from a SharedPropMap. In this
    878    // case we can just call getPrevious and then look up a shape for the
    879    // resulting map/mapLength.
    880    //
    881    // Don't reuse a previously-used Shape if the object has an ObjectFuse and
    882    // we are removing a tracked property, to avoid the following bug:
    883    //
    884    // 1) The object has Shape S1 and a property P that was marked NotConstant.
    885    // 2) We attach a SetSlot IC stub that guards on the object + S1 and stores
    886    //    a new value for this property.
    887    // 3) We remove property P (we are here now).
    888    // 4) We add a new property P, reuse Shape S1, and mark P Constant.
    889    // 5) We use the SetSlot IC stub again but this is invalid because P is
    890    //    still marked Constant.
    891    if (propMap == map && propIndex == mapLength - 1 &&
    892        !wasTrackedObjectFuseProp) {
    893      MOZ_ASSERT(obj->getLastProperty().key() == id);
    894 
    895      Rooted<SharedPropMap*> sharedMap(cx, map->asShared());
    896      SharedPropMap::getPrevious(&sharedMap, &mapLength);
    897 
    898      SharedShape* shape = obj->sharedShape();
    899      SharedShape* newShape;
    900      if (sharedMap) {
    901        newShape = SharedShape::getPropMapShape(
    902            cx, shape->base(), shape->numFixedSlots(), sharedMap, mapLength,
    903            shape->objectFlags());
    904      } else {
    905        newShape = SharedShape::getInitialShape(
    906            cx, shape->getObjectClass(), shape->realm(), shape->proto(),
    907            shape->numFixedSlots(), shape->objectFlags());
    908      }
    909      if (!newShape) {
    910        return false;
    911      }
    912 
    913      if (MOZ_LIKELY(prop.hasSlot())) {
    914        if (MOZ_LIKELY(prop.slot() == newShape->slotSpan())) {
    915          obj->setShapeAndRemoveLastSlot(cx, newShape, prop.slot());
    916          return true;
    917        }
    918        // Uncommon case: the property is stored in a reserved slot.
    919        // See NativeObject::addPropertyInReservedSlot.
    920        MOZ_ASSERT(prop.slot() < JSCLASS_RESERVED_SLOTS(obj->getClass()));
    921        obj->setSlot(prop.slot(), UndefinedValue());
    922      }
    923      obj->setShape(newShape);
    924      return true;
    925    }
    926 
    927    // Removing a non-last property. Switch to dictionary mode and relookup
    928    // pointers for the new dictionary map.
    929    if (!NativeObject::toDictionaryMode(cx, obj)) {
    930      return false;
    931    }
    932    map = obj->shape()->propMap();
    933    if (!PropMap::lookupForRemove(cx, map, mapLength, id, keep,
    934                                  propMap.address(), &propIndex, &table,
    935                                  &ptr)) {
    936      return false;
    937    }
    938  } else {
    939    if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
    940      return false;
    941    }
    942  }
    943 
    944  // The object has a new dictionary shape (see toDictionaryMode and
    945  // generateNewDictionaryShape calls above), so we can mutate the map and shape
    946  // in place.
    947 
    948  MOZ_ASSERT(map->isDictionary());
    949  MOZ_ASSERT(table);
    950  MOZ_ASSERT(prop == ptr->propertyInfo());
    951 
    952  Rooted<DictionaryPropMap*> dictMap(cx, map->asDictionary());
    953 
    954  // If the property has a slot, free its slot number.
    955  if (prop.hasSlot()) {
    956    obj->freeDictionarySlot(prop.slot());
    957  }
    958 
    959  DictionaryPropMap::removeProperty(cx, &dictMap, &mapLength, table, ptr);
    960 
    961  obj->dictionaryShape()->updateNewShape(obj->shape()->objectFlags(), dictMap,
    962                                         mapLength);
    963 
    964  // If we just deleted the last property, consider shrinking the slots. We only
    965  // do this if there are a lot of slots, to avoid allocating/freeing dynamic
    966  // slots repeatedly.
    967  static constexpr size_t MinSlotSpanForFree = 64;
    968  if (obj->dictionaryModeSlotSpan() >= MinSlotSpanForFree) {
    969    obj->maybeFreeDictionaryPropSlots(cx, dictMap, mapLength);
    970  }
    971 
    972  return true;
    973 }
    974 
    975 /* static */
    976 bool NativeObject::densifySparseElements(JSContext* cx,
    977                                         Handle<NativeObject*> obj) {
    978  AutoCheckShapeConsistency check(obj);
    979  MOZ_ASSERT(obj->inDictionaryMode());
    980 
    981  // First generate a new dictionary shape so that the shape and map can then
    982  // be updated infallibly.
    983  if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
    984    return false;
    985  }
    986 
    987  Rooted<DictionaryPropMap*> map(cx, obj->shape()->propMap()->asDictionary());
    988  uint32_t mapLength = obj->shape()->propMapLength();
    989 
    990  DictionaryPropMap::densifyElements(cx, &map, &mapLength, obj);
    991 
    992  // All indexed properties on the object are now dense. Clear the indexed
    993  // flag so that we will not start using sparse indexes again if we need
    994  // to grow the object.
    995  ObjectFlags objectFlags = obj->shape()->objectFlags();
    996  objectFlags.clearFlag(ObjectFlag::Indexed);
    997 
    998  obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength);
    999 
   1000  obj->maybeFreeDictionaryPropSlots(cx, map, mapLength);
   1001 
   1002  return true;
   1003 }
   1004 
   1005 // static
   1006 bool NativeObject::freezeOrSealProperties(JSContext* cx,
   1007                                          Handle<NativeObject*> obj,
   1008                                          IntegrityLevel level) {
   1009  AutoCheckShapeConsistency check(obj);
   1010 
   1011  if (!Watchtower::watchFreezeOrSeal(cx, obj, level)) {
   1012    return false;
   1013  }
   1014 
   1015  uint32_t mapLength = obj->shape()->propMapLength();
   1016  MOZ_ASSERT(mapLength > 0);
   1017 
   1018  const JSClass* clasp = obj->shape()->getObjectClass();
   1019  ObjectFlags objectFlags = obj->shape()->objectFlags();
   1020 
   1021  if (obj->inDictionaryMode()) {
   1022    // First generate a new dictionary shape so that the map and shape can be
   1023    // updated infallibly.
   1024    if (!generateNewDictionaryShape(cx, obj)) {
   1025      return false;
   1026    }
   1027    DictionaryPropMap* map = obj->dictionaryShape()->propMap();
   1028    map->freezeOrSealProperties(cx, level, clasp, mapLength, &objectFlags);
   1029    obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength);
   1030    return true;
   1031  }
   1032 
   1033  Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap());
   1034  if (!SharedPropMap::freezeOrSealProperties(cx, level, clasp, &map, mapLength,
   1035                                             &objectFlags)) {
   1036    return false;
   1037  }
   1038 
   1039  SharedShape* newShape = SharedShape::getPropMapShape(
   1040      cx, obj->shape()->base(), obj->numFixedSlots(), map, mapLength,
   1041      objectFlags);
   1042  if (!newShape) {
   1043    return false;
   1044  }
   1045  MOZ_ASSERT(obj->sharedShape()->slotSpan() == newShape->slotSpan());
   1046 
   1047  obj->setShape(newShape);
   1048  return true;
   1049 }
   1050 
   1051 /* static */
   1052 bool NativeObject::generateNewDictionaryShape(JSContext* cx,
   1053                                              Handle<NativeObject*> obj) {
   1054  // Clone the current dictionary shape to a new shape. This ensures ICs and
   1055  // other shape guards are properly invalidated before we start mutating the
   1056  // map or new shape.
   1057 
   1058  MOZ_ASSERT(obj->inDictionaryMode());
   1059 
   1060  Shape* shape = DictionaryShape::new_(cx, obj);
   1061  if (!shape) {
   1062    return false;
   1063  }
   1064 
   1065  obj->setShape(shape);
   1066  return true;
   1067 }
   1068 
   1069 /* static */
   1070 bool JSObject::setFlag(JSContext* cx, HandleObject obj, ObjectFlag flag) {
   1071  MOZ_ASSERT(cx->compartment() == obj->compartment());
   1072 
   1073  if (obj->hasFlag(flag)) {
   1074    return true;
   1075  }
   1076 
   1077  ObjectFlags objectFlags = obj->shape()->objectFlags();
   1078  objectFlags.setFlag(flag);
   1079 
   1080  uint32_t numFixed =
   1081      obj->is<NativeObject>() ? obj->as<NativeObject>().numFixedSlots() : 0;
   1082  return Shape::replaceShape(cx, obj, objectFlags, obj->shape()->proto(),
   1083                             numFixed);
   1084 }
   1085 
   1086 static bool SetObjectIsUsedAsPrototype(JSContext* cx, Handle<JSObject*> proto) {
   1087  MOZ_ASSERT(!proto->isUsedAsPrototype());
   1088 
   1089  // Ensure the proto object has a unique id to prevent OOM crashes below.
   1090  uint64_t unused;
   1091  if (!gc::GetOrCreateUniqueId(proto, &unused)) {
   1092    ReportOutOfMemory(cx);
   1093    return false;
   1094  }
   1095 
   1096  return JSObject::setIsUsedAsPrototype(cx, proto);
   1097 }
   1098 
   1099 /* static */
   1100 bool JSObject::setProtoUnchecked(JSContext* cx, HandleObject obj,
   1101                                 Handle<TaggedProto> proto) {
   1102  MOZ_ASSERT(cx->compartment() == obj->compartment());
   1103  MOZ_ASSERT(!obj->staticPrototypeIsImmutable());
   1104  MOZ_ASSERT_IF(!obj->is<ProxyObject>(), obj->nonProxyIsExtensible());
   1105  MOZ_ASSERT(obj->shape()->proto() != proto);
   1106 
   1107  // Notify Watchtower of this proto change, so it can properly invalidate shape
   1108  // teleporting and other optimizations.
   1109  if (!Watchtower::watchProtoChange(cx, obj)) {
   1110    return false;
   1111  }
   1112 
   1113  if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) {
   1114    RootedObject protoObj(cx, proto.toObject());
   1115    if (!SetObjectIsUsedAsPrototype(cx, protoObj)) {
   1116      return false;
   1117    }
   1118  }
   1119 
   1120  uint32_t numFixed =
   1121      obj->is<NativeObject>() ? obj->as<NativeObject>().numFixedSlots() : 0;
   1122  return Shape::replaceShape(cx, obj, obj->shape()->objectFlags(), proto,
   1123                             numFixed);
   1124 }
   1125 
   1126 /* static */
   1127 bool NativeObject::changeNumFixedSlotsAfterSwap(JSContext* cx,
   1128                                                Handle<NativeObject*> obj,
   1129                                                uint32_t nfixed) {
   1130  MOZ_ASSERT(nfixed != obj->shape()->numFixedSlots());
   1131 
   1132  return Shape::replaceShape(cx, obj, obj->shape()->objectFlags(),
   1133                             obj->shape()->proto(), nfixed);
   1134 }
   1135 
   1136 BaseShape::BaseShape(JSContext* cx, const JSClass* clasp, JS::Realm* realm,
   1137                     TaggedProto proto)
   1138    : TenuredCellWithNonGCPointer(clasp), realm_(realm), proto_(proto) {
   1139 #ifdef DEBUG
   1140  AssertJSClassInvariants(clasp);
   1141 #endif
   1142 
   1143  MOZ_ASSERT_IF(proto.isObject(),
   1144                compartment() == proto.toObject()->compartment());
   1145  MOZ_ASSERT_IF(proto.isObject(), proto.toObject()->isUsedAsPrototype());
   1146 
   1147  // Windows may not appear on prototype chains.
   1148  MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
   1149 
   1150  if (MOZ_UNLIKELY(clasp->emulatesUndefined())) {
   1151    cx->runtime()->runtimeFuses.ref().hasSeenObjectEmulateUndefinedFuse.popFuse(
   1152        cx);
   1153  }
   1154 
   1155 #ifdef DEBUG
   1156  if (GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal()) {
   1157    AssertTargetIsNotGray(global);
   1158  }
   1159 #endif
   1160 }
   1161 
   1162 /* static */
   1163 BaseShape* BaseShape::get(JSContext* cx, const JSClass* clasp, JS::Realm* realm,
   1164                          Handle<TaggedProto> proto) {
   1165  auto& table = cx->zone()->shapeZone().baseShapes;
   1166 
   1167  using Lookup = BaseShapeHasher::Lookup;
   1168 
   1169  auto p = MakeDependentAddPtr(cx, table, Lookup(clasp, realm, proto));
   1170  if (p) {
   1171    return *p;
   1172  }
   1173 
   1174  BaseShape* nbase = cx->newCell<BaseShape>(cx, clasp, realm, proto);
   1175  if (!nbase) {
   1176    return nullptr;
   1177  }
   1178 
   1179  if (!p.add(cx, table, Lookup(clasp, realm, proto), nbase)) {
   1180    return nullptr;
   1181  }
   1182 
   1183  return nbase;
   1184 }
   1185 
   1186 // static
   1187 SharedShape* SharedShape::new_(JSContext* cx, Handle<BaseShape*> base,
   1188                               ObjectFlags objectFlags, uint32_t nfixed,
   1189                               Handle<SharedPropMap*> map, uint32_t mapLength) {
   1190  return cx->newCell<SharedShape>(base, objectFlags, nfixed, map, mapLength);
   1191 }
   1192 
   1193 // static
   1194 DictionaryShape* DictionaryShape::new_(JSContext* cx, Handle<BaseShape*> base,
   1195                                       ObjectFlags objectFlags, uint32_t nfixed,
   1196                                       Handle<DictionaryPropMap*> map,
   1197                                       uint32_t mapLength) {
   1198  return cx->newCell<DictionaryShape>(base, objectFlags, nfixed, map,
   1199                                      mapLength);
   1200 }
   1201 
   1202 DictionaryShape::DictionaryShape(NativeObject* nobj)
   1203    : DictionaryShape(nobj->shape()->base(), nobj->shape()->objectFlags(),
   1204                      nobj->shape()->numFixedSlots(),
   1205                      nobj->dictionaryShape()->propMap(),
   1206                      nobj->shape()->propMapLength()) {}
   1207 
   1208 // static
   1209 DictionaryShape* DictionaryShape::new_(JSContext* cx,
   1210                                       Handle<NativeObject*> obj) {
   1211  return cx->newCell<DictionaryShape>(obj);
   1212 }
   1213 
   1214 // static
   1215 ProxyShape* ProxyShape::new_(JSContext* cx, Handle<BaseShape*> base,
   1216                             ObjectFlags objectFlags) {
   1217  return cx->newCell<ProxyShape>(base, objectFlags);
   1218 }
   1219 
   1220 // static
   1221 WasmGCShape* WasmGCShape::new_(JSContext* cx, Handle<BaseShape*> base,
   1222                               const wasm::RecGroup* recGroup,
   1223                               ObjectFlags objectFlags) {
   1224  WasmGCShape* shape = cx->newCell<WasmGCShape>(base, recGroup, objectFlags);
   1225  if (shape) {
   1226    shape->init();
   1227  }
   1228  return shape;
   1229 }
   1230 
   1231 MOZ_ALWAYS_INLINE HashNumber ShapeForAddHasher::hash(const Lookup& l) {
   1232  HashNumber hash = HashPropertyKey(l.key);
   1233  return mozilla::AddToHash(hash, l.flags.toRaw());
   1234 }
   1235 
   1236 MOZ_ALWAYS_INLINE bool ShapeForAddHasher::match(SharedShape* shape,
   1237                                                const Lookup& l) {
   1238  uint32_t slot;
   1239  return shape->lastPropertyMatchesForAdd(l.key, l.flags, &slot);
   1240 }
   1241 
   1242 #if defined(DEBUG) || defined(JS_JITSPEW)
   1243 void BaseShape::dump() const {
   1244  Fprinter out(stderr);
   1245  dump(out);
   1246 }
   1247 
   1248 void BaseShape::dump(js::GenericPrinter& out) const {
   1249  js::JSONPrinter json(out);
   1250  dump(json);
   1251  out.put("\n");
   1252 }
   1253 
   1254 void BaseShape::dump(js::JSONPrinter& json) const {
   1255  json.beginObject();
   1256  dumpFields(json);
   1257  json.endObject();
   1258 }
   1259 
   1260 void BaseShape::dumpFields(js::JSONPrinter& json) const {
   1261  json.formatProperty("address", "(js::BaseShape*)0x%p", this);
   1262 
   1263  json.formatProperty("realm", "(JS::Realm*)0x%p", realm());
   1264 
   1265  if (proto().isDynamic()) {
   1266    json.property("proto", "<dynamic>");
   1267  } else {
   1268    JSObject* protoObj = proto().toObjectOrNull();
   1269    if (protoObj) {
   1270      json.formatProperty("proto", "(JSObject*)0x%p", protoObj);
   1271    } else {
   1272      json.nullProperty("proto");
   1273    }
   1274  }
   1275 }
   1276 
   1277 void Shape::dump() const {
   1278  Fprinter out(stderr);
   1279  dump(out);
   1280 }
   1281 
   1282 void Shape::dump(js::GenericPrinter& out) const {
   1283  js::JSONPrinter json(out);
   1284  dump(json);
   1285  out.put("\n");
   1286 }
   1287 
   1288 void Shape::dump(js::JSONPrinter& json) const {
   1289  json.beginObject();
   1290  dumpFields(json);
   1291  json.endObject();
   1292 }
   1293 
   1294 template <typename KnownF, typename UnknownF>
   1295 void ForEachObjectFlag(ObjectFlags flags, KnownF known, UnknownF unknown) {
   1296  uint16_t raw = flags.toRaw();
   1297  for (uint16_t i = 1; i; i = i << 1) {
   1298    if (!(raw & i)) {
   1299      continue;
   1300    }
   1301    switch (ObjectFlag(raw & i)) {
   1302      case ObjectFlag::IsUsedAsPrototype:
   1303        known("IsUsedAsPrototype");
   1304        break;
   1305      case ObjectFlag::NotExtensible:
   1306        known("NotExtensible");
   1307        break;
   1308      case ObjectFlag::Indexed:
   1309        known("Indexed");
   1310        break;
   1311      case ObjectFlag::HasInterestingSymbol:
   1312        known("HasInterestingSymbol");
   1313        break;
   1314      case ObjectFlag::HasEnumerable:
   1315        known("HasEnumerable");
   1316        break;
   1317      case ObjectFlag::FrozenElements:
   1318        known("FrozenElements");
   1319        break;
   1320      case ObjectFlag::InvalidatedTeleporting:
   1321        known("InvalidatedTeleporting");
   1322        break;
   1323      case ObjectFlag::ImmutablePrototype:
   1324        known("ImmutablePrototype");
   1325        break;
   1326      case ObjectFlag::QualifiedVarObj:
   1327        known("QualifiedVarObj");
   1328        break;
   1329      case ObjectFlag::HasNonWritableOrAccessorPropExclProto:
   1330        known("HasNonWritableOrAccessorPropExclProto");
   1331        break;
   1332      case ObjectFlag::HadGetterSetterChange:
   1333        known("HadGetterSetterChange");
   1334        break;
   1335      case ObjectFlag::UseWatchtowerTestingLog:
   1336        known("UseWatchtowerTestingLog");
   1337        break;
   1338      case ObjectFlag::GenerationCountedGlobal:
   1339        known("GenerationCountedGlobal");
   1340        break;
   1341      case ObjectFlag::NeedsProxyGetSetResultValidation:
   1342        known("NeedsProxyGetSetResultValidation");
   1343        break;
   1344      case ObjectFlag::HasRealmFuseProperty:
   1345        known("HasRealmFuseProperty");
   1346        break;
   1347      case ObjectFlag::HasPreservedWrapper:
   1348        known("HasPreservedWrapper");
   1349        break;
   1350      case ObjectFlag::HasNonFunctionAccessor:
   1351        known("HasNonFunctionAccessor");
   1352        break;
   1353      default:
   1354        unknown(i);
   1355        break;
   1356    }
   1357  }
   1358 }
   1359 
   1360 void Shape::dumpFields(js::JSONPrinter& json) const {
   1361  json.formatProperty("address", "(js::Shape*)0x%p", this);
   1362 
   1363  json.beginObjectProperty("base");
   1364  base()->dumpFields(json);
   1365  json.endObject();
   1366 
   1367  switch (kind()) {
   1368    case Kind::Shared:
   1369      json.property("kind", "Shared");
   1370      break;
   1371    case Kind::Dictionary:
   1372      json.property("kind", "Dictionary");
   1373      break;
   1374    case Kind::Proxy:
   1375      json.property("kind", "Proxy");
   1376      break;
   1377    case Kind::WasmGC:
   1378      json.property("kind", "WasmGC");
   1379      break;
   1380  }
   1381 
   1382  json.beginInlineListProperty("objectFlags");
   1383  ForEachObjectFlag(
   1384      objectFlags(), [&](const char* name) { json.value("%s", name); },
   1385      [&](uint16_t value) { json.value("Unknown(%04x)", value); });
   1386  json.endInlineList();
   1387 
   1388  if (isNative()) {
   1389    json.property("numFixedSlots", asNative().numFixedSlots());
   1390    json.property("propMapLength", asNative().propMapLength());
   1391 
   1392    if (asNative().propMap()) {
   1393      json.beginObjectProperty("propMap");
   1394      asNative().propMap()->dumpFields(json);
   1395      json.endObject();
   1396    } else {
   1397      json.nullProperty("propMap");
   1398    }
   1399  }
   1400 
   1401  if (isShared()) {
   1402    if (getObjectClass()->isNativeObject()) {
   1403      json.property("slotSpan", asShared().slotSpan());
   1404    }
   1405  }
   1406 
   1407  if (isWasmGC()) {
   1408    json.formatProperty("recGroup", "(js::wasm::RecGroup*)0x%p",
   1409                        asWasmGC().recGroup());
   1410  }
   1411 }
   1412 
   1413 void Shape::dumpStringContent(js::GenericPrinter& out) const {
   1414  out.printf("<(js::Shape*)0x%p", this);
   1415 
   1416  if (isDictionary()) {
   1417    out.put(", dictionary");
   1418  }
   1419 
   1420  out.put(", objectFlags=[");
   1421  bool first = true;
   1422  ForEachObjectFlag(
   1423      objectFlags(),
   1424      [&](const char* name) {
   1425        if (!first) {
   1426          out.put(", ");
   1427        }
   1428        first = false;
   1429 
   1430        out.put(name);
   1431      },
   1432      [&](uint16_t value) {
   1433        if (!first) {
   1434          out.put(", ");
   1435        }
   1436        first = false;
   1437 
   1438        out.printf("Unknown(%04x)", value);
   1439      });
   1440  out.put("]>");
   1441 }
   1442 #endif  // defined(DEBUG) || defined(JS_JITSPEW)
   1443 
   1444 /* static */
   1445 SharedShape* SharedShape::getInitialShape(JSContext* cx, const JSClass* clasp,
   1446                                          JS::Realm* realm, TaggedProto proto,
   1447                                          size_t nfixed,
   1448                                          ObjectFlags objectFlags) {
   1449  MOZ_ASSERT(cx->compartment() == realm->compartment());
   1450  MOZ_ASSERT_IF(proto.isObject(),
   1451                cx->isInsideCurrentCompartment(proto.toObject()));
   1452 
   1453  if (proto.isObject()) {
   1454    if (proto.toObject()->isUsedAsPrototype()) {
   1455      // Use the cache on the prototype's shape to get to the initial shape.
   1456      // This cache has a hit rate of 80-90% on typical workloads and is faster
   1457      // than the HashSet lookup below.
   1458      JSObject* protoObj = proto.toObject();
   1459      Shape* protoObjShape = protoObj->shape();
   1460      if (protoObjShape->cache().isShapeWithProto()) {
   1461        SharedShape* shape = protoObjShape->cache().toShapeWithProto();
   1462        if (shape->numFixedSlots() == nfixed &&
   1463            shape->objectFlags() == objectFlags &&
   1464            shape->getObjectClass() == clasp && shape->realm() == realm &&
   1465            shape->proto() == proto) {
   1466 #ifdef DEBUG
   1467          // Verify the table lookup below would have resulted in the same
   1468          // shape.
   1469          using Lookup = InitialShapeHasher::Lookup;
   1470          Lookup lookup(clasp, realm, proto, nfixed, objectFlags);
   1471          auto p = realm->zone()->shapeZone().initialShapes.lookup(lookup);
   1472          MOZ_ASSERT(*p == shape);
   1473 #endif
   1474          return shape;
   1475        }
   1476      }
   1477    } else {
   1478      RootedObject protoObj(cx, proto.toObject());
   1479      if (!SetObjectIsUsedAsPrototype(cx, protoObj)) {
   1480        return nullptr;
   1481      }
   1482      proto = TaggedProto(protoObj);
   1483    }
   1484  }
   1485 
   1486  auto& table = realm->zone()->shapeZone().initialShapes;
   1487 
   1488  using Lookup = InitialShapeHasher::Lookup;
   1489  auto ptr = MakeDependentAddPtr(
   1490      cx, table, Lookup(clasp, realm, proto, nfixed, objectFlags));
   1491  if (ptr) {
   1492    // Cache the result of this lookup on the prototype's shape.
   1493    if (proto.isObject()) {
   1494      JSObject* protoObj = proto.toObject();
   1495      Shape* protoShape = protoObj->shape();
   1496      if (!protoShape->cache().isForAdd() &&
   1497          RegisterShapeCache(cx, protoShape)) {
   1498        protoShape->cacheRef().setShapeWithProto(*ptr);
   1499      }
   1500    }
   1501    return *ptr;
   1502  }
   1503 
   1504  Rooted<TaggedProto> protoRoot(cx, proto);
   1505  Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot));
   1506  if (!nbase) {
   1507    return nullptr;
   1508  }
   1509 
   1510  SharedShape* shape =
   1511      SharedShape::new_(cx, nbase, objectFlags, nfixed, nullptr, 0);
   1512  if (!shape) {
   1513    return nullptr;
   1514  }
   1515 
   1516  Lookup lookup(clasp, realm, protoRoot, nfixed, objectFlags);
   1517  if (!ptr.add(cx, table, lookup, shape)) {
   1518    return nullptr;
   1519  }
   1520 
   1521  return shape;
   1522 }
   1523 
   1524 /* static */
   1525 SharedShape* SharedShape::getInitialShape(JSContext* cx, const JSClass* clasp,
   1526                                          JS::Realm* realm, TaggedProto proto,
   1527                                          gc::AllocKind kind,
   1528                                          ObjectFlags objectFlags) {
   1529  return getInitialShape(cx, clasp, realm, proto, GetGCKindSlots(kind),
   1530                         objectFlags);
   1531 }
   1532 
   1533 /* static */
   1534 SharedShape* SharedShape::getPropMapShape(
   1535    JSContext* cx, BaseShape* base, size_t nfixed, Handle<SharedPropMap*> map,
   1536    uint32_t mapLength, ObjectFlags objectFlags, bool* allocatedNewShape) {
   1537  MOZ_ASSERT(cx->compartment() == base->compartment());
   1538  MOZ_ASSERT_IF(base->proto().isObject(),
   1539                cx->isInsideCurrentCompartment(base->proto().toObject()));
   1540  MOZ_ASSERT_IF(base->proto().isObject(),
   1541                base->proto().toObject()->isUsedAsPrototype());
   1542  MOZ_ASSERT(map);
   1543  MOZ_ASSERT(mapLength > 0);
   1544 
   1545  auto& table = cx->zone()->shapeZone().propMapShapes;
   1546 
   1547  using Lookup = PropMapShapeHasher::Lookup;
   1548  auto ptr = MakeDependentAddPtr(
   1549      cx, table, Lookup(base, nfixed, map, mapLength, objectFlags));
   1550  if (ptr) {
   1551    if (allocatedNewShape) {
   1552      *allocatedNewShape = false;
   1553    }
   1554    return *ptr;
   1555  }
   1556 
   1557  Rooted<BaseShape*> baseRoot(cx, base);
   1558  SharedShape* shape =
   1559      SharedShape::new_(cx, baseRoot, objectFlags, nfixed, map, mapLength);
   1560  if (!shape) {
   1561    return nullptr;
   1562  }
   1563 
   1564  Lookup lookup(baseRoot, nfixed, map, mapLength, objectFlags);
   1565  if (!ptr.add(cx, table, lookup, shape)) {
   1566    return nullptr;
   1567  }
   1568 
   1569  if (allocatedNewShape) {
   1570    *allocatedNewShape = true;
   1571  }
   1572 
   1573  return shape;
   1574 }
   1575 
   1576 /* static */
   1577 SharedShape* SharedShape::getInitialOrPropMapShape(
   1578    JSContext* cx, const JSClass* clasp, JS::Realm* realm, TaggedProto proto,
   1579    size_t nfixed, Handle<SharedPropMap*> map, uint32_t mapLength,
   1580    ObjectFlags objectFlags) {
   1581  if (!map) {
   1582    MOZ_ASSERT(mapLength == 0);
   1583    return getInitialShape(cx, clasp, realm, proto, nfixed, objectFlags);
   1584  }
   1585 
   1586  Rooted<TaggedProto> protoRoot(cx, proto);
   1587  BaseShape* nbase = BaseShape::get(cx, clasp, realm, protoRoot);
   1588  if (!nbase) {
   1589    return nullptr;
   1590  }
   1591 
   1592  return getPropMapShape(cx, nbase, nfixed, map, mapLength, objectFlags);
   1593 }
   1594 
   1595 /* static */
   1596 void SharedShape::insertInitialShape(JSContext* cx,
   1597                                     Handle<SharedShape*> shape) {
   1598  using Lookup = InitialShapeHasher::Lookup;
   1599  Lookup lookup(shape->getObjectClass(), shape->realm(), shape->proto(),
   1600                shape->numFixedSlots(), shape->objectFlags());
   1601 
   1602  auto& table = cx->zone()->shapeZone().initialShapes;
   1603  InitialShapeSet::Ptr p = table.lookup(lookup);
   1604  MOZ_ASSERT(p);
   1605 
   1606  // The metadata callback can end up causing redundant changes of the initial
   1607  // shape.
   1608  SharedShape* initialShape = *p;
   1609  if (initialShape == shape) {
   1610    return;
   1611  }
   1612 
   1613  MOZ_ASSERT(initialShape->numFixedSlots() == shape->numFixedSlots());
   1614  MOZ_ASSERT(initialShape->base() == shape->base());
   1615  MOZ_ASSERT(initialShape->objectFlags() == shape->objectFlags());
   1616 
   1617  table.replaceKey(p, lookup, shape.get());
   1618 
   1619  // Purge the prototype's shape cache entry.
   1620  if (shape->proto().isObject()) {
   1621    JSObject* protoObj = shape->proto().toObject();
   1622    if (protoObj->shape()->cache().isShapeWithProto()) {
   1623      protoObj->shape()->cacheRef().setNone();
   1624    }
   1625  }
   1626 }
   1627 
   1628 /* static */
   1629 ProxyShape* ProxyShape::getShape(JSContext* cx, const JSClass* clasp,
   1630                                 JS::Realm* realm, TaggedProto proto,
   1631                                 ObjectFlags objectFlags) {
   1632  MOZ_ASSERT(cx->compartment() == realm->compartment());
   1633  MOZ_ASSERT_IF(proto.isObject(),
   1634                cx->isInsideCurrentCompartment(proto.toObject()));
   1635 
   1636  if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) {
   1637    RootedObject protoObj(cx, proto.toObject());
   1638    if (!SetObjectIsUsedAsPrototype(cx, protoObj)) {
   1639      return nullptr;
   1640    }
   1641    proto = TaggedProto(protoObj);
   1642  }
   1643 
   1644  auto& table = realm->zone()->shapeZone().proxyShapes;
   1645 
   1646  using Lookup = ProxyShapeHasher::Lookup;
   1647  auto ptr =
   1648      MakeDependentAddPtr(cx, table, Lookup(clasp, realm, proto, objectFlags));
   1649  if (ptr) {
   1650    return *ptr;
   1651  }
   1652 
   1653  Rooted<TaggedProto> protoRoot(cx, proto);
   1654  Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot));
   1655  if (!nbase) {
   1656    return nullptr;
   1657  }
   1658 
   1659  Rooted<ProxyShape*> shape(cx, ProxyShape::new_(cx, nbase, objectFlags));
   1660  if (!shape) {
   1661    return nullptr;
   1662  }
   1663 
   1664  Lookup lookup(clasp, realm, protoRoot, objectFlags);
   1665  if (!ptr.add(cx, table, lookup, shape)) {
   1666    return nullptr;
   1667  }
   1668 
   1669  return shape;
   1670 }
   1671 
   1672 /* static */
   1673 WasmGCShape* WasmGCShape::getShape(JSContext* cx, const JSClass* clasp,
   1674                                   JS::Realm* realm, TaggedProto proto,
   1675                                   const wasm::RecGroup* recGroup,
   1676                                   ObjectFlags objectFlags) {
   1677  MOZ_ASSERT(cx->compartment() == realm->compartment());
   1678  MOZ_ASSERT_IF(proto.isObject(),
   1679                cx->isInsideCurrentCompartment(proto.toObject()));
   1680 
   1681  if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) {
   1682    RootedObject protoObj(cx, proto.toObject());
   1683    if (!SetObjectIsUsedAsPrototype(cx, protoObj)) {
   1684      return nullptr;
   1685    }
   1686    proto = TaggedProto(protoObj);
   1687  }
   1688 
   1689  auto& table = realm->zone()->shapeZone().wasmGCShapes;
   1690 
   1691  using Lookup = WasmGCShapeHasher::Lookup;
   1692  auto ptr = MakeDependentAddPtr(
   1693      cx, table, Lookup(clasp, realm, proto, recGroup, objectFlags));
   1694  if (ptr) {
   1695    return *ptr;
   1696  }
   1697 
   1698  Rooted<TaggedProto> protoRoot(cx, proto);
   1699  Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot));
   1700  if (!nbase) {
   1701    return nullptr;
   1702  }
   1703 
   1704  Rooted<WasmGCShape*> shape(
   1705      cx, WasmGCShape::new_(cx, nbase, recGroup, objectFlags));
   1706  if (!shape) {
   1707    return nullptr;
   1708  }
   1709 
   1710  Lookup lookup(clasp, realm, protoRoot, recGroup, objectFlags);
   1711  if (!ptr.add(cx, table, lookup, shape)) {
   1712    return nullptr;
   1713  }
   1714 
   1715  return shape;
   1716 }
   1717 
   1718 JS::ubi::Node::Size JS::ubi::Concrete<js::Shape>::size(
   1719    mozilla::MallocSizeOf mallocSizeOf) const {
   1720  Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
   1721 
   1722  if (get().cache().isShapeSetForAdd()) {
   1723    ShapeSetForAdd* set = get().cache().toShapeSetForAdd();
   1724    size += set->shallowSizeOfIncludingThis(mallocSizeOf);
   1725  }
   1726 
   1727  return size;
   1728 }
   1729 
   1730 JS::ubi::Node::Size JS::ubi::Concrete<js::BaseShape>::size(
   1731    mozilla::MallocSizeOf mallocSizeOf) const {
   1732  return js::gc::Arena::thingSize(get().asTenured().getAllocKind());
   1733 }