diff options
Diffstat (limited to 'js/src/vm/Shape.cpp')
-rw-r--r-- | js/src/vm/Shape.cpp | 1652 |
1 files changed, 1652 insertions, 0 deletions
diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp new file mode 100644 index 0000000000..c04b5e2cf2 --- /dev/null +++ b/js/src/vm/Shape.cpp @@ -0,0 +1,1652 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/Shape-inl.h" + +#include "mozilla/MathAlgorithms.h" +#include "mozilla/PodOperations.h" + +#include "gc/HashUtil.h" +#include "js/friend/WindowProxy.h" // js::IsWindow +#include "js/HashTable.h" +#include "js/Printer.h" // js::GenericPrinter, js::Fprinter +#include "js/UniquePtr.h" +#include "vm/JSObject.h" +#include "vm/JSONPrinter.h" // js::JSONPrinter +#include "vm/ShapeZone.h" +#include "vm/Watchtower.h" + +#include "gc/StableCellHasher-inl.h" +#include "vm/JSContext-inl.h" +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; + +using mozilla::CeilingLog2Size; +using mozilla::PodZero; + +using JS::AutoCheckCannotGC; + +/* static */ +bool Shape::replaceShape(JSContext* cx, HandleObject obj, + ObjectFlags objectFlags, TaggedProto proto, + uint32_t nfixed) { + Shape* newShape; + switch (obj->shape()->kind()) { + case Kind::Shared: { + Handle<NativeObject*> nobj = obj.as<NativeObject>(); + if (nobj->shape()->propMap()) { + Rooted<BaseShape*> base(cx, obj->shape()->base()); + if (proto != base->proto()) { + Rooted<TaggedProto> protoRoot(cx, proto); + base = BaseShape::get(cx, base->clasp(), base->realm(), protoRoot); + if (!base) { + return false; + } + } + Rooted<SharedPropMap*> map(cx, nobj->sharedShape()->propMap()); + uint32_t mapLength = nobj->shape()->propMapLength(); + newShape = SharedShape::getPropMapShape(cx, base, nfixed, map, + mapLength, objectFlags); + } else { + newShape = SharedShape::getInitialShape( + cx, obj->shape()->getObjectClass(), obj->shape()->realm(), proto, + nfixed, objectFlags); + } + break; + } + case Kind::Dictionary: { + Handle<NativeObject*> nobj = obj.as<NativeObject>(); + + Rooted<BaseShape*> base(cx, nobj->shape()->base()); + if (proto != base->proto()) { + Rooted<TaggedProto> protoRoot(cx, proto); + base = BaseShape::get(cx, nobj->getClass(), nobj->realm(), protoRoot); + if (!base) { + return false; + } + } + + Rooted<DictionaryPropMap*> map(cx, nobj->dictionaryShape()->propMap()); + uint32_t mapLength = nobj->shape()->propMapLength(); + newShape = + DictionaryShape::new_(cx, base, objectFlags, nfixed, map, mapLength); + break; + } + case Kind::Proxy: + MOZ_ASSERT(nfixed == 0); + newShape = + ProxyShape::getShape(cx, obj->shape()->getObjectClass(), + obj->shape()->realm(), proto, objectFlags); + break; + case Kind::WasmGC: + MOZ_ASSERT(nfixed == 0); + const wasm::RecGroup* recGroup = obj->shape()->asWasmGC().recGroup(); + newShape = WasmGCShape::getShape(cx, obj->shape()->getObjectClass(), + obj->shape()->realm(), proto, recGroup, + objectFlags); + break; + } + if (!newShape) { + return false; + } + + obj->setShape(newShape); + return true; +} + +/* static */ +bool js::NativeObject::toDictionaryMode(JSContext* cx, + Handle<NativeObject*> obj) { + MOZ_ASSERT(!obj->inDictionaryMode()); + MOZ_ASSERT(cx->isInsideCurrentCompartment(obj)); + + Rooted<NativeShape*> shape(cx, obj->shape()); + uint32_t span = obj->slotSpan(); + + uint32_t mapLength = shape->propMapLength(); + MOZ_ASSERT(mapLength > 0, "shouldn't convert empty object to dictionary"); + + // Clone the shared property map to an unshared dictionary map. + Rooted<SharedPropMap*> map(cx, shape->propMap()->asShared()); + Rooted<DictionaryPropMap*> dictMap( + cx, SharedPropMap::toDictionaryMap(cx, map, mapLength)); + if (!dictMap) { + return false; + } + + // Allocate and use a new dictionary shape. + Rooted<BaseShape*> base(cx, shape->base()); + shape = DictionaryShape::new_(cx, base, shape->objectFlags(), + shape->numFixedSlots(), dictMap, mapLength); + if (!shape) { + return false; + } + obj->setShape(shape); + + MOZ_ASSERT(obj->inDictionaryMode()); + obj->setDictionaryModeSlotSpan(span); + + return true; +} + +namespace js { + +class MOZ_RAII AutoCheckShapeConsistency { +#ifdef DEBUG + Handle<NativeObject*> obj_; +#endif + + public: + explicit AutoCheckShapeConsistency(Handle<NativeObject*> obj) +#ifdef DEBUG + : obj_(obj) +#endif + { + } + +#ifdef DEBUG + ~AutoCheckShapeConsistency() { obj_->checkShapeConsistency(); } +#endif +}; + +} // namespace js + +/* static */ MOZ_ALWAYS_INLINE bool +NativeObject::maybeConvertToDictionaryForAdd(JSContext* cx, + Handle<NativeObject*> obj) { + if (obj->inDictionaryMode()) { + return true; + } + SharedPropMap* map = obj->sharedShape()->propMap(); + if (!map) { + return true; + } + if (MOZ_LIKELY(!map->shouldConvertToDictionaryForAdd())) { + return true; + } + return toDictionaryMode(cx, obj); +} + +static void AssertValidCustomDataProp(NativeObject* obj, PropertyFlags flags) { + // We only support custom data properties on ArrayObject and ArgumentsObject. + // The mechanism is deprecated so we don't want to add new uses. + MOZ_ASSERT(flags.isCustomDataProperty()); + MOZ_ASSERT(!flags.isAccessorProperty()); + MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<ArgumentsObject>()); +} + +/* static */ +bool NativeObject::addCustomDataProperty(JSContext* cx, + Handle<NativeObject*> obj, HandleId id, + PropertyFlags flags) { + MOZ_ASSERT(!id.isVoid()); + MOZ_ASSERT(!id.isPrivateName()); + MOZ_ASSERT(!obj->containsPure(id)); + + AutoCheckShapeConsistency check(obj); + AssertValidCustomDataProp(obj, flags); + + if (!Watchtower::watchPropertyAdd(cx, obj, id)) { + return false; + } + + if (!maybeConvertToDictionaryForAdd(cx, obj)) { + return false; + } + + ObjectFlags objectFlags = obj->shape()->objectFlags(); + const JSClass* clasp = obj->shape()->getObjectClass(); + + if (obj->inDictionaryMode()) { + // First generate a new dictionary shape so that the map can be mutated + // without having to worry about OOM conditions. + if (!NativeObject::generateNewDictionaryShape(cx, obj)) { + return false; + } + + Rooted<DictionaryPropMap*> map(cx, obj->dictionaryShape()->propMap()); + uint32_t mapLength = obj->shape()->propMapLength(); + if (!DictionaryPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags, + SHAPE_INVALID_SLOT, &objectFlags)) { + return false; + } + + obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength); + return true; + } + + Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap()); + uint32_t mapLength = obj->shape()->propMapLength(); + if (!SharedPropMap::addCustomDataProperty(cx, clasp, &map, &mapLength, id, + flags, &objectFlags)) { + return false; + } + + Shape* shape = SharedShape::getPropMapShape(cx, obj->shape()->base(), + obj->shape()->numFixedSlots(), + map, mapLength, objectFlags); + if (!shape) { + return false; + } + + obj->setShape(shape); + return true; +} + +static ShapeSetForAdd* MakeShapeSetForAdd(SharedShape* shape1, + SharedShape* shape2) { + MOZ_ASSERT(shape1 != shape2); + MOZ_ASSERT(shape1->propMapLength() == shape2->propMapLength()); + + auto hash = MakeUnique<ShapeSetForAdd>(); + if (!hash || !hash->reserve(2)) { + return nullptr; + } + + PropertyInfoWithKey prop = shape1->lastProperty(); + hash->putNewInfallible(ShapeForAddHasher::Lookup(prop.key(), prop.flags()), + shape1); + + prop = shape2->lastProperty(); + hash->putNewInfallible(ShapeForAddHasher::Lookup(prop.key(), prop.flags()), + shape2); + + return hash.release(); +} + +static MOZ_ALWAYS_INLINE SharedShape* LookupShapeForAdd(Shape* shape, + PropertyKey key, + PropertyFlags flags, + uint32_t* slot) { + ShapeCachePtr cache = shape->cache(); + + if (cache.isSingleShapeForAdd()) { + SharedShape* newShape = cache.toSingleShapeForAdd(); + if (newShape->lastPropertyMatchesForAdd(key, flags, slot)) { + return newShape; + } + return nullptr; + } + + if (cache.isShapeSetForAdd()) { + ShapeSetForAdd* set = cache.toShapeSetForAdd(); + ShapeForAddHasher::Lookup lookup(key, flags); + if (auto p = set->lookup(lookup)) { + SharedShape* newShape = *p; + *slot = newShape->lastProperty().slot(); + return newShape; + } + return nullptr; + } + + MOZ_ASSERT(!cache.isForAdd()); + return nullptr; +} + +// Add shapes with a non-None ShapeCachePtr to the shapesWithCache list so that +// these caches can be discarded on GC. +static bool RegisterShapeCache(JSContext* cx, Shape* shape) { + ShapeCachePtr cache = shape->cache(); + if (!cache.isNone()) { + // Already registered this shape. + return true; + } + return cx->zone()->shapeZone().shapesWithCache.append(shape); +} + +/* static */ +bool NativeObject::addProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, PropertyFlags flags, + uint32_t* slot) { + AutoCheckShapeConsistency check(obj); + MOZ_ASSERT(!flags.isCustomDataProperty(), + "Use addCustomDataProperty for custom data properties"); + + // The object must not contain a property named |id|. The object must be + // extensible, but allow private fields and sparsifying dense elements. + MOZ_ASSERT(!id.isVoid()); + MOZ_ASSERT(!obj->containsPure(id)); + MOZ_ASSERT_IF(!id.isPrivateName(), + obj->isExtensible() || + (id.isInt() && obj->containsDenseElement(id.toInt())) || + // R&T wrappers are non-extensible, but we still want to be + // able to lazily resolve their properties. We can + // special-case them to allow doing so. + IF_RECORD_TUPLE(IsExtendedPrimitiveWrapper(*obj), false)); + + if (!Watchtower::watchPropertyAdd(cx, obj, id)) { + return false; + } + + if (!maybeConvertToDictionaryForAdd(cx, obj)) { + return false; + } + + if (auto* shape = LookupShapeForAdd(obj->shape(), id, flags, slot)) { + return obj->setShapeAndAddNewSlot(cx, shape, *slot); + } + + if (obj->inDictionaryMode()) { + // First generate a new dictionary shape so that the map and shape can be + // mutated without having to worry about OOM conditions. + if (!NativeObject::generateNewDictionaryShape(cx, obj)) { + return false; + } + if (!allocDictionarySlot(cx, obj, slot)) { + return false; + } + + ObjectFlags objectFlags = obj->shape()->objectFlags(); + const JSClass* clasp = obj->shape()->getObjectClass(); + + Rooted<DictionaryPropMap*> map(cx, obj->shape()->propMap()->asDictionary()); + uint32_t mapLength = obj->shape()->propMapLength(); + if (!DictionaryPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags, + *slot, &objectFlags)) { + return false; + } + + obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength); + return true; + } + + ObjectFlags objectFlags = obj->shape()->objectFlags(); + const JSClass* clasp = obj->shape()->getObjectClass(); + + Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap()); + uint32_t mapLength = obj->shape()->propMapLength(); + + if (!SharedPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags, + &objectFlags, slot)) { + return false; + } + + bool allocatedNewShape; + SharedShape* newShape = SharedShape::getPropMapShape( + cx, obj->shape()->base(), obj->shape()->numFixedSlots(), map, mapLength, + objectFlags, &allocatedNewShape); + if (!newShape) { + return false; + } + + Shape* oldShape = obj->shape(); + if (!obj->setShapeAndAddNewSlot(cx, newShape, *slot)) { + return false; + } + + // Add the new shape to the old shape's shape cache, to optimize this shape + // transition. Don't do this if we just allocated a new shape, because that + // suggests this may not be a hot transition that would benefit from the + // cache. + + if (allocatedNewShape) { + return true; + } + + if (!RegisterShapeCache(cx, oldShape)) { + // Ignore OOM, the cache is just an optimization. + return true; + } + + ShapeCachePtr& cache = oldShape->cacheRef(); + if (!cache.isForAdd()) { + cache.setSingleShapeForAdd(newShape); + } else if (cache.isSingleShapeForAdd()) { + SharedShape* prevShape = cache.toSingleShapeForAdd(); + if (ShapeSetForAdd* set = MakeShapeSetForAdd(prevShape, newShape)) { + cache.setShapeSetForAdd(set); + AddCellMemory(oldShape, sizeof(ShapeSetForAdd), + MemoryUse::ShapeSetForAdd); + } + } else { + ShapeForAddHasher::Lookup lookup(id, flags); + (void)cache.toShapeSetForAdd()->putNew(lookup, newShape); + } + + return true; +} + +void Shape::maybeCacheIterator(JSContext* cx, PropertyIteratorObject* iter) { + if (!cache().isNone() && !cache().isIterator()) { + // If we're already caching other shape data, skip caching the iterator. + return; + } + if (MOZ_UNLIKELY(!RegisterShapeCache(cx, this))) { + // Ignore OOM. The cache is just an optimization. + return; + } + cacheRef().setIterator(iter); +} + +/* static */ +bool NativeObject::addPropertyInReservedSlot(JSContext* cx, + Handle<NativeObject*> obj, + HandleId id, uint32_t slot, + PropertyFlags flags) { + AutoCheckShapeConsistency check(obj); + MOZ_ASSERT(!flags.isCustomDataProperty(), + "Use addCustomDataProperty for custom data properties"); + + // The slot must be a reserved slot. + MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(obj->getClass())); + + // The object must not contain a property named |id| and must be extensible. + MOZ_ASSERT(!id.isVoid()); + MOZ_ASSERT(!obj->containsPure(id)); + MOZ_ASSERT(!id.isPrivateName()); + MOZ_ASSERT(obj->isExtensible()); + + // The object must not be in dictionary mode. This simplifies the code below. + MOZ_ASSERT(!obj->inDictionaryMode()); + + // We don't need to call Watchtower::watchPropertyAdd here because this isn't + // used for any watched objects. + MOZ_ASSERT(!Watchtower::watchesPropertyAdd(obj)); + + ObjectFlags objectFlags = obj->shape()->objectFlags(); + const JSClass* clasp = obj->shape()->getObjectClass(); + + Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap()); + uint32_t mapLength = obj->shape()->propMapLength(); + if (!SharedPropMap::addPropertyInReservedSlot(cx, clasp, &map, &mapLength, id, + flags, slot, &objectFlags)) { + return false; + } + + Shape* shape = SharedShape::getPropMapShape(cx, obj->shape()->base(), + obj->shape()->numFixedSlots(), + map, mapLength, objectFlags); + if (!shape) { + return false; + } + obj->setShape(shape); + + MOZ_ASSERT(obj->getLastProperty().slot() == slot); + return true; +} + +/* + * Assert some invariants that should hold when changing properties. It's the + * responsibility of the callers to ensure these hold. + */ +static void AssertCanChangeFlags(PropertyInfo prop, PropertyFlags flags) { +#ifdef DEBUG + if (prop.configurable()) { + return; + } + + // A non-configurable property must stay non-configurable. + MOZ_ASSERT(!flags.configurable()); + + // Reject attempts to turn a non-configurable data property into an accessor + // or custom data property. + MOZ_ASSERT_IF(prop.isDataProperty(), flags.isDataProperty()); + + // Reject attempts to turn a non-configurable accessor property into a data + // property or custom data property. + MOZ_ASSERT_IF(prop.isAccessorProperty(), flags.isAccessorProperty()); +#endif +} + +static void AssertValidArrayIndex(NativeObject* obj, jsid id) { +#ifdef DEBUG + if (obj->is<ArrayObject>()) { + ArrayObject* arr = &obj->as<ArrayObject>(); + uint32_t index; + if (IdIsIndex(id, &index)) { + MOZ_ASSERT(index < arr->length() || arr->lengthIsWritable()); + } + } +#endif +} + +/* static */ +bool NativeObject::changeProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id, PropertyFlags flags, + uint32_t* slotOut) { + MOZ_ASSERT(!id.isVoid()); + + AutoCheckShapeConsistency check(obj); + AssertValidArrayIndex(obj, id); + MOZ_ASSERT(!flags.isCustomDataProperty(), + "Use changeCustomDataPropAttributes for custom data properties"); + + if (!Watchtower::watchPropertyChange(cx, obj, id, flags)) { + return false; + } + + Rooted<PropMap*> map(cx, obj->shape()->propMap()); + uint32_t mapLength = obj->shape()->propMapLength(); + + uint32_t propIndex; + Rooted<PropMap*> propMap(cx, map->lookup(cx, mapLength, id, &propIndex)); + MOZ_ASSERT(propMap); + + ObjectFlags objectFlags = obj->shape()->objectFlags(); + + PropertyInfo oldProp = propMap->getPropertyInfo(propIndex); + AssertCanChangeFlags(oldProp, flags); + + if (oldProp.isAccessorProperty()) { + objectFlags.setFlag(ObjectFlag::HadGetterSetterChange); + } + + // If the property flags are not changing, the only thing we have to do is + // update the object flags. This prevents a dictionary mode conversion below. + if (oldProp.flags() == flags) { + *slotOut = oldProp.slot(); + if (objectFlags == obj->shape()->objectFlags()) { + return true; + } + return Shape::replaceShape(cx, obj, objectFlags, obj->shape()->proto(), + obj->shape()->numFixedSlots()); + } + + const JSClass* clasp = obj->shape()->getObjectClass(); + + if (map->isShared()) { + // Fast path for changing the last property in a SharedPropMap. Call + // getPrevious to "remove" the last property and then call addProperty + // to re-add the last property with the new flags. + if (propMap == map && propIndex == mapLength - 1) { + MOZ_ASSERT(obj->getLastProperty().key() == id); + + Rooted<SharedPropMap*> sharedMap(cx, map->asShared()); + SharedPropMap::getPrevious(&sharedMap, &mapLength); + + if (MOZ_LIKELY(oldProp.hasSlot())) { + *slotOut = oldProp.slot(); + if (!SharedPropMap::addPropertyWithKnownSlot(cx, clasp, &sharedMap, + &mapLength, id, flags, + *slotOut, &objectFlags)) { + return false; + } + } else { + if (!SharedPropMap::addProperty(cx, clasp, &sharedMap, &mapLength, id, + flags, &objectFlags, slotOut)) { + return false; + } + } + + SharedShape* newShape = SharedShape::getPropMapShape( + cx, obj->shape()->base(), obj->shape()->numFixedSlots(), sharedMap, + mapLength, objectFlags); + if (!newShape) { + return false; + } + + if (MOZ_LIKELY(oldProp.hasSlot())) { + MOZ_ASSERT(obj->sharedShape()->slotSpan() == newShape->slotSpan()); + obj->setShape(newShape); + return true; + } + return obj->setShapeAndAddNewSlot(cx, newShape, *slotOut); + } + + // Changing a non-last property. Switch to dictionary mode and relookup + // pointers for the new dictionary map. + if (!NativeObject::toDictionaryMode(cx, obj)) { + return false; + } + map = obj->shape()->propMap(); + propMap = map->lookup(cx, mapLength, id, &propIndex); + MOZ_ASSERT(propMap); + } else { + if (!NativeObject::generateNewDictionaryShape(cx, obj)) { + return false; + } + } + + // The object has a new dictionary shape (see toDictionaryMode and + // generateNewDictionaryShape calls above), so we can mutate the map and shape + // in place. + + MOZ_ASSERT(map->isDictionary()); + MOZ_ASSERT(propMap->isDictionary()); + + uint32_t slot = oldProp.hasSlot() ? oldProp.slot() : SHAPE_INVALID_SLOT; + if (slot == SHAPE_INVALID_SLOT) { + if (!allocDictionarySlot(cx, obj, &slot)) { + return false; + } + } + + propMap->asDictionary()->changeProperty(cx, clasp, propIndex, flags, slot, + &objectFlags); + obj->dictionaryShape()->setObjectFlagsOfNewShape(objectFlags); + + *slotOut = slot; + return true; +} + +/* static */ +bool NativeObject::changeCustomDataPropAttributes(JSContext* cx, + Handle<NativeObject*> obj, + HandleId id, + PropertyFlags flags) { + MOZ_ASSERT(!id.isVoid()); + + AutoCheckShapeConsistency check(obj); + AssertValidArrayIndex(obj, id); + AssertValidCustomDataProp(obj, flags); + + if (!Watchtower::watchPropertyChange(cx, obj, id, flags)) { + return false; + } + + Rooted<PropMap*> map(cx, obj->shape()->propMap()); + uint32_t mapLength = obj->shape()->propMapLength(); + + uint32_t propIndex; + Rooted<PropMap*> propMap(cx, map->lookup(cx, mapLength, id, &propIndex)); + MOZ_ASSERT(propMap); + + PropertyInfo oldProp = propMap->getPropertyInfo(propIndex); + MOZ_ASSERT(oldProp.isCustomDataProperty()); + AssertCanChangeFlags(oldProp, flags); + + // If the property flags are not changing, we're done. + if (oldProp.flags() == flags) { + return true; + } + + const JSClass* clasp = obj->shape()->getObjectClass(); + ObjectFlags objectFlags = obj->shape()->objectFlags(); + + if (map->isShared()) { + // Fast path for changing the last property in a SharedPropMap. Call + // getPrevious to "remove" the last property and then call + // addCustomDataProperty to re-add the last property with the new flags. + if (propMap == map && propIndex == mapLength - 1) { + MOZ_ASSERT(obj->getLastProperty().key() == id); + + Rooted<SharedPropMap*> sharedMap(cx, map->asShared()); + SharedPropMap::getPrevious(&sharedMap, &mapLength); + + if (!SharedPropMap::addCustomDataProperty( + cx, clasp, &sharedMap, &mapLength, id, flags, &objectFlags)) { + return false; + } + + Shape* newShape = SharedShape::getPropMapShape( + cx, obj->shape()->base(), obj->shape()->numFixedSlots(), sharedMap, + mapLength, objectFlags); + if (!newShape) { + return false; + } + obj->setShape(newShape); + return true; + } + + // Changing a non-last property. Switch to dictionary mode and relookup + // pointers for the new dictionary map. + if (!NativeObject::toDictionaryMode(cx, obj)) { + return false; + } + map = obj->shape()->propMap(); + propMap = map->lookup(cx, mapLength, id, &propIndex); + MOZ_ASSERT(propMap); + } else { + if (!NativeObject::generateNewDictionaryShape(cx, obj)) { + return false; + } + } + + // The object has a new dictionary shape (see toDictionaryMode and + // generateNewDictionaryShape calls above), so we can mutate the map and shape + // in place. + + MOZ_ASSERT(map->isDictionary()); + MOZ_ASSERT(propMap->isDictionary()); + + propMap->asDictionary()->changePropertyFlags(cx, clasp, propIndex, flags, + &objectFlags); + obj->dictionaryShape()->setObjectFlagsOfNewShape(objectFlags); + return true; +} + +void NativeObject::maybeFreeDictionaryPropSlots(JSContext* cx, + DictionaryPropMap* map, + uint32_t mapLength) { + // We can free all non-reserved slots if there are no properties left. We also + // handle the case where there's a single slotless property, to support arrays + // (array.length is a custom data property). + + MOZ_ASSERT(dictionaryShape()->propMap() == map); + MOZ_ASSERT(shape()->propMapLength() == mapLength); + + if (mapLength > 1 || map->previous()) { + return; + } + if (mapLength == 1 && map->getPropertyInfo(0).hasSlot()) { + return; + } + + uint32_t oldSpan = dictionaryModeSlotSpan(); + uint32_t newSpan = JSCLASS_RESERVED_SLOTS(getClass()); + if (oldSpan == newSpan) { + return; + } + + MOZ_ASSERT(newSpan < oldSpan); + + // Trigger write barriers on the old slots before reallocating. + prepareSlotRangeForOverwrite(newSpan, oldSpan); + invalidateSlotRange(newSpan, oldSpan); + + uint32_t oldCapacity = numDynamicSlots(); + uint32_t newCapacity = + calculateDynamicSlots(numFixedSlots(), newSpan, getClass()); + if (newCapacity < oldCapacity) { + shrinkSlots(cx, oldCapacity, newCapacity); + } + + setDictionaryModeSlotSpan(newSpan); + map->setFreeList(SHAPE_INVALID_SLOT); +} + +void NativeObject::setShapeAndRemoveLastSlot(JSContext* cx, + SharedShape* newShape, + uint32_t slot) { + MOZ_ASSERT(!inDictionaryMode()); + MOZ_ASSERT(newShape->isShared()); + MOZ_ASSERT(newShape->slotSpan() == slot); + + uint32_t numFixed = newShape->numFixedSlots(); + if (slot < numFixed) { + setFixedSlot(slot, UndefinedValue()); + } else { + setDynamicSlot(numFixed, slot, UndefinedValue()); + uint32_t oldCapacity = numDynamicSlots(); + uint32_t newCapacity = calculateDynamicSlots(numFixed, slot, getClass()); + MOZ_ASSERT(newCapacity <= oldCapacity); + if (newCapacity < oldCapacity) { + shrinkSlots(cx, oldCapacity, newCapacity); + } + } + + setShape(newShape); +} + +/* static */ +bool NativeObject::removeProperty(JSContext* cx, Handle<NativeObject*> obj, + HandleId id) { + AutoCheckShapeConsistency check(obj); + + Rooted<PropMap*> map(cx, obj->shape()->propMap()); + uint32_t mapLength = obj->shape()->propMapLength(); + + AutoKeepPropMapTables keep(cx); + PropMapTable* table; + PropMapTable::Ptr ptr; + Rooted<PropMap*> propMap(cx); + uint32_t propIndex; + if (!PropMap::lookupForRemove(cx, map, mapLength, id, keep, propMap.address(), + &propIndex, &table, &ptr)) { + return false; + } + + if (!propMap) { + return true; + } + + if (!Watchtower::watchPropertyRemove(cx, obj, id)) { + return false; + } + + PropertyInfo prop = propMap->getPropertyInfo(propIndex); + + // If we're removing an accessor property, ensure the HadGetterSetterChange + // object flag is set. This is necessary because the slot holding the + // GetterSetter can be changed indirectly by removing the property and then + // adding it back with a different GetterSetter value but the same shape. + if (prop.isAccessorProperty() && !obj->hadGetterSetterChange()) { + if (!NativeObject::setHadGetterSetterChange(cx, obj)) { + return false; + } + } + + if (map->isShared()) { + // Fast path for removing the last property from a SharedPropMap. In this + // case we can just call getPrevious and then look up a shape for the + // resulting map/mapLength. + if (propMap == map && propIndex == mapLength - 1) { + MOZ_ASSERT(obj->getLastProperty().key() == id); + + Rooted<SharedPropMap*> sharedMap(cx, map->asShared()); + SharedPropMap::getPrevious(&sharedMap, &mapLength); + + SharedShape* shape = obj->sharedShape(); + SharedShape* newShape; + if (sharedMap) { + newShape = SharedShape::getPropMapShape( + cx, shape->base(), shape->numFixedSlots(), sharedMap, mapLength, + shape->objectFlags()); + } else { + newShape = SharedShape::getInitialShape( + cx, shape->getObjectClass(), shape->realm(), shape->proto(), + shape->numFixedSlots(), shape->objectFlags()); + } + if (!newShape) { + return false; + } + + if (MOZ_LIKELY(prop.hasSlot())) { + if (MOZ_LIKELY(prop.slot() == newShape->slotSpan())) { + obj->setShapeAndRemoveLastSlot(cx, newShape, prop.slot()); + return true; + } + // Uncommon case: the property is stored in a reserved slot. + // See NativeObject::addPropertyInReservedSlot. + MOZ_ASSERT(prop.slot() < JSCLASS_RESERVED_SLOTS(obj->getClass())); + obj->setSlot(prop.slot(), UndefinedValue()); + } + obj->setShape(newShape); + return true; + } + + // Removing a non-last property. Switch to dictionary mode and relookup + // pointers for the new dictionary map. + if (!NativeObject::toDictionaryMode(cx, obj)) { + return false; + } + map = obj->shape()->propMap(); + if (!PropMap::lookupForRemove(cx, map, mapLength, id, keep, + propMap.address(), &propIndex, &table, + &ptr)) { + return false; + } + } else { + if (!NativeObject::generateNewDictionaryShape(cx, obj)) { + return false; + } + } + + // The object has a new dictionary shape (see toDictionaryMode and + // generateNewDictionaryShape calls above), so we can mutate the map and shape + // in place. + + MOZ_ASSERT(map->isDictionary()); + MOZ_ASSERT(table); + MOZ_ASSERT(prop == ptr->propertyInfo()); + + Rooted<DictionaryPropMap*> dictMap(cx, map->asDictionary()); + + // If the property has a slot, free its slot number. + if (prop.hasSlot()) { + obj->freeDictionarySlot(prop.slot()); + } + + DictionaryPropMap::removeProperty(cx, &dictMap, &mapLength, table, ptr); + + obj->dictionaryShape()->updateNewShape(obj->shape()->objectFlags(), dictMap, + mapLength); + + // If we just deleted the last property, consider shrinking the slots. We only + // do this if there are a lot of slots, to avoid allocating/freeing dynamic + // slots repeatedly. + static constexpr size_t MinSlotSpanForFree = 64; + if (obj->dictionaryModeSlotSpan() >= MinSlotSpanForFree) { + obj->maybeFreeDictionaryPropSlots(cx, dictMap, mapLength); + } + + return true; +} + +/* static */ +bool NativeObject::densifySparseElements(JSContext* cx, + Handle<NativeObject*> obj) { + AutoCheckShapeConsistency check(obj); + MOZ_ASSERT(obj->inDictionaryMode()); + + // First generate a new dictionary shape so that the shape and map can then + // be updated infallibly. + if (!NativeObject::generateNewDictionaryShape(cx, obj)) { + return false; + } + + Rooted<DictionaryPropMap*> map(cx, obj->shape()->propMap()->asDictionary()); + uint32_t mapLength = obj->shape()->propMapLength(); + + DictionaryPropMap::densifyElements(cx, &map, &mapLength, obj); + + // All indexed properties on the object are now dense. Clear the indexed + // flag so that we will not start using sparse indexes again if we need + // to grow the object. + ObjectFlags objectFlags = obj->shape()->objectFlags(); + objectFlags.clearFlag(ObjectFlag::Indexed); + + obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength); + + obj->maybeFreeDictionaryPropSlots(cx, map, mapLength); + + return true; +} + +// static +bool NativeObject::freezeOrSealProperties(JSContext* cx, + Handle<NativeObject*> obj, + IntegrityLevel level) { + AutoCheckShapeConsistency check(obj); + + if (!Watchtower::watchFreezeOrSeal(cx, obj)) { + return false; + } + + uint32_t mapLength = obj->shape()->propMapLength(); + MOZ_ASSERT(mapLength > 0); + + const JSClass* clasp = obj->shape()->getObjectClass(); + ObjectFlags objectFlags = obj->shape()->objectFlags(); + + if (obj->inDictionaryMode()) { + // First generate a new dictionary shape so that the map and shape can be + // updated infallibly. + if (!generateNewDictionaryShape(cx, obj)) { + return false; + } + DictionaryPropMap* map = obj->dictionaryShape()->propMap(); + map->freezeOrSealProperties(cx, level, clasp, mapLength, &objectFlags); + obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength); + return true; + } + + Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap()); + if (!SharedPropMap::freezeOrSealProperties(cx, level, clasp, &map, mapLength, + &objectFlags)) { + return false; + } + + SharedShape* newShape = SharedShape::getPropMapShape( + cx, obj->shape()->base(), obj->numFixedSlots(), map, mapLength, + objectFlags); + if (!newShape) { + return false; + } + MOZ_ASSERT(obj->sharedShape()->slotSpan() == newShape->slotSpan()); + + obj->setShape(newShape); + return true; +} + +/* static */ +bool NativeObject::generateNewDictionaryShape(JSContext* cx, + Handle<NativeObject*> obj) { + // Clone the current dictionary shape to a new shape. This ensures ICs and + // other shape guards are properly invalidated before we start mutating the + // map or new shape. + + MOZ_ASSERT(obj->inDictionaryMode()); + + Shape* shape = DictionaryShape::new_(cx, obj); + if (!shape) { + return false; + } + + obj->setShape(shape); + return true; +} + +/* static */ +bool JSObject::setFlag(JSContext* cx, HandleObject obj, ObjectFlag flag) { + MOZ_ASSERT(cx->compartment() == obj->compartment()); + + if (obj->hasFlag(flag)) { + return true; + } + + ObjectFlags objectFlags = obj->shape()->objectFlags(); + objectFlags.setFlag(flag); + + uint32_t numFixed = + obj->is<NativeObject>() ? obj->as<NativeObject>().numFixedSlots() : 0; + return Shape::replaceShape(cx, obj, objectFlags, obj->shape()->proto(), + numFixed); +} + +static bool SetObjectIsUsedAsPrototype(JSContext* cx, Handle<JSObject*> proto) { + MOZ_ASSERT(!proto->isUsedAsPrototype()); + + // Ensure the proto object has a unique id to prevent OOM crashes below. + uint64_t unused; + if (!gc::GetOrCreateUniqueId(proto, &unused)) { + ReportOutOfMemory(cx); + return false; + } + + return JSObject::setIsUsedAsPrototype(cx, proto); +} + +/* static */ +bool JSObject::setProtoUnchecked(JSContext* cx, HandleObject obj, + Handle<TaggedProto> proto) { + MOZ_ASSERT(cx->compartment() == obj->compartment()); + MOZ_ASSERT(!obj->staticPrototypeIsImmutable()); + MOZ_ASSERT_IF(!obj->is<ProxyObject>(), obj->nonProxyIsExtensible()); + MOZ_ASSERT(obj->shape()->proto() != proto); + + // Notify Watchtower of this proto change, so it can properly invalidate shape + // teleporting and other optimizations. + if (!Watchtower::watchProtoChange(cx, obj)) { + return false; + } + + if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) { + RootedObject protoObj(cx, proto.toObject()); + if (!SetObjectIsUsedAsPrototype(cx, protoObj)) { + return false; + } + } + + uint32_t numFixed = + obj->is<NativeObject>() ? obj->as<NativeObject>().numFixedSlots() : 0; + return Shape::replaceShape(cx, obj, obj->shape()->objectFlags(), proto, + numFixed); +} + +/* static */ +bool NativeObject::changeNumFixedSlotsAfterSwap(JSContext* cx, + Handle<NativeObject*> obj, + uint32_t nfixed) { + MOZ_ASSERT(nfixed != obj->shape()->numFixedSlots()); + + return Shape::replaceShape(cx, obj, obj->shape()->objectFlags(), + obj->shape()->proto(), nfixed); +} + +BaseShape::BaseShape(JSContext* cx, const JSClass* clasp, JS::Realm* realm, + TaggedProto proto) + : TenuredCellWithNonGCPointer(clasp), realm_(realm), proto_(proto) { +#ifdef DEBUG + AssertJSClassInvariants(clasp); +#endif + + MOZ_ASSERT_IF(proto.isObject(), + compartment() == proto.toObject()->compartment()); + MOZ_ASSERT_IF(proto.isObject(), proto.toObject()->isUsedAsPrototype()); + + // Windows may not appear on prototype chains. + MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject())); + + if (MOZ_UNLIKELY(clasp->emulatesUndefined())) { + cx->runtime()->hasSeenObjectEmulateUndefinedFuse.ref().popFuse(cx); + } + +#ifdef DEBUG + if (GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal()) { + AssertTargetIsNotGray(global); + } +#endif +} + +/* static */ +BaseShape* BaseShape::get(JSContext* cx, const JSClass* clasp, JS::Realm* realm, + Handle<TaggedProto> proto) { + auto& table = cx->zone()->shapeZone().baseShapes; + + using Lookup = BaseShapeHasher::Lookup; + + auto p = MakeDependentAddPtr(cx, table, Lookup(clasp, realm, proto)); + if (p) { + return *p; + } + + BaseShape* nbase = cx->newCell<BaseShape>(cx, clasp, realm, proto); + if (!nbase) { + return nullptr; + } + + if (!p.add(cx, table, Lookup(clasp, realm, proto), nbase)) { + return nullptr; + } + + return nbase; +} + +// static +SharedShape* SharedShape::new_(JSContext* cx, Handle<BaseShape*> base, + ObjectFlags objectFlags, uint32_t nfixed, + Handle<SharedPropMap*> map, uint32_t mapLength) { + return cx->newCell<SharedShape>(base, objectFlags, nfixed, map, mapLength); +} + +// static +DictionaryShape* DictionaryShape::new_(JSContext* cx, Handle<BaseShape*> base, + ObjectFlags objectFlags, uint32_t nfixed, + Handle<DictionaryPropMap*> map, + uint32_t mapLength) { + return cx->newCell<DictionaryShape>(base, objectFlags, nfixed, map, + mapLength); +} + +DictionaryShape::DictionaryShape(NativeObject* nobj) + : DictionaryShape(nobj->shape()->base(), nobj->shape()->objectFlags(), + nobj->shape()->numFixedSlots(), + nobj->dictionaryShape()->propMap(), + nobj->shape()->propMapLength()) {} + +// static +DictionaryShape* DictionaryShape::new_(JSContext* cx, + Handle<NativeObject*> obj) { + return cx->newCell<DictionaryShape>(obj); +} + +// static +ProxyShape* ProxyShape::new_(JSContext* cx, Handle<BaseShape*> base, + ObjectFlags objectFlags) { + return cx->newCell<ProxyShape>(base, objectFlags); +} + +// static +WasmGCShape* WasmGCShape::new_(JSContext* cx, Handle<BaseShape*> base, + const wasm::RecGroup* recGroup, + ObjectFlags objectFlags) { + WasmGCShape* shape = cx->newCell<WasmGCShape>(base, recGroup, objectFlags); + if (shape) { + shape->init(); + } + return shape; +} + +MOZ_ALWAYS_INLINE HashNumber ShapeForAddHasher::hash(const Lookup& l) { + HashNumber hash = HashPropertyKey(l.key); + return mozilla::AddToHash(hash, l.flags.toRaw()); +} + +MOZ_ALWAYS_INLINE bool ShapeForAddHasher::match(SharedShape* shape, + const Lookup& l) { + uint32_t slot; + return shape->lastPropertyMatchesForAdd(l.key, l.flags, &slot); +} + +#if defined(DEBUG) || defined(JS_JITSPEW) +void BaseShape::dump() const { + Fprinter out(stderr); + dump(out); +} + +void BaseShape::dump(js::GenericPrinter& out) const { + js::JSONPrinter json(out); + dump(json); + out.put("\n"); +} + +void BaseShape::dump(js::JSONPrinter& json) const { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void BaseShape::dumpFields(js::JSONPrinter& json) const { + json.formatProperty("address", "(js::BaseShape*)0x%p", this); + + json.formatProperty("realm", "(JS::Realm*)0x%p", realm()); + + if (proto().isDynamic()) { + json.property("proto", "<dynamic>"); + } else { + JSObject* protoObj = proto().toObjectOrNull(); + if (protoObj) { + json.formatProperty("proto", "(JSObject*)0x%p", protoObj); + } else { + json.nullProperty("proto"); + } + } +} + +void Shape::dump() const { + Fprinter out(stderr); + dump(out); +} + +void Shape::dump(js::GenericPrinter& out) const { + js::JSONPrinter json(out); + dump(json); + out.put("\n"); +} + +void Shape::dump(js::JSONPrinter& json) const { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +template <typename KnownF, typename UnknownF> +void ForEachObjectFlag(ObjectFlags flags, KnownF known, UnknownF unknown) { + uint16_t raw = flags.toRaw(); + for (uint16_t i = 1; i; i = i << 1) { + if (!(raw & i)) { + continue; + } + switch (ObjectFlag(raw & i)) { + case ObjectFlag::IsUsedAsPrototype: + known("IsUsedAsPrototype"); + break; + case ObjectFlag::NotExtensible: + known("NotExtensible"); + break; + case ObjectFlag::Indexed: + known("Indexed"); + break; + case ObjectFlag::HasInterestingSymbol: + known("HasInterestingSymbol"); + break; + case ObjectFlag::HasEnumerable: + known("HasEnumerable"); + break; + case ObjectFlag::FrozenElements: + known("FrozenElements"); + break; + case ObjectFlag::InvalidatedTeleporting: + known("InvalidatedTeleporting"); + break; + case ObjectFlag::ImmutablePrototype: + known("ImmutablePrototype"); + break; + case ObjectFlag::QualifiedVarObj: + known("QualifiedVarObj"); + break; + case ObjectFlag::HasNonWritableOrAccessorPropExclProto: + known("HasNonWritableOrAccessorPropExclProto"); + break; + case ObjectFlag::HadGetterSetterChange: + known("HadGetterSetterChange"); + break; + case ObjectFlag::UseWatchtowerTestingLog: + known("UseWatchtowerTestingLog"); + break; + case ObjectFlag::GenerationCountedGlobal: + known("GenerationCountedGlobal"); + break; + case ObjectFlag::NeedsProxyGetSetResultValidation: + known("NeedsProxyGetSetResultValidation"); + break; + case ObjectFlag::HasFuseProperty: + known("HasFuseProperty"); + break; + default: + unknown(i); + break; + } + } +} + +void Shape::dumpFields(js::JSONPrinter& json) const { + json.formatProperty("address", "(js::Shape*)0x%p", this); + + json.beginObjectProperty("base"); + base()->dumpFields(json); + json.endObject(); + + switch (kind()) { + case Kind::Shared: + json.property("kind", "Shared"); + break; + case Kind::Dictionary: + json.property("kind", "Dictionary"); + break; + case Kind::Proxy: + json.property("kind", "Proxy"); + break; + case Kind::WasmGC: + json.property("kind", "WasmGC"); + break; + } + + json.beginInlineListProperty("objectFlags"); + ForEachObjectFlag( + objectFlags(), [&](const char* name) { json.value("%s", name); }, + [&](uint16_t value) { json.value("Unknown(%04x)", value); }); + json.endInlineList(); + + if (isNative()) { + json.property("numFixedSlots", asNative().numFixedSlots()); + json.property("propMapLength", asNative().propMapLength()); + + if (asNative().propMap()) { + json.beginObjectProperty("propMap"); + asNative().propMap()->dumpFields(json); + json.endObject(); + } else { + json.nullProperty("propMap"); + } + } + + if (isShared()) { + if (getObjectClass()->isNativeObject()) { + json.property("slotSpan", asShared().slotSpan()); + } + } + + if (isWasmGC()) { + json.formatProperty("recGroup", "(js::wasm::RecGroup*)0x%p", + asWasmGC().recGroup()); + } +} + +void Shape::dumpStringContent(js::GenericPrinter& out) const { + out.printf("<(js::Shape*)0x%p", this); + + if (isDictionary()) { + out.put(", dictionary"); + } + + out.put(", objectFlags=["); + bool first = true; + ForEachObjectFlag( + objectFlags(), + [&](const char* name) { + if (!first) { + out.put(", "); + } + first = false; + + out.put(name); + }, + [&](uint16_t value) { + if (!first) { + out.put(", "); + } + first = false; + + out.printf("Unknown(%04x)", value); + }); + out.put("]>"); +} +#endif // defined(DEBUG) || defined(JS_JITSPEW) + +/* static */ +SharedShape* SharedShape::getInitialShape(JSContext* cx, const JSClass* clasp, + JS::Realm* realm, TaggedProto proto, + size_t nfixed, + ObjectFlags objectFlags) { + MOZ_ASSERT(cx->compartment() == realm->compartment()); + MOZ_ASSERT_IF(proto.isObject(), + cx->isInsideCurrentCompartment(proto.toObject())); + + if (proto.isObject()) { + if (proto.toObject()->isUsedAsPrototype()) { + // Use the cache on the prototype's shape to get to the initial shape. + // This cache has a hit rate of 80-90% on typical workloads and is faster + // than the HashSet lookup below. + JSObject* protoObj = proto.toObject(); + Shape* protoObjShape = protoObj->shape(); + if (protoObjShape->cache().isShapeWithProto()) { + SharedShape* shape = protoObjShape->cache().toShapeWithProto(); + if (shape->numFixedSlots() == nfixed && + shape->objectFlags() == objectFlags && + shape->getObjectClass() == clasp && shape->realm() == realm && + shape->proto() == proto) { +#ifdef DEBUG + // Verify the table lookup below would have resulted in the same + // shape. + using Lookup = InitialShapeHasher::Lookup; + Lookup lookup(clasp, realm, proto, nfixed, objectFlags); + auto p = realm->zone()->shapeZone().initialShapes.lookup(lookup); + MOZ_ASSERT(*p == shape); +#endif + return shape; + } + } + } else { + RootedObject protoObj(cx, proto.toObject()); + if (!SetObjectIsUsedAsPrototype(cx, protoObj)) { + return nullptr; + } + proto = TaggedProto(protoObj); + } + } + + auto& table = realm->zone()->shapeZone().initialShapes; + + using Lookup = InitialShapeHasher::Lookup; + auto ptr = MakeDependentAddPtr( + cx, table, Lookup(clasp, realm, proto, nfixed, objectFlags)); + if (ptr) { + // Cache the result of this lookup on the prototype's shape. + if (proto.isObject()) { + JSObject* protoObj = proto.toObject(); + Shape* protoShape = protoObj->shape(); + if (!protoShape->cache().isForAdd() && + RegisterShapeCache(cx, protoShape)) { + protoShape->cacheRef().setShapeWithProto(*ptr); + } + } + return *ptr; + } + + Rooted<TaggedProto> protoRoot(cx, proto); + Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot)); + if (!nbase) { + return nullptr; + } + + Rooted<SharedShape*> shape( + cx, SharedShape::new_(cx, nbase, objectFlags, nfixed, nullptr, 0)); + if (!shape) { + return nullptr; + } + + Lookup lookup(clasp, realm, protoRoot, nfixed, objectFlags); + if (!ptr.add(cx, table, lookup, shape)) { + return nullptr; + } + + return shape; +} + +/* static */ +SharedShape* SharedShape::getInitialShape(JSContext* cx, const JSClass* clasp, + JS::Realm* realm, TaggedProto proto, + gc::AllocKind kind, + ObjectFlags objectFlags) { + return getInitialShape(cx, clasp, realm, proto, GetGCKindSlots(kind), + objectFlags); +} + +/* static */ +SharedShape* SharedShape::getPropMapShape( + JSContext* cx, BaseShape* base, size_t nfixed, Handle<SharedPropMap*> map, + uint32_t mapLength, ObjectFlags objectFlags, bool* allocatedNewShape) { + MOZ_ASSERT(cx->compartment() == base->compartment()); + MOZ_ASSERT_IF(base->proto().isObject(), + cx->isInsideCurrentCompartment(base->proto().toObject())); + MOZ_ASSERT_IF(base->proto().isObject(), + base->proto().toObject()->isUsedAsPrototype()); + MOZ_ASSERT(map); + MOZ_ASSERT(mapLength > 0); + + auto& table = cx->zone()->shapeZone().propMapShapes; + + using Lookup = PropMapShapeHasher::Lookup; + auto ptr = MakeDependentAddPtr( + cx, table, Lookup(base, nfixed, map, mapLength, objectFlags)); + if (ptr) { + if (allocatedNewShape) { + *allocatedNewShape = false; + } + return *ptr; + } + + Rooted<BaseShape*> baseRoot(cx, base); + Rooted<SharedShape*> shape( + cx, SharedShape::new_(cx, baseRoot, objectFlags, nfixed, map, mapLength)); + if (!shape) { + return nullptr; + } + + Lookup lookup(baseRoot, nfixed, map, mapLength, objectFlags); + if (!ptr.add(cx, table, lookup, shape)) { + return nullptr; + } + + if (allocatedNewShape) { + *allocatedNewShape = true; + } + + return shape; +} + +/* static */ +SharedShape* SharedShape::getInitialOrPropMapShape( + JSContext* cx, const JSClass* clasp, JS::Realm* realm, TaggedProto proto, + size_t nfixed, Handle<SharedPropMap*> map, uint32_t mapLength, + ObjectFlags objectFlags) { + if (!map) { + MOZ_ASSERT(mapLength == 0); + return getInitialShape(cx, clasp, realm, proto, nfixed, objectFlags); + } + + Rooted<TaggedProto> protoRoot(cx, proto); + BaseShape* nbase = BaseShape::get(cx, clasp, realm, protoRoot); + if (!nbase) { + return nullptr; + } + + return getPropMapShape(cx, nbase, nfixed, map, mapLength, objectFlags); +} + +/* static */ +void SharedShape::insertInitialShape(JSContext* cx, + Handle<SharedShape*> shape) { + using Lookup = InitialShapeHasher::Lookup; + Lookup lookup(shape->getObjectClass(), shape->realm(), shape->proto(), + shape->numFixedSlots(), shape->objectFlags()); + + auto& table = cx->zone()->shapeZone().initialShapes; + InitialShapeSet::Ptr p = table.lookup(lookup); + MOZ_ASSERT(p); + + // The metadata callback can end up causing redundant changes of the initial + // shape. + SharedShape* initialShape = *p; + if (initialShape == shape) { + return; + } + + MOZ_ASSERT(initialShape->numFixedSlots() == shape->numFixedSlots()); + MOZ_ASSERT(initialShape->base() == shape->base()); + MOZ_ASSERT(initialShape->objectFlags() == shape->objectFlags()); + + table.replaceKey(p, lookup, shape.get()); + + // Purge the prototype's shape cache entry. + if (shape->proto().isObject()) { + JSObject* protoObj = shape->proto().toObject(); + if (protoObj->shape()->cache().isShapeWithProto()) { + protoObj->shape()->cacheRef().setNone(); + } + } +} + +/* static */ +ProxyShape* ProxyShape::getShape(JSContext* cx, const JSClass* clasp, + JS::Realm* realm, TaggedProto proto, + ObjectFlags objectFlags) { + MOZ_ASSERT(cx->compartment() == realm->compartment()); + MOZ_ASSERT_IF(proto.isObject(), + cx->isInsideCurrentCompartment(proto.toObject())); + + if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) { + RootedObject protoObj(cx, proto.toObject()); + if (!SetObjectIsUsedAsPrototype(cx, protoObj)) { + return nullptr; + } + proto = TaggedProto(protoObj); + } + + auto& table = realm->zone()->shapeZone().proxyShapes; + + using Lookup = ProxyShapeHasher::Lookup; + auto ptr = + MakeDependentAddPtr(cx, table, Lookup(clasp, realm, proto, objectFlags)); + if (ptr) { + return *ptr; + } + + Rooted<TaggedProto> protoRoot(cx, proto); + Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot)); + if (!nbase) { + return nullptr; + } + + Rooted<ProxyShape*> shape(cx, ProxyShape::new_(cx, nbase, objectFlags)); + if (!shape) { + return nullptr; + } + + Lookup lookup(clasp, realm, protoRoot, objectFlags); + if (!ptr.add(cx, table, lookup, shape)) { + return nullptr; + } + + return shape; +} + +/* static */ +WasmGCShape* WasmGCShape::getShape(JSContext* cx, const JSClass* clasp, + JS::Realm* realm, TaggedProto proto, + const wasm::RecGroup* recGroup, + ObjectFlags objectFlags) { + MOZ_ASSERT(cx->compartment() == realm->compartment()); + MOZ_ASSERT_IF(proto.isObject(), + cx->isInsideCurrentCompartment(proto.toObject())); + + if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) { + RootedObject protoObj(cx, proto.toObject()); + if (!SetObjectIsUsedAsPrototype(cx, protoObj)) { + return nullptr; + } + proto = TaggedProto(protoObj); + } + + auto& table = realm->zone()->shapeZone().wasmGCShapes; + + using Lookup = WasmGCShapeHasher::Lookup; + auto ptr = MakeDependentAddPtr( + cx, table, Lookup(clasp, realm, proto, recGroup, objectFlags)); + if (ptr) { + return *ptr; + } + + Rooted<TaggedProto> protoRoot(cx, proto); + Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot)); + if (!nbase) { + return nullptr; + } + + Rooted<WasmGCShape*> shape( + cx, WasmGCShape::new_(cx, nbase, recGroup, objectFlags)); + if (!shape) { + return nullptr; + } + + Lookup lookup(clasp, realm, protoRoot, recGroup, objectFlags); + if (!ptr.add(cx, table, lookup, shape)) { + return nullptr; + } + + return shape; +} + +JS::ubi::Node::Size JS::ubi::Concrete<js::Shape>::size( + mozilla::MallocSizeOf mallocSizeOf) const { + Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind()); + + if (get().cache().isShapeSetForAdd()) { + ShapeSetForAdd* set = get().cache().toShapeSetForAdd(); + size += set->shallowSizeOfIncludingThis(mallocSizeOf); + } + + return size; +} + +JS::ubi::Node::Size JS::ubi::Concrete<js::BaseShape>::size( + mozilla::MallocSizeOf mallocSizeOf) const { + return js::gc::Arena::thingSize(get().asTenured().getAllocKind()); +} |