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/. */
7 #include "vm/Shape-inl.h"
9 #include "mozilla/MathAlgorithms.h"
10 #include "mozilla/PodOperations.h"
12 #include "gc/HashUtil.h"
13 #include "js/friend/WindowProxy.h" // js::IsWindow
14 #include "js/HashTable.h"
15 #include "js/Printer.h" // js::GenericPrinter, js::Fprinter
16 #include "js/UniquePtr.h"
17 #include "vm/JSObject.h"
18 #include "vm/JSONPrinter.h" // js::JSONPrinter
19 #include "vm/ShapeZone.h"
20 #include "vm/Watchtower.h"
22 #include "gc/StableCellHasher-inl.h"
23 #include "vm/JSContext-inl.h"
24 #include "vm/JSObject-inl.h"
25 #include "vm/NativeObject-inl.h"
29 using mozilla::CeilingLog2Size
;
30 using mozilla::PodZero
;
32 using JS::AutoCheckCannotGC
;
35 bool Shape::replaceShape(JSContext
* cx
, HandleObject obj
,
36 ObjectFlags objectFlags
, TaggedProto proto
,
39 switch (obj
->shape()->kind()) {
41 Handle
<NativeObject
*> nobj
= obj
.as
<NativeObject
>();
42 if (nobj
->shape()->propMap()) {
43 Rooted
<BaseShape
*> base(cx
, obj
->shape()->base());
44 if (proto
!= base
->proto()) {
45 Rooted
<TaggedProto
> protoRoot(cx
, proto
);
46 base
= BaseShape::get(cx
, base
->clasp(), base
->realm(), protoRoot
);
51 Rooted
<SharedPropMap
*> map(cx
, nobj
->sharedShape()->propMap());
52 uint32_t mapLength
= nobj
->shape()->propMapLength();
53 newShape
= SharedShape::getPropMapShape(cx
, base
, nfixed
, map
,
54 mapLength
, objectFlags
);
56 newShape
= SharedShape::getInitialShape(
57 cx
, obj
->shape()->getObjectClass(), obj
->shape()->realm(), proto
,
62 case Kind::Dictionary
: {
63 Handle
<NativeObject
*> nobj
= obj
.as
<NativeObject
>();
65 Rooted
<BaseShape
*> base(cx
, nobj
->shape()->base());
66 if (proto
!= base
->proto()) {
67 Rooted
<TaggedProto
> protoRoot(cx
, proto
);
68 base
= BaseShape::get(cx
, nobj
->getClass(), nobj
->realm(), protoRoot
);
74 Rooted
<DictionaryPropMap
*> map(cx
, nobj
->dictionaryShape()->propMap());
75 uint32_t mapLength
= nobj
->shape()->propMapLength();
77 DictionaryShape::new_(cx
, base
, objectFlags
, nfixed
, map
, mapLength
);
81 MOZ_ASSERT(nfixed
== 0);
83 ProxyShape::getShape(cx
, obj
->shape()->getObjectClass(),
84 obj
->shape()->realm(), proto
, objectFlags
);
87 MOZ_ASSERT(nfixed
== 0);
88 const wasm::RecGroup
* recGroup
= obj
->shape()->asWasmGC().recGroup();
89 newShape
= WasmGCShape::getShape(cx
, obj
->shape()->getObjectClass(),
90 obj
->shape()->realm(), proto
, recGroup
,
98 obj
->setShape(newShape
);
103 bool js::NativeObject::toDictionaryMode(JSContext
* cx
,
104 Handle
<NativeObject
*> obj
) {
105 MOZ_ASSERT(!obj
->inDictionaryMode());
106 MOZ_ASSERT(cx
->isInsideCurrentCompartment(obj
));
108 Rooted
<NativeShape
*> shape(cx
, obj
->shape());
109 uint32_t span
= obj
->slotSpan();
111 uint32_t mapLength
= shape
->propMapLength();
112 MOZ_ASSERT(mapLength
> 0, "shouldn't convert empty object to dictionary");
114 // Clone the shared property map to an unshared dictionary map.
115 Rooted
<SharedPropMap
*> map(cx
, shape
->propMap()->asShared());
116 Rooted
<DictionaryPropMap
*> dictMap(
117 cx
, SharedPropMap::toDictionaryMap(cx
, map
, mapLength
));
122 // Allocate and use a new dictionary shape.
123 Rooted
<BaseShape
*> base(cx
, shape
->base());
124 shape
= DictionaryShape::new_(cx
, base
, shape
->objectFlags(),
125 shape
->numFixedSlots(), dictMap
, mapLength
);
129 obj
->setShape(shape
);
131 MOZ_ASSERT(obj
->inDictionaryMode());
132 obj
->setDictionaryModeSlotSpan(span
);
139 class MOZ_RAII AutoCheckShapeConsistency
{
141 Handle
<NativeObject
*> obj_
;
145 explicit AutoCheckShapeConsistency(Handle
<NativeObject
*> obj
)
153 ~AutoCheckShapeConsistency() { obj_
->checkShapeConsistency(); }
159 /* static */ MOZ_ALWAYS_INLINE
bool
160 NativeObject::maybeConvertToDictionaryForAdd(JSContext
* cx
,
161 Handle
<NativeObject
*> obj
) {
162 if (obj
->inDictionaryMode()) {
165 SharedPropMap
* map
= obj
->sharedShape()->propMap();
169 if (MOZ_LIKELY(!map
->shouldConvertToDictionaryForAdd())) {
172 return toDictionaryMode(cx
, obj
);
175 static void AssertValidCustomDataProp(NativeObject
* obj
, PropertyFlags flags
) {
176 // We only support custom data properties on ArrayObject and ArgumentsObject.
177 // The mechanism is deprecated so we don't want to add new uses.
178 MOZ_ASSERT(flags
.isCustomDataProperty());
179 MOZ_ASSERT(!flags
.isAccessorProperty());
180 MOZ_ASSERT(obj
->is
<ArrayObject
>() || obj
->is
<ArgumentsObject
>());
184 bool NativeObject::addCustomDataProperty(JSContext
* cx
,
185 Handle
<NativeObject
*> obj
, HandleId id
,
186 PropertyFlags flags
) {
187 MOZ_ASSERT(!id
.isVoid());
188 MOZ_ASSERT(!id
.isPrivateName());
189 MOZ_ASSERT(!obj
->containsPure(id
));
191 AutoCheckShapeConsistency
check(obj
);
192 AssertValidCustomDataProp(obj
, flags
);
194 if (!Watchtower::watchPropertyAdd(cx
, obj
, id
)) {
198 if (!maybeConvertToDictionaryForAdd(cx
, obj
)) {
202 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
203 const JSClass
* clasp
= obj
->shape()->getObjectClass();
205 if (obj
->inDictionaryMode()) {
206 // First generate a new dictionary shape so that the map can be mutated
207 // without having to worry about OOM conditions.
208 if (!NativeObject::generateNewDictionaryShape(cx
, obj
)) {
212 Rooted
<DictionaryPropMap
*> map(cx
, obj
->dictionaryShape()->propMap());
213 uint32_t mapLength
= obj
->shape()->propMapLength();
214 if (!DictionaryPropMap::addProperty(cx
, clasp
, &map
, &mapLength
, id
, flags
,
215 SHAPE_INVALID_SLOT
, &objectFlags
)) {
219 obj
->dictionaryShape()->updateNewShape(objectFlags
, map
, mapLength
);
223 Rooted
<SharedPropMap
*> map(cx
, obj
->sharedShape()->propMap());
224 uint32_t mapLength
= obj
->shape()->propMapLength();
225 if (!SharedPropMap::addCustomDataProperty(cx
, clasp
, &map
, &mapLength
, id
,
226 flags
, &objectFlags
)) {
230 Shape
* shape
= SharedShape::getPropMapShape(cx
, obj
->shape()->base(),
231 obj
->shape()->numFixedSlots(),
232 map
, mapLength
, objectFlags
);
237 obj
->setShape(shape
);
241 static ShapeSetForAdd
* MakeShapeSetForAdd(SharedShape
* shape1
,
242 SharedShape
* shape2
) {
243 MOZ_ASSERT(shape1
!= shape2
);
244 MOZ_ASSERT(shape1
->propMapLength() == shape2
->propMapLength());
246 auto hash
= MakeUnique
<ShapeSetForAdd
>();
247 if (!hash
|| !hash
->reserve(2)) {
251 PropertyInfoWithKey prop
= shape1
->lastProperty();
252 hash
->putNewInfallible(ShapeForAddHasher::Lookup(prop
.key(), prop
.flags()),
255 prop
= shape2
->lastProperty();
256 hash
->putNewInfallible(ShapeForAddHasher::Lookup(prop
.key(), prop
.flags()),
259 return hash
.release();
262 static MOZ_ALWAYS_INLINE SharedShape
* LookupShapeForAdd(Shape
* shape
,
266 ShapeCachePtr cache
= shape
->cache();
268 if (cache
.isSingleShapeForAdd()) {
269 SharedShape
* newShape
= cache
.toSingleShapeForAdd();
270 if (newShape
->lastPropertyMatchesForAdd(key
, flags
, slot
)) {
276 if (cache
.isShapeSetForAdd()) {
277 ShapeSetForAdd
* set
= cache
.toShapeSetForAdd();
278 ShapeForAddHasher::Lookup
lookup(key
, flags
);
279 if (auto p
= set
->lookup(lookup
)) {
280 SharedShape
* newShape
= *p
;
281 *slot
= newShape
->lastProperty().slot();
287 MOZ_ASSERT(!cache
.isForAdd());
291 // Add shapes with a non-None ShapeCachePtr to the shapesWithCache list so that
292 // these caches can be discarded on GC.
293 static bool RegisterShapeCache(JSContext
* cx
, Shape
* shape
) {
294 ShapeCachePtr cache
= shape
->cache();
295 if (!cache
.isNone()) {
296 // Already registered this shape.
299 return cx
->zone()->shapeZone().shapesWithCache
.append(shape
);
303 bool NativeObject::addProperty(JSContext
* cx
, Handle
<NativeObject
*> obj
,
304 HandleId id
, PropertyFlags flags
,
306 AutoCheckShapeConsistency
check(obj
);
307 MOZ_ASSERT(!flags
.isCustomDataProperty(),
308 "Use addCustomDataProperty for custom data properties");
310 // The object must not contain a property named |id|. The object must be
311 // extensible, but allow private fields and sparsifying dense elements.
312 MOZ_ASSERT(!id
.isVoid());
313 MOZ_ASSERT(!obj
->containsPure(id
));
314 MOZ_ASSERT_IF(!id
.isPrivateName(),
315 obj
->isExtensible() ||
316 (id
.isInt() && obj
->containsDenseElement(id
.toInt())) ||
317 // R&T wrappers are non-extensible, but we still want to be
318 // able to lazily resolve their properties. We can
319 // special-case them to allow doing so.
320 IF_RECORD_TUPLE(IsExtendedPrimitiveWrapper(*obj
), false));
322 if (!Watchtower::watchPropertyAdd(cx
, obj
, id
)) {
326 if (!maybeConvertToDictionaryForAdd(cx
, obj
)) {
330 if (auto* shape
= LookupShapeForAdd(obj
->shape(), id
, flags
, slot
)) {
331 return obj
->setShapeAndAddNewSlot(cx
, shape
, *slot
);
334 if (obj
->inDictionaryMode()) {
335 // First generate a new dictionary shape so that the map and shape can be
336 // mutated without having to worry about OOM conditions.
337 if (!NativeObject::generateNewDictionaryShape(cx
, obj
)) {
340 if (!allocDictionarySlot(cx
, obj
, slot
)) {
344 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
345 const JSClass
* clasp
= obj
->shape()->getObjectClass();
347 Rooted
<DictionaryPropMap
*> map(cx
, obj
->shape()->propMap()->asDictionary());
348 uint32_t mapLength
= obj
->shape()->propMapLength();
349 if (!DictionaryPropMap::addProperty(cx
, clasp
, &map
, &mapLength
, id
, flags
,
350 *slot
, &objectFlags
)) {
354 obj
->dictionaryShape()->updateNewShape(objectFlags
, map
, mapLength
);
358 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
359 const JSClass
* clasp
= obj
->shape()->getObjectClass();
361 Rooted
<SharedPropMap
*> map(cx
, obj
->sharedShape()->propMap());
362 uint32_t mapLength
= obj
->shape()->propMapLength();
364 if (!SharedPropMap::addProperty(cx
, clasp
, &map
, &mapLength
, id
, flags
,
365 &objectFlags
, slot
)) {
369 bool allocatedNewShape
;
370 SharedShape
* newShape
= SharedShape::getPropMapShape(
371 cx
, obj
->shape()->base(), obj
->shape()->numFixedSlots(), map
, mapLength
,
372 objectFlags
, &allocatedNewShape
);
377 Shape
* oldShape
= obj
->shape();
378 if (!obj
->setShapeAndAddNewSlot(cx
, newShape
, *slot
)) {
382 // Add the new shape to the old shape's shape cache, to optimize this shape
383 // transition. Don't do this if we just allocated a new shape, because that
384 // suggests this may not be a hot transition that would benefit from the
387 if (allocatedNewShape
) {
391 if (!RegisterShapeCache(cx
, oldShape
)) {
392 // Ignore OOM, the cache is just an optimization.
396 ShapeCachePtr
& cache
= oldShape
->cacheRef();
397 if (!cache
.isForAdd()) {
398 cache
.setSingleShapeForAdd(newShape
);
399 } else if (cache
.isSingleShapeForAdd()) {
400 SharedShape
* prevShape
= cache
.toSingleShapeForAdd();
401 if (ShapeSetForAdd
* set
= MakeShapeSetForAdd(prevShape
, newShape
)) {
402 cache
.setShapeSetForAdd(set
);
403 AddCellMemory(oldShape
, sizeof(ShapeSetForAdd
),
404 MemoryUse::ShapeSetForAdd
);
407 ShapeForAddHasher::Lookup
lookup(id
, flags
);
408 (void)cache
.toShapeSetForAdd()->putNew(lookup
, newShape
);
414 void Shape::maybeCacheIterator(JSContext
* cx
, PropertyIteratorObject
* iter
) {
415 if (!cache().isNone() && !cache().isIterator()) {
416 // If we're already caching other shape data, skip caching the iterator.
419 if (MOZ_UNLIKELY(!RegisterShapeCache(cx
, this))) {
420 // Ignore OOM. The cache is just an optimization.
423 cacheRef().setIterator(iter
);
427 bool NativeObject::addPropertyInReservedSlot(JSContext
* cx
,
428 Handle
<NativeObject
*> obj
,
429 HandleId id
, uint32_t slot
,
430 PropertyFlags flags
) {
431 AutoCheckShapeConsistency
check(obj
);
432 MOZ_ASSERT(!flags
.isCustomDataProperty(),
433 "Use addCustomDataProperty for custom data properties");
435 // The slot must be a reserved slot.
436 MOZ_ASSERT(slot
< JSCLASS_RESERVED_SLOTS(obj
->getClass()));
438 // The object must not contain a property named |id| and must be extensible.
439 MOZ_ASSERT(!id
.isVoid());
440 MOZ_ASSERT(!obj
->containsPure(id
));
441 MOZ_ASSERT(!id
.isPrivateName());
442 MOZ_ASSERT(obj
->isExtensible());
444 // The object must not be in dictionary mode. This simplifies the code below.
445 MOZ_ASSERT(!obj
->inDictionaryMode());
447 // We don't need to call Watchtower::watchPropertyAdd here because this isn't
448 // used for any watched objects.
449 MOZ_ASSERT(!Watchtower::watchesPropertyAdd(obj
));
451 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
452 const JSClass
* clasp
= obj
->shape()->getObjectClass();
454 Rooted
<SharedPropMap
*> map(cx
, obj
->sharedShape()->propMap());
455 uint32_t mapLength
= obj
->shape()->propMapLength();
456 if (!SharedPropMap::addPropertyInReservedSlot(cx
, clasp
, &map
, &mapLength
, id
,
457 flags
, slot
, &objectFlags
)) {
461 Shape
* shape
= SharedShape::getPropMapShape(cx
, obj
->shape()->base(),
462 obj
->shape()->numFixedSlots(),
463 map
, mapLength
, objectFlags
);
467 obj
->setShape(shape
);
469 MOZ_ASSERT(obj
->getLastProperty().slot() == slot
);
474 * Assert some invariants that should hold when changing properties. It's the
475 * responsibility of the callers to ensure these hold.
477 static void AssertCanChangeFlags(PropertyInfo prop
, PropertyFlags flags
) {
479 if (prop
.configurable()) {
483 // A non-configurable property must stay non-configurable.
484 MOZ_ASSERT(!flags
.configurable());
486 // Reject attempts to turn a non-configurable data property into an accessor
487 // or custom data property.
488 MOZ_ASSERT_IF(prop
.isDataProperty(), flags
.isDataProperty());
490 // Reject attempts to turn a non-configurable accessor property into a data
491 // property or custom data property.
492 MOZ_ASSERT_IF(prop
.isAccessorProperty(), flags
.isAccessorProperty());
496 static void AssertValidArrayIndex(NativeObject
* obj
, jsid id
) {
498 if (obj
->is
<ArrayObject
>()) {
499 ArrayObject
* arr
= &obj
->as
<ArrayObject
>();
501 if (IdIsIndex(id
, &index
)) {
502 MOZ_ASSERT(index
< arr
->length() || arr
->lengthIsWritable());
509 bool NativeObject::changeProperty(JSContext
* cx
, Handle
<NativeObject
*> obj
,
510 HandleId id
, PropertyFlags flags
,
512 MOZ_ASSERT(!id
.isVoid());
514 AutoCheckShapeConsistency
check(obj
);
515 AssertValidArrayIndex(obj
, id
);
516 MOZ_ASSERT(!flags
.isCustomDataProperty(),
517 "Use changeCustomDataPropAttributes for custom data properties");
519 if (!Watchtower::watchPropertyChange(cx
, obj
, id
, flags
)) {
523 Rooted
<PropMap
*> map(cx
, obj
->shape()->propMap());
524 uint32_t mapLength
= obj
->shape()->propMapLength();
527 Rooted
<PropMap
*> propMap(cx
, map
->lookup(cx
, mapLength
, id
, &propIndex
));
530 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
532 PropertyInfo oldProp
= propMap
->getPropertyInfo(propIndex
);
533 AssertCanChangeFlags(oldProp
, flags
);
535 if (oldProp
.isAccessorProperty()) {
536 objectFlags
.setFlag(ObjectFlag::HadGetterSetterChange
);
539 // If the property flags are not changing, the only thing we have to do is
540 // update the object flags. This prevents a dictionary mode conversion below.
541 if (oldProp
.flags() == flags
) {
542 *slotOut
= oldProp
.slot();
543 if (objectFlags
== obj
->shape()->objectFlags()) {
546 return Shape::replaceShape(cx
, obj
, objectFlags
, obj
->shape()->proto(),
547 obj
->shape()->numFixedSlots());
550 const JSClass
* clasp
= obj
->shape()->getObjectClass();
552 if (map
->isShared()) {
553 // Fast path for changing the last property in a SharedPropMap. Call
554 // getPrevious to "remove" the last property and then call addProperty
555 // to re-add the last property with the new flags.
556 if (propMap
== map
&& propIndex
== mapLength
- 1) {
557 MOZ_ASSERT(obj
->getLastProperty().key() == id
);
559 Rooted
<SharedPropMap
*> sharedMap(cx
, map
->asShared());
560 SharedPropMap::getPrevious(&sharedMap
, &mapLength
);
562 if (MOZ_LIKELY(oldProp
.hasSlot())) {
563 *slotOut
= oldProp
.slot();
564 if (!SharedPropMap::addPropertyWithKnownSlot(cx
, clasp
, &sharedMap
,
565 &mapLength
, id
, flags
,
566 *slotOut
, &objectFlags
)) {
570 if (!SharedPropMap::addProperty(cx
, clasp
, &sharedMap
, &mapLength
, id
,
571 flags
, &objectFlags
, slotOut
)) {
576 SharedShape
* newShape
= SharedShape::getPropMapShape(
577 cx
, obj
->shape()->base(), obj
->shape()->numFixedSlots(), sharedMap
,
578 mapLength
, objectFlags
);
583 if (MOZ_LIKELY(oldProp
.hasSlot())) {
584 MOZ_ASSERT(obj
->sharedShape()->slotSpan() == newShape
->slotSpan());
585 obj
->setShape(newShape
);
588 return obj
->setShapeAndAddNewSlot(cx
, newShape
, *slotOut
);
591 // Changing a non-last property. Switch to dictionary mode and relookup
592 // pointers for the new dictionary map.
593 if (!NativeObject::toDictionaryMode(cx
, obj
)) {
596 map
= obj
->shape()->propMap();
597 propMap
= map
->lookup(cx
, mapLength
, id
, &propIndex
);
600 if (!NativeObject::generateNewDictionaryShape(cx
, obj
)) {
605 // The object has a new dictionary shape (see toDictionaryMode and
606 // generateNewDictionaryShape calls above), so we can mutate the map and shape
609 MOZ_ASSERT(map
->isDictionary());
610 MOZ_ASSERT(propMap
->isDictionary());
612 uint32_t slot
= oldProp
.hasSlot() ? oldProp
.slot() : SHAPE_INVALID_SLOT
;
613 if (slot
== SHAPE_INVALID_SLOT
) {
614 if (!allocDictionarySlot(cx
, obj
, &slot
)) {
619 propMap
->asDictionary()->changeProperty(cx
, clasp
, propIndex
, flags
, slot
,
621 obj
->dictionaryShape()->setObjectFlagsOfNewShape(objectFlags
);
628 bool NativeObject::changeCustomDataPropAttributes(JSContext
* cx
,
629 Handle
<NativeObject
*> obj
,
631 PropertyFlags flags
) {
632 MOZ_ASSERT(!id
.isVoid());
634 AutoCheckShapeConsistency
check(obj
);
635 AssertValidArrayIndex(obj
, id
);
636 AssertValidCustomDataProp(obj
, flags
);
638 if (!Watchtower::watchPropertyChange(cx
, obj
, id
, flags
)) {
642 Rooted
<PropMap
*> map(cx
, obj
->shape()->propMap());
643 uint32_t mapLength
= obj
->shape()->propMapLength();
646 Rooted
<PropMap
*> propMap(cx
, map
->lookup(cx
, mapLength
, id
, &propIndex
));
649 PropertyInfo oldProp
= propMap
->getPropertyInfo(propIndex
);
650 MOZ_ASSERT(oldProp
.isCustomDataProperty());
651 AssertCanChangeFlags(oldProp
, flags
);
653 // If the property flags are not changing, we're done.
654 if (oldProp
.flags() == flags
) {
658 const JSClass
* clasp
= obj
->shape()->getObjectClass();
659 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
661 if (map
->isShared()) {
662 // Fast path for changing the last property in a SharedPropMap. Call
663 // getPrevious to "remove" the last property and then call
664 // addCustomDataProperty to re-add the last property with the new flags.
665 if (propMap
== map
&& propIndex
== mapLength
- 1) {
666 MOZ_ASSERT(obj
->getLastProperty().key() == id
);
668 Rooted
<SharedPropMap
*> sharedMap(cx
, map
->asShared());
669 SharedPropMap::getPrevious(&sharedMap
, &mapLength
);
671 if (!SharedPropMap::addCustomDataProperty(
672 cx
, clasp
, &sharedMap
, &mapLength
, id
, flags
, &objectFlags
)) {
676 Shape
* newShape
= SharedShape::getPropMapShape(
677 cx
, obj
->shape()->base(), obj
->shape()->numFixedSlots(), sharedMap
,
678 mapLength
, objectFlags
);
682 obj
->setShape(newShape
);
686 // Changing a non-last property. Switch to dictionary mode and relookup
687 // pointers for the new dictionary map.
688 if (!NativeObject::toDictionaryMode(cx
, obj
)) {
691 map
= obj
->shape()->propMap();
692 propMap
= map
->lookup(cx
, mapLength
, id
, &propIndex
);
695 if (!NativeObject::generateNewDictionaryShape(cx
, obj
)) {
700 // The object has a new dictionary shape (see toDictionaryMode and
701 // generateNewDictionaryShape calls above), so we can mutate the map and shape
704 MOZ_ASSERT(map
->isDictionary());
705 MOZ_ASSERT(propMap
->isDictionary());
707 propMap
->asDictionary()->changePropertyFlags(cx
, clasp
, propIndex
, flags
,
709 obj
->dictionaryShape()->setObjectFlagsOfNewShape(objectFlags
);
713 void NativeObject::maybeFreeDictionaryPropSlots(JSContext
* cx
,
714 DictionaryPropMap
* map
,
715 uint32_t mapLength
) {
716 // We can free all non-reserved slots if there are no properties left. We also
717 // handle the case where there's a single slotless property, to support arrays
718 // (array.length is a custom data property).
720 MOZ_ASSERT(dictionaryShape()->propMap() == map
);
721 MOZ_ASSERT(shape()->propMapLength() == mapLength
);
723 if (mapLength
> 1 || map
->previous()) {
726 if (mapLength
== 1 && map
->getPropertyInfo(0).hasSlot()) {
730 uint32_t oldSpan
= dictionaryModeSlotSpan();
731 uint32_t newSpan
= JSCLASS_RESERVED_SLOTS(getClass());
732 if (oldSpan
== newSpan
) {
736 MOZ_ASSERT(newSpan
< oldSpan
);
738 // Trigger write barriers on the old slots before reallocating.
739 prepareSlotRangeForOverwrite(newSpan
, oldSpan
);
740 invalidateSlotRange(newSpan
, oldSpan
);
742 uint32_t oldCapacity
= numDynamicSlots();
743 uint32_t newCapacity
=
744 calculateDynamicSlots(numFixedSlots(), newSpan
, getClass());
745 if (newCapacity
< oldCapacity
) {
746 shrinkSlots(cx
, oldCapacity
, newCapacity
);
749 setDictionaryModeSlotSpan(newSpan
);
750 map
->setFreeList(SHAPE_INVALID_SLOT
);
753 void NativeObject::setShapeAndRemoveLastSlot(JSContext
* cx
,
754 SharedShape
* newShape
,
756 MOZ_ASSERT(!inDictionaryMode());
757 MOZ_ASSERT(newShape
->isShared());
758 MOZ_ASSERT(newShape
->slotSpan() == slot
);
760 uint32_t numFixed
= newShape
->numFixedSlots();
761 if (slot
< numFixed
) {
762 setFixedSlot(slot
, UndefinedValue());
764 setDynamicSlot(numFixed
, slot
, UndefinedValue());
765 uint32_t oldCapacity
= numDynamicSlots();
766 uint32_t newCapacity
= calculateDynamicSlots(numFixed
, slot
, getClass());
767 MOZ_ASSERT(newCapacity
<= oldCapacity
);
768 if (newCapacity
< oldCapacity
) {
769 shrinkSlots(cx
, oldCapacity
, newCapacity
);
777 bool NativeObject::removeProperty(JSContext
* cx
, Handle
<NativeObject
*> obj
,
779 AutoCheckShapeConsistency
check(obj
);
781 Rooted
<PropMap
*> map(cx
, obj
->shape()->propMap());
782 uint32_t mapLength
= obj
->shape()->propMapLength();
784 AutoKeepPropMapTables
keep(cx
);
786 PropMapTable::Ptr ptr
;
787 Rooted
<PropMap
*> propMap(cx
);
789 if (!PropMap::lookupForRemove(cx
, map
, mapLength
, id
, keep
, propMap
.address(),
790 &propIndex
, &table
, &ptr
)) {
798 if (!Watchtower::watchPropertyRemove(cx
, obj
, id
)) {
802 PropertyInfo prop
= propMap
->getPropertyInfo(propIndex
);
804 // If we're removing an accessor property, ensure the HadGetterSetterChange
805 // object flag is set. This is necessary because the slot holding the
806 // GetterSetter can be changed indirectly by removing the property and then
807 // adding it back with a different GetterSetter value but the same shape.
808 if (prop
.isAccessorProperty() && !obj
->hadGetterSetterChange()) {
809 if (!NativeObject::setHadGetterSetterChange(cx
, obj
)) {
814 if (map
->isShared()) {
815 // Fast path for removing the last property from a SharedPropMap. In this
816 // case we can just call getPrevious and then look up a shape for the
817 // resulting map/mapLength.
818 if (propMap
== map
&& propIndex
== mapLength
- 1) {
819 MOZ_ASSERT(obj
->getLastProperty().key() == id
);
821 Rooted
<SharedPropMap
*> sharedMap(cx
, map
->asShared());
822 SharedPropMap::getPrevious(&sharedMap
, &mapLength
);
824 SharedShape
* shape
= obj
->sharedShape();
825 SharedShape
* newShape
;
827 newShape
= SharedShape::getPropMapShape(
828 cx
, shape
->base(), shape
->numFixedSlots(), sharedMap
, mapLength
,
829 shape
->objectFlags());
831 newShape
= SharedShape::getInitialShape(
832 cx
, shape
->getObjectClass(), shape
->realm(), shape
->proto(),
833 shape
->numFixedSlots(), shape
->objectFlags());
839 if (MOZ_LIKELY(prop
.hasSlot())) {
840 if (MOZ_LIKELY(prop
.slot() == newShape
->slotSpan())) {
841 obj
->setShapeAndRemoveLastSlot(cx
, newShape
, prop
.slot());
844 // Uncommon case: the property is stored in a reserved slot.
845 // See NativeObject::addPropertyInReservedSlot.
846 MOZ_ASSERT(prop
.slot() < JSCLASS_RESERVED_SLOTS(obj
->getClass()));
847 obj
->setSlot(prop
.slot(), UndefinedValue());
849 obj
->setShape(newShape
);
853 // Removing a non-last property. Switch to dictionary mode and relookup
854 // pointers for the new dictionary map.
855 if (!NativeObject::toDictionaryMode(cx
, obj
)) {
858 map
= obj
->shape()->propMap();
859 if (!PropMap::lookupForRemove(cx
, map
, mapLength
, id
, keep
,
860 propMap
.address(), &propIndex
, &table
,
865 if (!NativeObject::generateNewDictionaryShape(cx
, obj
)) {
870 // The object has a new dictionary shape (see toDictionaryMode and
871 // generateNewDictionaryShape calls above), so we can mutate the map and shape
874 MOZ_ASSERT(map
->isDictionary());
876 MOZ_ASSERT(prop
== ptr
->propertyInfo());
878 Rooted
<DictionaryPropMap
*> dictMap(cx
, map
->asDictionary());
880 // If the property has a slot, free its slot number.
881 if (prop
.hasSlot()) {
882 obj
->freeDictionarySlot(prop
.slot());
885 DictionaryPropMap::removeProperty(cx
, &dictMap
, &mapLength
, table
, ptr
);
887 obj
->dictionaryShape()->updateNewShape(obj
->shape()->objectFlags(), dictMap
,
890 // If we just deleted the last property, consider shrinking the slots. We only
891 // do this if there are a lot of slots, to avoid allocating/freeing dynamic
893 static constexpr size_t MinSlotSpanForFree
= 64;
894 if (obj
->dictionaryModeSlotSpan() >= MinSlotSpanForFree
) {
895 obj
->maybeFreeDictionaryPropSlots(cx
, dictMap
, mapLength
);
902 bool NativeObject::densifySparseElements(JSContext
* cx
,
903 Handle
<NativeObject
*> obj
) {
904 AutoCheckShapeConsistency
check(obj
);
905 MOZ_ASSERT(obj
->inDictionaryMode());
907 // First generate a new dictionary shape so that the shape and map can then
908 // be updated infallibly.
909 if (!NativeObject::generateNewDictionaryShape(cx
, obj
)) {
913 Rooted
<DictionaryPropMap
*> map(cx
, obj
->shape()->propMap()->asDictionary());
914 uint32_t mapLength
= obj
->shape()->propMapLength();
916 DictionaryPropMap::densifyElements(cx
, &map
, &mapLength
, obj
);
918 // All indexed properties on the object are now dense. Clear the indexed
919 // flag so that we will not start using sparse indexes again if we need
920 // to grow the object.
921 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
922 objectFlags
.clearFlag(ObjectFlag::Indexed
);
924 obj
->dictionaryShape()->updateNewShape(objectFlags
, map
, mapLength
);
926 obj
->maybeFreeDictionaryPropSlots(cx
, map
, mapLength
);
932 bool NativeObject::freezeOrSealProperties(JSContext
* cx
,
933 Handle
<NativeObject
*> obj
,
934 IntegrityLevel level
) {
935 AutoCheckShapeConsistency
check(obj
);
937 if (!Watchtower::watchFreezeOrSeal(cx
, obj
)) {
941 uint32_t mapLength
= obj
->shape()->propMapLength();
942 MOZ_ASSERT(mapLength
> 0);
944 const JSClass
* clasp
= obj
->shape()->getObjectClass();
945 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
947 if (obj
->inDictionaryMode()) {
948 // First generate a new dictionary shape so that the map and shape can be
949 // updated infallibly.
950 if (!generateNewDictionaryShape(cx
, obj
)) {
953 DictionaryPropMap
* map
= obj
->dictionaryShape()->propMap();
954 map
->freezeOrSealProperties(cx
, level
, clasp
, mapLength
, &objectFlags
);
955 obj
->dictionaryShape()->updateNewShape(objectFlags
, map
, mapLength
);
959 Rooted
<SharedPropMap
*> map(cx
, obj
->sharedShape()->propMap());
960 if (!SharedPropMap::freezeOrSealProperties(cx
, level
, clasp
, &map
, mapLength
,
965 SharedShape
* newShape
= SharedShape::getPropMapShape(
966 cx
, obj
->shape()->base(), obj
->numFixedSlots(), map
, mapLength
,
971 MOZ_ASSERT(obj
->sharedShape()->slotSpan() == newShape
->slotSpan());
973 obj
->setShape(newShape
);
978 bool NativeObject::generateNewDictionaryShape(JSContext
* cx
,
979 Handle
<NativeObject
*> obj
) {
980 // Clone the current dictionary shape to a new shape. This ensures ICs and
981 // other shape guards are properly invalidated before we start mutating the
984 MOZ_ASSERT(obj
->inDictionaryMode());
986 Shape
* shape
= DictionaryShape::new_(cx
, obj
);
991 obj
->setShape(shape
);
996 bool JSObject::setFlag(JSContext
* cx
, HandleObject obj
, ObjectFlag flag
) {
997 MOZ_ASSERT(cx
->compartment() == obj
->compartment());
999 if (obj
->hasFlag(flag
)) {
1003 ObjectFlags objectFlags
= obj
->shape()->objectFlags();
1004 objectFlags
.setFlag(flag
);
1007 obj
->is
<NativeObject
>() ? obj
->as
<NativeObject
>().numFixedSlots() : 0;
1008 return Shape::replaceShape(cx
, obj
, objectFlags
, obj
->shape()->proto(),
1012 static bool SetObjectIsUsedAsPrototype(JSContext
* cx
, Handle
<JSObject
*> proto
) {
1013 MOZ_ASSERT(!proto
->isUsedAsPrototype());
1015 // Ensure the proto object has a unique id to prevent OOM crashes below.
1017 if (!gc::GetOrCreateUniqueId(proto
, &unused
)) {
1018 ReportOutOfMemory(cx
);
1022 return JSObject::setIsUsedAsPrototype(cx
, proto
);
1026 bool JSObject::setProtoUnchecked(JSContext
* cx
, HandleObject obj
,
1027 Handle
<TaggedProto
> proto
) {
1028 MOZ_ASSERT(cx
->compartment() == obj
->compartment());
1029 MOZ_ASSERT(!obj
->staticPrototypeIsImmutable());
1030 MOZ_ASSERT_IF(!obj
->is
<ProxyObject
>(), obj
->nonProxyIsExtensible());
1031 MOZ_ASSERT(obj
->shape()->proto() != proto
);
1033 // Notify Watchtower of this proto change, so it can properly invalidate shape
1034 // teleporting and other optimizations.
1035 if (!Watchtower::watchProtoChange(cx
, obj
)) {
1039 if (proto
.isObject() && !proto
.toObject()->isUsedAsPrototype()) {
1040 RootedObject
protoObj(cx
, proto
.toObject());
1041 if (!SetObjectIsUsedAsPrototype(cx
, protoObj
)) {
1047 obj
->is
<NativeObject
>() ? obj
->as
<NativeObject
>().numFixedSlots() : 0;
1048 return Shape::replaceShape(cx
, obj
, obj
->shape()->objectFlags(), proto
,
1053 bool NativeObject::changeNumFixedSlotsAfterSwap(JSContext
* cx
,
1054 Handle
<NativeObject
*> obj
,
1056 MOZ_ASSERT(nfixed
!= obj
->shape()->numFixedSlots());
1058 return Shape::replaceShape(cx
, obj
, obj
->shape()->objectFlags(),
1059 obj
->shape()->proto(), nfixed
);
1062 BaseShape::BaseShape(JSContext
* cx
, const JSClass
* clasp
, JS::Realm
* realm
,
1064 : TenuredCellWithNonGCPointer(clasp
), realm_(realm
), proto_(proto
) {
1066 AssertJSClassInvariants(clasp
);
1069 MOZ_ASSERT_IF(proto
.isObject(),
1070 compartment() == proto
.toObject()->compartment());
1071 MOZ_ASSERT_IF(proto
.isObject(), proto
.toObject()->isUsedAsPrototype());
1073 // Windows may not appear on prototype chains.
1074 MOZ_ASSERT_IF(proto
.isObject(), !IsWindow(proto
.toObject()));
1076 if (MOZ_UNLIKELY(clasp
->emulatesUndefined())) {
1077 cx
->runtime()->hasSeenObjectEmulateUndefinedFuse
.ref().popFuse(cx
);
1081 if (GlobalObject
* global
= realm
->unsafeUnbarrieredMaybeGlobal()) {
1082 AssertTargetIsNotGray(global
);
1088 BaseShape
* BaseShape::get(JSContext
* cx
, const JSClass
* clasp
, JS::Realm
* realm
,
1089 Handle
<TaggedProto
> proto
) {
1090 auto& table
= cx
->zone()->shapeZone().baseShapes
;
1092 using Lookup
= BaseShapeHasher::Lookup
;
1094 auto p
= MakeDependentAddPtr(cx
, table
, Lookup(clasp
, realm
, proto
));
1099 BaseShape
* nbase
= cx
->newCell
<BaseShape
>(cx
, clasp
, realm
, proto
);
1104 if (!p
.add(cx
, table
, Lookup(clasp
, realm
, proto
), nbase
)) {
1112 SharedShape
* SharedShape::new_(JSContext
* cx
, Handle
<BaseShape
*> base
,
1113 ObjectFlags objectFlags
, uint32_t nfixed
,
1114 Handle
<SharedPropMap
*> map
, uint32_t mapLength
) {
1115 return cx
->newCell
<SharedShape
>(base
, objectFlags
, nfixed
, map
, mapLength
);
1119 DictionaryShape
* DictionaryShape::new_(JSContext
* cx
, Handle
<BaseShape
*> base
,
1120 ObjectFlags objectFlags
, uint32_t nfixed
,
1121 Handle
<DictionaryPropMap
*> map
,
1122 uint32_t mapLength
) {
1123 return cx
->newCell
<DictionaryShape
>(base
, objectFlags
, nfixed
, map
,
1127 DictionaryShape::DictionaryShape(NativeObject
* nobj
)
1128 : DictionaryShape(nobj
->shape()->base(), nobj
->shape()->objectFlags(),
1129 nobj
->shape()->numFixedSlots(),
1130 nobj
->dictionaryShape()->propMap(),
1131 nobj
->shape()->propMapLength()) {}
1134 DictionaryShape
* DictionaryShape::new_(JSContext
* cx
,
1135 Handle
<NativeObject
*> obj
) {
1136 return cx
->newCell
<DictionaryShape
>(obj
);
1140 ProxyShape
* ProxyShape::new_(JSContext
* cx
, Handle
<BaseShape
*> base
,
1141 ObjectFlags objectFlags
) {
1142 return cx
->newCell
<ProxyShape
>(base
, objectFlags
);
1146 WasmGCShape
* WasmGCShape::new_(JSContext
* cx
, Handle
<BaseShape
*> base
,
1147 const wasm::RecGroup
* recGroup
,
1148 ObjectFlags objectFlags
) {
1149 WasmGCShape
* shape
= cx
->newCell
<WasmGCShape
>(base
, recGroup
, objectFlags
);
1156 MOZ_ALWAYS_INLINE HashNumber
ShapeForAddHasher::hash(const Lookup
& l
) {
1157 HashNumber hash
= HashPropertyKey(l
.key
);
1158 return mozilla::AddToHash(hash
, l
.flags
.toRaw());
1161 MOZ_ALWAYS_INLINE
bool ShapeForAddHasher::match(SharedShape
* shape
,
1164 return shape
->lastPropertyMatchesForAdd(l
.key
, l
.flags
, &slot
);
1167 #if defined(DEBUG) || defined(JS_JITSPEW)
1168 void BaseShape::dump() const {
1169 Fprinter
out(stderr
);
1173 void BaseShape::dump(js::GenericPrinter
& out
) const {
1174 js::JSONPrinter
json(out
);
1179 void BaseShape::dump(js::JSONPrinter
& json
) const {
1185 void BaseShape::dumpFields(js::JSONPrinter
& json
) const {
1186 json
.formatProperty("address", "(js::BaseShape*)0x%p", this);
1188 json
.formatProperty("realm", "(JS::Realm*)0x%p", realm());
1190 if (proto().isDynamic()) {
1191 json
.property("proto", "<dynamic>");
1193 JSObject
* protoObj
= proto().toObjectOrNull();
1195 json
.formatProperty("proto", "(JSObject*)0x%p", protoObj
);
1197 json
.nullProperty("proto");
1202 void Shape::dump() const {
1203 Fprinter
out(stderr
);
1207 void Shape::dump(js::GenericPrinter
& out
) const {
1208 js::JSONPrinter
json(out
);
1213 void Shape::dump(js::JSONPrinter
& json
) const {
1219 template <typename KnownF
, typename UnknownF
>
1220 void ForEachObjectFlag(ObjectFlags flags
, KnownF known
, UnknownF unknown
) {
1221 uint16_t raw
= flags
.toRaw();
1222 for (uint16_t i
= 1; i
; i
= i
<< 1) {
1226 switch (ObjectFlag(raw
& i
)) {
1227 case ObjectFlag::IsUsedAsPrototype
:
1228 known("IsUsedAsPrototype");
1230 case ObjectFlag::NotExtensible
:
1231 known("NotExtensible");
1233 case ObjectFlag::Indexed
:
1236 case ObjectFlag::HasInterestingSymbol
:
1237 known("HasInterestingSymbol");
1239 case ObjectFlag::HasEnumerable
:
1240 known("HasEnumerable");
1242 case ObjectFlag::FrozenElements
:
1243 known("FrozenElements");
1245 case ObjectFlag::InvalidatedTeleporting
:
1246 known("InvalidatedTeleporting");
1248 case ObjectFlag::ImmutablePrototype
:
1249 known("ImmutablePrototype");
1251 case ObjectFlag::QualifiedVarObj
:
1252 known("QualifiedVarObj");
1254 case ObjectFlag::HasNonWritableOrAccessorPropExclProto
:
1255 known("HasNonWritableOrAccessorPropExclProto");
1257 case ObjectFlag::HadGetterSetterChange
:
1258 known("HadGetterSetterChange");
1260 case ObjectFlag::UseWatchtowerTestingLog
:
1261 known("UseWatchtowerTestingLog");
1263 case ObjectFlag::GenerationCountedGlobal
:
1264 known("GenerationCountedGlobal");
1266 case ObjectFlag::NeedsProxyGetSetResultValidation
:
1267 known("NeedsProxyGetSetResultValidation");
1269 case ObjectFlag::HasFuseProperty
:
1270 known("HasFuseProperty");
1279 void Shape::dumpFields(js::JSONPrinter
& json
) const {
1280 json
.formatProperty("address", "(js::Shape*)0x%p", this);
1282 json
.beginObjectProperty("base");
1283 base()->dumpFields(json
);
1288 json
.property("kind", "Shared");
1290 case Kind::Dictionary
:
1291 json
.property("kind", "Dictionary");
1294 json
.property("kind", "Proxy");
1297 json
.property("kind", "WasmGC");
1301 json
.beginInlineListProperty("objectFlags");
1303 objectFlags(), [&](const char* name
) { json
.value("%s", name
); },
1304 [&](uint16_t value
) { json
.value("Unknown(%04x)", value
); });
1305 json
.endInlineList();
1308 json
.property("numFixedSlots", asNative().numFixedSlots());
1309 json
.property("propMapLength", asNative().propMapLength());
1311 if (asNative().propMap()) {
1312 json
.beginObjectProperty("propMap");
1313 asNative().propMap()->dumpFields(json
);
1316 json
.nullProperty("propMap");
1321 if (getObjectClass()->isNativeObject()) {
1322 json
.property("slotSpan", asShared().slotSpan());
1327 json
.formatProperty("recGroup", "(js::wasm::RecGroup*)0x%p",
1328 asWasmGC().recGroup());
1332 void Shape::dumpStringContent(js::GenericPrinter
& out
) const {
1333 out
.printf("<(js::Shape*)0x%p", this);
1335 if (isDictionary()) {
1336 out
.put(", dictionary");
1339 out
.put(", objectFlags=[");
1343 [&](const char* name
) {
1351 [&](uint16_t value
) {
1357 out
.printf("Unknown(%04x)", value
);
1361 #endif // defined(DEBUG) || defined(JS_JITSPEW)
1364 SharedShape
* SharedShape::getInitialShape(JSContext
* cx
, const JSClass
* clasp
,
1365 JS::Realm
* realm
, TaggedProto proto
,
1367 ObjectFlags objectFlags
) {
1368 MOZ_ASSERT(cx
->compartment() == realm
->compartment());
1369 MOZ_ASSERT_IF(proto
.isObject(),
1370 cx
->isInsideCurrentCompartment(proto
.toObject()));
1372 if (proto
.isObject()) {
1373 if (proto
.toObject()->isUsedAsPrototype()) {
1374 // Use the cache on the prototype's shape to get to the initial shape.
1375 // This cache has a hit rate of 80-90% on typical workloads and is faster
1376 // than the HashSet lookup below.
1377 JSObject
* protoObj
= proto
.toObject();
1378 Shape
* protoObjShape
= protoObj
->shape();
1379 if (protoObjShape
->cache().isShapeWithProto()) {
1380 SharedShape
* shape
= protoObjShape
->cache().toShapeWithProto();
1381 if (shape
->numFixedSlots() == nfixed
&&
1382 shape
->objectFlags() == objectFlags
&&
1383 shape
->getObjectClass() == clasp
&& shape
->realm() == realm
&&
1384 shape
->proto() == proto
) {
1386 // Verify the table lookup below would have resulted in the same
1388 using Lookup
= InitialShapeHasher::Lookup
;
1389 Lookup
lookup(clasp
, realm
, proto
, nfixed
, objectFlags
);
1390 auto p
= realm
->zone()->shapeZone().initialShapes
.lookup(lookup
);
1391 MOZ_ASSERT(*p
== shape
);
1397 RootedObject
protoObj(cx
, proto
.toObject());
1398 if (!SetObjectIsUsedAsPrototype(cx
, protoObj
)) {
1401 proto
= TaggedProto(protoObj
);
1405 auto& table
= realm
->zone()->shapeZone().initialShapes
;
1407 using Lookup
= InitialShapeHasher::Lookup
;
1408 auto ptr
= MakeDependentAddPtr(
1409 cx
, table
, Lookup(clasp
, realm
, proto
, nfixed
, objectFlags
));
1411 // Cache the result of this lookup on the prototype's shape.
1412 if (proto
.isObject()) {
1413 JSObject
* protoObj
= proto
.toObject();
1414 Shape
* protoShape
= protoObj
->shape();
1415 if (!protoShape
->cache().isForAdd() &&
1416 RegisterShapeCache(cx
, protoShape
)) {
1417 protoShape
->cacheRef().setShapeWithProto(*ptr
);
1423 Rooted
<TaggedProto
> protoRoot(cx
, proto
);
1424 Rooted
<BaseShape
*> nbase(cx
, BaseShape::get(cx
, clasp
, realm
, protoRoot
));
1429 Rooted
<SharedShape
*> shape(
1430 cx
, SharedShape::new_(cx
, nbase
, objectFlags
, nfixed
, nullptr, 0));
1435 Lookup
lookup(clasp
, realm
, protoRoot
, nfixed
, objectFlags
);
1436 if (!ptr
.add(cx
, table
, lookup
, shape
)) {
1444 SharedShape
* SharedShape::getInitialShape(JSContext
* cx
, const JSClass
* clasp
,
1445 JS::Realm
* realm
, TaggedProto proto
,
1447 ObjectFlags objectFlags
) {
1448 return getInitialShape(cx
, clasp
, realm
, proto
, GetGCKindSlots(kind
),
1453 SharedShape
* SharedShape::getPropMapShape(
1454 JSContext
* cx
, BaseShape
* base
, size_t nfixed
, Handle
<SharedPropMap
*> map
,
1455 uint32_t mapLength
, ObjectFlags objectFlags
, bool* allocatedNewShape
) {
1456 MOZ_ASSERT(cx
->compartment() == base
->compartment());
1457 MOZ_ASSERT_IF(base
->proto().isObject(),
1458 cx
->isInsideCurrentCompartment(base
->proto().toObject()));
1459 MOZ_ASSERT_IF(base
->proto().isObject(),
1460 base
->proto().toObject()->isUsedAsPrototype());
1462 MOZ_ASSERT(mapLength
> 0);
1464 auto& table
= cx
->zone()->shapeZone().propMapShapes
;
1466 using Lookup
= PropMapShapeHasher::Lookup
;
1467 auto ptr
= MakeDependentAddPtr(
1468 cx
, table
, Lookup(base
, nfixed
, map
, mapLength
, objectFlags
));
1470 if (allocatedNewShape
) {
1471 *allocatedNewShape
= false;
1476 Rooted
<BaseShape
*> baseRoot(cx
, base
);
1477 Rooted
<SharedShape
*> shape(
1478 cx
, SharedShape::new_(cx
, baseRoot
, objectFlags
, nfixed
, map
, mapLength
));
1483 Lookup
lookup(baseRoot
, nfixed
, map
, mapLength
, objectFlags
);
1484 if (!ptr
.add(cx
, table
, lookup
, shape
)) {
1488 if (allocatedNewShape
) {
1489 *allocatedNewShape
= true;
1496 SharedShape
* SharedShape::getInitialOrPropMapShape(
1497 JSContext
* cx
, const JSClass
* clasp
, JS::Realm
* realm
, TaggedProto proto
,
1498 size_t nfixed
, Handle
<SharedPropMap
*> map
, uint32_t mapLength
,
1499 ObjectFlags objectFlags
) {
1501 MOZ_ASSERT(mapLength
== 0);
1502 return getInitialShape(cx
, clasp
, realm
, proto
, nfixed
, objectFlags
);
1505 Rooted
<TaggedProto
> protoRoot(cx
, proto
);
1506 BaseShape
* nbase
= BaseShape::get(cx
, clasp
, realm
, protoRoot
);
1511 return getPropMapShape(cx
, nbase
, nfixed
, map
, mapLength
, objectFlags
);
1515 void SharedShape::insertInitialShape(JSContext
* cx
,
1516 Handle
<SharedShape
*> shape
) {
1517 using Lookup
= InitialShapeHasher::Lookup
;
1518 Lookup
lookup(shape
->getObjectClass(), shape
->realm(), shape
->proto(),
1519 shape
->numFixedSlots(), shape
->objectFlags());
1521 auto& table
= cx
->zone()->shapeZone().initialShapes
;
1522 InitialShapeSet::Ptr p
= table
.lookup(lookup
);
1525 // The metadata callback can end up causing redundant changes of the initial
1527 SharedShape
* initialShape
= *p
;
1528 if (initialShape
== shape
) {
1532 MOZ_ASSERT(initialShape
->numFixedSlots() == shape
->numFixedSlots());
1533 MOZ_ASSERT(initialShape
->base() == shape
->base());
1534 MOZ_ASSERT(initialShape
->objectFlags() == shape
->objectFlags());
1536 table
.replaceKey(p
, lookup
, shape
.get());
1538 // Purge the prototype's shape cache entry.
1539 if (shape
->proto().isObject()) {
1540 JSObject
* protoObj
= shape
->proto().toObject();
1541 if (protoObj
->shape()->cache().isShapeWithProto()) {
1542 protoObj
->shape()->cacheRef().setNone();
1548 ProxyShape
* ProxyShape::getShape(JSContext
* cx
, const JSClass
* clasp
,
1549 JS::Realm
* realm
, TaggedProto proto
,
1550 ObjectFlags objectFlags
) {
1551 MOZ_ASSERT(cx
->compartment() == realm
->compartment());
1552 MOZ_ASSERT_IF(proto
.isObject(),
1553 cx
->isInsideCurrentCompartment(proto
.toObject()));
1555 if (proto
.isObject() && !proto
.toObject()->isUsedAsPrototype()) {
1556 RootedObject
protoObj(cx
, proto
.toObject());
1557 if (!SetObjectIsUsedAsPrototype(cx
, protoObj
)) {
1560 proto
= TaggedProto(protoObj
);
1563 auto& table
= realm
->zone()->shapeZone().proxyShapes
;
1565 using Lookup
= ProxyShapeHasher::Lookup
;
1567 MakeDependentAddPtr(cx
, table
, Lookup(clasp
, realm
, proto
, objectFlags
));
1572 Rooted
<TaggedProto
> protoRoot(cx
, proto
);
1573 Rooted
<BaseShape
*> nbase(cx
, BaseShape::get(cx
, clasp
, realm
, protoRoot
));
1578 Rooted
<ProxyShape
*> shape(cx
, ProxyShape::new_(cx
, nbase
, objectFlags
));
1583 Lookup
lookup(clasp
, realm
, protoRoot
, objectFlags
);
1584 if (!ptr
.add(cx
, table
, lookup
, shape
)) {
1592 WasmGCShape
* WasmGCShape::getShape(JSContext
* cx
, const JSClass
* clasp
,
1593 JS::Realm
* realm
, TaggedProto proto
,
1594 const wasm::RecGroup
* recGroup
,
1595 ObjectFlags objectFlags
) {
1596 MOZ_ASSERT(cx
->compartment() == realm
->compartment());
1597 MOZ_ASSERT_IF(proto
.isObject(),
1598 cx
->isInsideCurrentCompartment(proto
.toObject()));
1600 if (proto
.isObject() && !proto
.toObject()->isUsedAsPrototype()) {
1601 RootedObject
protoObj(cx
, proto
.toObject());
1602 if (!SetObjectIsUsedAsPrototype(cx
, protoObj
)) {
1605 proto
= TaggedProto(protoObj
);
1608 auto& table
= realm
->zone()->shapeZone().wasmGCShapes
;
1610 using Lookup
= WasmGCShapeHasher::Lookup
;
1611 auto ptr
= MakeDependentAddPtr(
1612 cx
, table
, Lookup(clasp
, realm
, proto
, recGroup
, objectFlags
));
1617 Rooted
<TaggedProto
> protoRoot(cx
, proto
);
1618 Rooted
<BaseShape
*> nbase(cx
, BaseShape::get(cx
, clasp
, realm
, protoRoot
));
1623 Rooted
<WasmGCShape
*> shape(
1624 cx
, WasmGCShape::new_(cx
, nbase
, recGroup
, objectFlags
));
1629 Lookup
lookup(clasp
, realm
, protoRoot
, recGroup
, objectFlags
);
1630 if (!ptr
.add(cx
, table
, lookup
, shape
)) {
1637 JS::ubi::Node::Size
JS::ubi::Concrete
<js::Shape
>::size(
1638 mozilla::MallocSizeOf mallocSizeOf
) const {
1639 Size size
= js::gc::Arena::thingSize(get().asTenured().getAllocKind());
1641 if (get().cache().isShapeSetForAdd()) {
1642 ShapeSetForAdd
* set
= get().cache().toShapeSetForAdd();
1643 size
+= set
->shallowSizeOfIncludingThis(mallocSizeOf
);
1649 JS::ubi::Node::Size
JS::ubi::Concrete
<js::BaseShape
>::size(
1650 mozilla::MallocSizeOf mallocSizeOf
) const {
1651 return js::gc::Arena::thingSize(get().asTenured().getAllocKind());