Source code

Revision control

Copy as Markdown

Other Tools

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