1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef vm_Shape_h
8 #define vm_Shape_h
9 
10 #include "js/shadow/Shape.h"  // JS::shadow::Shape, JS::shadow::BaseShape
11 
12 #include "mozilla/Attributes.h"
13 #include "mozilla/HashFunctions.h"
14 #include "mozilla/MathAlgorithms.h"
15 #include "mozilla/Maybe.h"
16 #include "mozilla/MemoryReporting.h"
17 #include "mozilla/TemplateLib.h"
18 
19 #include <algorithm>
20 
21 #include "jsapi.h"
22 #include "jsfriendapi.h"
23 #include "jstypes.h"
24 #include "NamespaceImports.h"
25 
26 #include "gc/Barrier.h"
27 #include "gc/FreeOp.h"
28 #include "gc/MaybeRooted.h"
29 #include "gc/Rooting.h"
30 #include "js/HashTable.h"
31 #include "js/Id.h"  // JS::PropertyKey
32 #include "js/MemoryMetrics.h"
33 #include "js/RootingAPI.h"
34 #include "js/UbiNode.h"
35 #include "util/EnumFlags.h"
36 #include "vm/JSAtom.h"
37 #include "vm/ObjectFlags.h"
38 #include "vm/Printer.h"
39 #include "vm/PropertyInfo.h"
40 #include "vm/PropertyKey.h"
41 #include "vm/PropMap.h"
42 #include "vm/StringType.h"
43 #include "vm/SymbolType.h"
44 
45 // [SMDOC] Shapes
46 //
47 // A Shape represents the layout of an object. It stores and implies:
48 //
49 //  * The object's JSClass, Realm, prototype (see BaseShape).
50 //  * For native objects, the object's properties (PropMap and map length).
51 //  * The fixed slot capacity of the object (numFixedSlots).
52 //  * The object's flags (ObjectFlags).
53 //
54 // The shape implies the property structure (keys, attributes, property order
55 // for enumeration) but not the property values. The values are stored in object
56 // slots.
57 //
58 // Every JSObject has a pointer, |shape_|, accessible via shape(), to the
59 // current shape of the object. This pointer permits fast object layout tests.
60 //
61 // There are two kinds of shapes:
62 //
63 // * Shared shapes. Either initial shapes (no property map) or SharedPropMap
64 //   shapes (for native objects with properties).
65 //
66 //   These are immutable tuples stored in a hash table, so that objects with the
67 //   same structure end up with the same shape (this both saves memory and
68 //   allows JIT optimizations based on this shape).
69 //
70 //   To avoid hash table lookups on the hot addProperty path, shapes have a
71 //   ShapeCachePtr that's used as cache for this. This cache is purged on GC.
72 //   The shape cache is also used as cache for prototype shapes, to point to the
73 //   initial shape for objects using that shape.
74 //
75 // * Dictionary shapes. Used only for native objects. An object with a
76 //   dictionary shape is "in dictionary mode". Certain property operations
77 //   are not supported for shared maps so in these cases we need to convert the
78 //   object to dictionary mode by creating a dictionary property map and a
79 //   dictionary shape. An object is converted to dictionary mode in the
80 //   following cases:
81 //
82 //   - Changing a property's flags/attributes and the property is not the last
83 //     property.
84 //   - Removing a property other than the object's last property.
85 //   - The object has many properties. See maybeConvertToDictionaryForAdd for
86 //     the heuristics.
87 //   - For prototype objects: when a shadowing property is added to an object
88 //     with this object on its prototype chain. This is used to invalidate the
89 //     shape teleporting optimization. See reshapeForShadowedProp.
90 //
91 //   Dictionary shapes are unshared, private to a single object, and always have
92 //   a DictionaryPropMap that's similarly unshared. Dictionary shape mutations
93 //   do require allocating a new dictionary shape for the object, to properly
94 //   invalidate JIT inline caches and other shape guards.
95 //   See NativeObject::generateNewDictionaryShape.
96 //
97 // Because many Shapes have similar data, there is actually a secondary type
98 // called a BaseShape that holds some of a Shape's data (the JSClass, Realm,
99 // prototype). Many shapes can share a single BaseShape.
100 
JSSLOT_FREE(const JSClass * clasp)101 MOZ_ALWAYS_INLINE size_t JSSLOT_FREE(const JSClass* clasp) {
102   // Proxy classes have reserved slots, but proxies manage their own slot
103   // layout.
104   MOZ_ASSERT(!clasp->isProxyObject());
105   return JSCLASS_RESERVED_SLOTS(clasp);
106 }
107 
108 namespace js {
109 
110 class Shape;
111 
112 // Hash policy for ShapeCachePtr's ShapeSetForAdd. Maps the new property key and
113 // flags to the new shape.
114 struct ShapeForAddHasher : public DefaultHasher<Shape*> {
115   using Key = Shape*;
116 
117   struct Lookup {
118     PropertyKey key;
119     PropertyFlags flags;
120 
LookupShapeForAddHasher::Lookup121     Lookup(PropertyKey key, PropertyFlags flags) : key(key), flags(flags) {}
122   };
123 
124   static MOZ_ALWAYS_INLINE HashNumber hash(const Lookup& l);
125   static MOZ_ALWAYS_INLINE bool match(Shape* shape, const Lookup& l);
126 };
127 using ShapeSetForAdd = HashSet<Shape*, ShapeForAddHasher, SystemAllocPolicy>;
128 
129 // Each shape has a cache pointer that's either:
130 //
131 // * None
132 // * For shared shapes, a single shape used to speed up addProperty.
133 // * For shared shapes, a set of shapes used to speed up addProperty.
134 // * For prototype shapes, the most recently used initial shape allocated for a
135 //   prototype object with this shape.
136 //
137 // The cache is purely an optimization and is purged on GC (all shapes with a
138 // non-None ShapeCachePtr are added to a vector in the Zone).
139 class ShapeCachePtr {
140   enum {
141     SINGLE_SHAPE_FOR_ADD = 0,
142     SHAPE_SET_FOR_ADD = 1,
143     SHAPE_WITH_PROTO = 2,
144     MASK = 3
145   };
146 
147   uintptr_t bits = 0;
148 
149  public:
isNone()150   bool isNone() const { return !bits; }
setNone()151   void setNone() { bits = 0; }
152 
isSingleShapeForAdd()153   bool isSingleShapeForAdd() const {
154     return (bits & MASK) == SINGLE_SHAPE_FOR_ADD && !isNone();
155   }
toSingleShapeForAdd()156   Shape* toSingleShapeForAdd() const {
157     MOZ_ASSERT(isSingleShapeForAdd());
158     return reinterpret_cast<Shape*>(bits & ~uintptr_t(MASK));
159   }
setSingleShapeForAdd(Shape * shape)160   void setSingleShapeForAdd(Shape* shape) {
161     MOZ_ASSERT(shape);
162     MOZ_ASSERT((uintptr_t(shape) & MASK) == 0);
163     MOZ_ASSERT(!isShapeSetForAdd());  // Don't leak the ShapeSet.
164     bits = uintptr_t(shape) | SINGLE_SHAPE_FOR_ADD;
165   }
166 
isShapeSetForAdd()167   bool isShapeSetForAdd() const { return (bits & MASK) == SHAPE_SET_FOR_ADD; }
toShapeSetForAdd()168   ShapeSetForAdd* toShapeSetForAdd() const {
169     MOZ_ASSERT(isShapeSetForAdd());
170     return reinterpret_cast<ShapeSetForAdd*>(bits & ~uintptr_t(MASK));
171   }
setShapeSetForAdd(ShapeSetForAdd * hash)172   void setShapeSetForAdd(ShapeSetForAdd* hash) {
173     MOZ_ASSERT(hash);
174     MOZ_ASSERT((uintptr_t(hash) & MASK) == 0);
175     bits = uintptr_t(hash) | SHAPE_SET_FOR_ADD;
176   }
177 
isShapeWithProto()178   bool isShapeWithProto() const { return (bits & MASK) == SHAPE_WITH_PROTO; }
toShapeWithProto()179   Shape* toShapeWithProto() const {
180     MOZ_ASSERT(isShapeWithProto());
181     return reinterpret_cast<Shape*>(bits & ~uintptr_t(MASK));
182   }
setShapeWithProto(Shape * shape)183   void setShapeWithProto(Shape* shape) {
184     MOZ_ASSERT(shape);
185     MOZ_ASSERT((uintptr_t(shape) & MASK) == 0);
186     MOZ_ASSERT(!isShapeSetForAdd());  // Don't leak the ShapeSet.
187     bits = uintptr_t(shape) | SHAPE_WITH_PROTO;
188   }
189 } JS_HAZ_GC_POINTER;
190 
191 class TenuringTracer;
192 
193 // BaseShapes store the object's class, realm and prototype. BaseShapes are
194 // immutable tuples stored in a per-Zone hash table.
195 class BaseShape : public gc::TenuredCellWithNonGCPointer<const JSClass> {
196  public:
197   /* Class of referring object, stored in the cell header */
clasp()198   const JSClass* clasp() const { return headerPtr(); }
199 
200  private:
201   JS::Realm* realm_;
202   GCPtr<TaggedProto> proto_;
203 
204   BaseShape(const BaseShape& base) = delete;
205   BaseShape& operator=(const BaseShape& other) = delete;
206 
207  public:
finalize(JSFreeOp * fop)208   void finalize(JSFreeOp* fop) {}
209 
210   BaseShape(const JSClass* clasp, JS::Realm* realm, TaggedProto proto);
211 
212   /* Not defined: BaseShapes must not be stack allocated. */
213   ~BaseShape() = delete;
214 
realm()215   JS::Realm* realm() const { return realm_; }
compartment()216   JS::Compartment* compartment() const {
217     return JS::GetCompartmentForRealm(realm());
218   }
maybeCompartment()219   JS::Compartment* maybeCompartment() const { return compartment(); }
220 
proto()221   TaggedProto proto() const { return proto_; }
222 
setRealmForMergeRealms(JS::Realm * realm)223   void setRealmForMergeRealms(JS::Realm* realm) { realm_ = realm; }
setProtoForMergeRealms(TaggedProto proto)224   void setProtoForMergeRealms(TaggedProto proto) { proto_ = proto; }
225 
226   /*
227    * Lookup base shapes from the zone's baseShapes table, adding if not
228    * already found.
229    */
230   static BaseShape* get(JSContext* cx, const JSClass* clasp, JS::Realm* realm,
231                         Handle<TaggedProto> proto);
232 
233   static const JS::TraceKind TraceKind = JS::TraceKind::BaseShape;
234 
235   void traceChildren(JSTracer* trc);
236 
offsetOfClasp()237   static constexpr size_t offsetOfClasp() { return offsetOfHeaderPtr(); }
238 
offsetOfRealm()239   static constexpr size_t offsetOfRealm() {
240     return offsetof(BaseShape, realm_);
241   }
242 
offsetOfProto()243   static constexpr size_t offsetOfProto() {
244     return offsetof(BaseShape, proto_);
245   }
246 
247  private:
staticAsserts()248   static void staticAsserts() {
249     static_assert(offsetOfClasp() == offsetof(JS::shadow::BaseShape, clasp));
250     static_assert(offsetOfRealm() == offsetof(JS::shadow::BaseShape, realm));
251     static_assert(sizeof(BaseShape) % gc::CellAlignBytes == 0,
252                   "Things inheriting from gc::Cell must have a size that's "
253                   "a multiple of gc::CellAlignBytes");
254     // Sanity check BaseShape size is what we expect.
255 #ifdef JS_64BIT
256     static_assert(sizeof(BaseShape) == 3 * sizeof(void*));
257 #else
258     static_assert(sizeof(BaseShape) == 4 * sizeof(void*));
259 #endif
260   }
261 };
262 
263 class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
264   friend class ::JSObject;
265   friend class ::JSFunction;
266   friend class GCMarker;
267   friend class NativeObject;
268   friend class SharedShape;
269   friend class PropertyTree;
270   friend class TenuringTracer;
271   friend class JS::ubi::Concrete<Shape>;
272   friend class js::gc::RelocationOverlay;
273 
274  public:
275   // Base shape, stored in the cell header.
base()276   BaseShape* base() const { return headerPtr(); }
277 
278  protected:
279   // Flags that are not modified after the Shape is created. Off-thread Ion
280   // compilation can access the immutableFlags word, so we don't want any
281   // mutable state here to avoid (TSan) races.
282   enum ImmutableFlags : uint32_t {
283     // The length associated with the property map. This is a value in the range
284     // [0, PropMap::Capacity]. A length of 0 indicates the object is empty (has
285     // no properties).
286     MAP_LENGTH_MASK = BitMask(4),
287 
288     // If set, this is a dictionary shape.
289     IS_DICTIONARY = 1 << 4,
290 
291     // Number of fixed slots in objects with this shape.
292     // FIXED_SLOTS_MAX is the biggest count of fixed slots a Shape can store.
293     FIXED_SLOTS_MAX = 0x1f,
294     FIXED_SLOTS_SHIFT = 5,
295     FIXED_SLOTS_MASK = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT),
296 
297     // For non-dictionary shapes: the slot span of the object, if it fits in a
298     // single byte. If the value is SMALL_SLOTSPAN_MAX, the slot span has to be
299     // computed based on the property map (which is slower).
300     //
301     // Note: NativeObject::addProperty will convert to dictionary mode before we
302     // reach this limit, but there are other places where we add properties to
303     // shapes, for example environment object shapes.
304     SMALL_SLOTSPAN_MAX = 0x3ff,  // 10 bits.
305     SMALL_SLOTSPAN_SHIFT = 10,
306     SMALL_SLOTSPAN_MASK = uint32_t(SMALL_SLOTSPAN_MAX << SMALL_SLOTSPAN_SHIFT),
307   };
308 
309   uint32_t immutableFlags;   // Immutable flags, see above.
310   ObjectFlags objectFlags_;  // Immutable object flags, see ObjectFlags.
311 
312   // The shape's property map. This is either nullptr for shared initial (empty)
313   // shapes, a SharedPropMap for SharedPropMap shapes, or a DictionaryPropMap
314   // for dictionary shapes.
315   GCPtr<PropMap*> propMap_;
316 
317   // Cache used to speed up common operations on shapes.
318   ShapeCachePtr cache_;
319 
320   // Give the object a shape that's similar to its current shape, but with the
321   // passed objectFlags, proto, and nfixed values.
322   static bool replaceShape(JSContext* cx, HandleObject obj,
323                            ObjectFlags objectFlags, TaggedProto proto,
324                            uint32_t nfixed);
325 
setObjectFlags(ObjectFlags flags)326   void setObjectFlags(ObjectFlags flags) {
327     MOZ_ASSERT(isDictionary());
328     objectFlags_ = flags;
329   }
330 
331  public:
addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,JS::ShapeInfo * info)332   void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
333                               JS::ShapeInfo* info) const {
334     if (cache_.isShapeSetForAdd()) {
335       info->shapesMallocHeapCache +=
336           cache_.toShapeSetForAdd()->shallowSizeOfIncludingThis(mallocSizeOf);
337     }
338   }
339 
propMap()340   PropMap* propMap() const { return propMap_; }
341 
cacheRef()342   ShapeCachePtr& cacheRef() { return cache_; }
cache()343   ShapeCachePtr cache() const { return cache_; }
344 
sharedPropMap()345   SharedPropMap* sharedPropMap() const {
346     MOZ_ASSERT(!isDictionary());
347     return propMap_ ? propMap_->asShared() : nullptr;
348   }
dictionaryPropMap()349   DictionaryPropMap* dictionaryPropMap() const {
350     MOZ_ASSERT(isDictionary());
351     MOZ_ASSERT(propMap_);
352     return propMap_->asDictionary();
353   }
354 
propMapLength()355   uint32_t propMapLength() const { return immutableFlags & MAP_LENGTH_MASK; }
356 
lastProperty()357   PropertyInfoWithKey lastProperty() const {
358     MOZ_ASSERT(propMapLength() > 0);
359     size_t index = propMapLength() - 1;
360     return propMap()->getPropertyInfoWithKey(index);
361   }
362 
363   MOZ_ALWAYS_INLINE PropMap* lookup(JSContext* cx, PropertyKey key,
364                                     uint32_t* index);
365   MOZ_ALWAYS_INLINE PropMap* lookupPure(PropertyKey key, uint32_t* index);
366 
lastPropertyMatchesForAdd(PropertyKey key,PropertyFlags flags,uint32_t * slot)367   bool lastPropertyMatchesForAdd(PropertyKey key, PropertyFlags flags,
368                                  uint32_t* slot) const {
369     MOZ_ASSERT(propMapLength() > 0);
370     MOZ_ASSERT(!isDictionary());
371     uint32_t index = propMapLength() - 1;
372     SharedPropMap* map = sharedPropMap();
373     if (map->getKey(index) != key) {
374       return false;
375     }
376     PropertyInfo prop = map->getPropertyInfo(index);
377     if (prop.flags() != flags) {
378       return false;
379     }
380     *slot = prop.maybeSlot();
381     return true;
382   }
383 
getObjectClass()384   const JSClass* getObjectClass() const { return base()->clasp(); }
realm()385   JS::Realm* realm() const { return base()->realm(); }
386 
compartment()387   JS::Compartment* compartment() const { return base()->compartment(); }
maybeCompartment()388   JS::Compartment* maybeCompartment() const {
389     return base()->maybeCompartment();
390   }
391 
proto()392   TaggedProto proto() const { return base()->proto(); }
393 
objectFlags()394   ObjectFlags objectFlags() const { return objectFlags_; }
hasObjectFlag(ObjectFlag flag)395   bool hasObjectFlag(ObjectFlag flag) const {
396     return objectFlags_.hasFlag(flag);
397   }
398 
399  protected:
Shape(BaseShape * base,ObjectFlags objectFlags,uint32_t nfixed,PropMap * map,uint32_t mapLength,bool isDictionary)400   Shape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed, PropMap* map,
401         uint32_t mapLength, bool isDictionary)
402       : CellWithTenuredGCPointer(base),
403         immutableFlags((isDictionary ? IS_DICTIONARY : 0) |
404                        (nfixed << FIXED_SLOTS_SHIFT) | mapLength),
405         objectFlags_(objectFlags),
406         propMap_(map) {
407     MOZ_ASSERT(base);
408     MOZ_ASSERT(mapLength <= PropMap::Capacity);
409     if (!isDictionary && base->clasp()->isNativeObject()) {
410       initSmallSlotSpan();
411     }
412   }
413 
414   Shape(const Shape& other) = delete;
415 
416  public:
isDictionary()417   bool isDictionary() const { return immutableFlags & IS_DICTIONARY; }
418 
slotSpanSlow()419   uint32_t slotSpanSlow() const {
420     MOZ_ASSERT(!isDictionary());
421     const JSClass* clasp = getObjectClass();
422     return SharedPropMap::slotSpan(clasp, sharedPropMap(), propMapLength());
423   }
424 
initSmallSlotSpan()425   void initSmallSlotSpan() {
426     MOZ_ASSERT(!isDictionary());
427     uint32_t slotSpan = slotSpanSlow();
428     if (slotSpan > SMALL_SLOTSPAN_MAX) {
429       slotSpan = SMALL_SLOTSPAN_MAX;
430     }
431     MOZ_ASSERT((immutableFlags & SMALL_SLOTSPAN_MASK) == 0);
432     immutableFlags |= (slotSpan << SMALL_SLOTSPAN_SHIFT);
433   }
434 
slotSpan()435   uint32_t slotSpan() const {
436     MOZ_ASSERT(!isDictionary());
437     MOZ_ASSERT(getObjectClass()->isNativeObject());
438     uint32_t span =
439         (immutableFlags & SMALL_SLOTSPAN_MASK) >> SMALL_SLOTSPAN_SHIFT;
440     if (MOZ_LIKELY(span < SMALL_SLOTSPAN_MAX)) {
441       MOZ_ASSERT(slotSpanSlow() == span);
442       return span;
443     }
444     return slotSpanSlow();
445   }
446 
numFixedSlots()447   uint32_t numFixedSlots() const {
448     return (immutableFlags & FIXED_SLOTS_MASK) >> FIXED_SLOTS_SHIFT;
449   }
450 
setNumFixedSlots(uint32_t nfixed)451   void setNumFixedSlots(uint32_t nfixed) {
452     MOZ_ASSERT(nfixed < FIXED_SLOTS_MAX);
453     immutableFlags = immutableFlags & ~FIXED_SLOTS_MASK;
454     immutableFlags = immutableFlags | (nfixed << FIXED_SLOTS_SHIFT);
455   }
456 
setBase(BaseShape * base)457   void setBase(BaseShape* base) {
458     MOZ_ASSERT(base);
459     MOZ_ASSERT(isDictionary());
460     setHeaderPtr(base);
461   }
462 
463  public:
464 #ifdef DEBUG
465   void dump(js::GenericPrinter& out) const;
466   void dump() const;
467 #endif
468 
469   inline void purgeCache(JSFreeOp* fop);
470   inline void finalize(JSFreeOp* fop);
471 
472   static const JS::TraceKind TraceKind = JS::TraceKind::Shape;
473 
474   void traceChildren(JSTracer* trc);
475 
476   // For JIT usage.
offsetOfBaseShape()477   static constexpr size_t offsetOfBaseShape() { return offsetOfHeaderPtr(); }
478 
offsetOfObjectFlags()479   static constexpr size_t offsetOfObjectFlags() {
480     return offsetof(Shape, objectFlags_);
481   }
482 
483 #ifdef DEBUG
offsetOfImmutableFlags()484   static inline size_t offsetOfImmutableFlags() {
485     return offsetof(Shape, immutableFlags);
486   }
fixedSlotsMask()487   static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; }
488 #endif
489 
490  private:
updateNewDictionaryShape(ObjectFlags flags,DictionaryPropMap * map,uint32_t mapLength)491   void updateNewDictionaryShape(ObjectFlags flags, DictionaryPropMap* map,
492                                 uint32_t mapLength) {
493     MOZ_ASSERT(isDictionary());
494     objectFlags_ = flags;
495     propMap_ = map;
496     immutableFlags = (immutableFlags & ~MAP_LENGTH_MASK) | mapLength;
497     MOZ_ASSERT(propMapLength() == mapLength);
498   }
499 
staticAsserts()500   static void staticAsserts() {
501     static_assert(offsetOfBaseShape() == offsetof(JS::shadow::Shape, base));
502     static_assert(offsetof(Shape, immutableFlags) ==
503                   offsetof(JS::shadow::Shape, immutableFlags));
504     static_assert(FIXED_SLOTS_SHIFT == JS::shadow::Shape::FIXED_SLOTS_SHIFT);
505     static_assert(FIXED_SLOTS_MASK == JS::shadow::Shape::FIXED_SLOTS_MASK);
506     // Sanity check Shape size is what we expect.
507 #ifdef JS_64BIT
508     static_assert(sizeof(Shape) == 4 * sizeof(void*));
509 #else
510     static_assert(sizeof(Shape) == 6 * sizeof(void*));
511 #endif
512   }
513 };
514 
515 class SharedShape : public js::Shape {
SharedShape(BaseShape * base,ObjectFlags objectFlags,uint32_t nfixed,PropMap * map,uint32_t mapLength)516   SharedShape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed,
517               PropMap* map, uint32_t mapLength)
518       : Shape(base, objectFlags, nfixed, map, mapLength,
519               /* isDictionary = */ false) {}
520 
521   static Shape* new_(JSContext* cx, Handle<BaseShape*> base,
522                      ObjectFlags objectFlags, uint32_t nfixed,
523                      Handle<SharedPropMap*> map, uint32_t mapLength);
524 
525  public:
526   /*
527    * Lookup an initial shape matching the given parameters, creating an empty
528    * shape if none was found.
529    */
530   static Shape* getInitialShape(JSContext* cx, const JSClass* clasp,
531                                 JS::Realm* realm, TaggedProto proto,
532                                 size_t nfixed, ObjectFlags objectFlags = {});
533   static Shape* getInitialShape(JSContext* cx, const JSClass* clasp,
534                                 JS::Realm* realm, TaggedProto proto,
535                                 gc::AllocKind kind,
536                                 ObjectFlags objectFlags = {});
537 
538   static Shape* getPropMapShape(JSContext* cx, BaseShape* base, size_t nfixed,
539                                 Handle<SharedPropMap*> map, uint32_t mapLength,
540                                 ObjectFlags objectFlags,
541                                 bool* allocatedNewShape = nullptr);
542 
543   static Shape* getInitialOrPropMapShape(JSContext* cx, const JSClass* clasp,
544                                          JS::Realm* realm, TaggedProto proto,
545                                          size_t nfixed,
546                                          Handle<SharedPropMap*> map,
547                                          uint32_t mapLength,
548                                          ObjectFlags objectFlags);
549 
550   /*
551    * Reinsert an alternate initial shape, to be returned by future
552    * getInitialShape calls, until the new shape becomes unreachable in a GC
553    * and the table entry is purged.
554    */
555   static void insertInitialShape(JSContext* cx, HandleShape shape);
556 
557   /*
558    * Some object subclasses are allocated with a built-in set of properties.
559    * The first time such an object is created, these built-in properties must
560    * be set manually, to compute an initial shape.  Afterward, that initial
561    * shape can be reused for newly-created objects that use the subclass's
562    * standard prototype.  This method should be used in a post-allocation
563    * init method, to ensure that objects of such subclasses compute and cache
564    * the initial shape, if it hasn't already been computed.
565    */
566   template <class ObjectSubclass>
567   static inline bool ensureInitialCustomShape(JSContext* cx,
568                                               Handle<ObjectSubclass*> obj);
569 };
570 
571 class DictionaryShape : public js::Shape {
DictionaryShape(BaseShape * base,ObjectFlags objectFlags,uint32_t nfixed,PropMap * map,uint32_t mapLength)572   DictionaryShape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed,
573                   PropMap* map, uint32_t mapLength)
574       : Shape(base, objectFlags, nfixed, map, mapLength,
575               /* isDictionary = */ true) {
576     MOZ_ASSERT(map);
577   }
578 
579  public:
580   static Shape* new_(JSContext* cx, Handle<BaseShape*> base,
581                      ObjectFlags objectFlags, uint32_t nfixed,
582                      Handle<DictionaryPropMap*> map, uint32_t mapLength);
583 };
584 
585 // Iterator for iterating over a shape's properties. It can be used like this:
586 //
587 //   for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) {
588 //     PropertyKey key = iter->key();
589 //     if (iter->isDataProperty() && iter->enumerable()) { .. }
590 //   }
591 //
592 // Properties are iterated in reverse order (i.e., iteration starts at the most
593 // recently added property).
594 template <AllowGC allowGC>
595 class MOZ_RAII ShapePropertyIter {
596  protected:
597   friend class Shape;
598 
599   typename MaybeRooted<PropMap*, allowGC>::RootType map_;
600   uint32_t mapLength_;
601   const bool isDictionary_;
602 
603  public:
ShapePropertyIter(JSContext * cx,Shape * shape)604   ShapePropertyIter(JSContext* cx, Shape* shape)
605       : map_(cx, shape->propMap()),
606         mapLength_(shape->propMapLength()),
607         isDictionary_(shape->isDictionary()) {
608     static_assert(allowGC == CanGC);
609     MOZ_ASSERT(shape->getObjectClass()->isNativeObject());
610   }
611 
ShapePropertyIter(Shape * shape)612   explicit ShapePropertyIter(Shape* shape)
613       : map_(nullptr, shape->propMap()),
614         mapLength_(shape->propMapLength()),
615         isDictionary_(shape->isDictionary()) {
616     static_assert(allowGC == NoGC);
617     MOZ_ASSERT(shape->getObjectClass()->isNativeObject());
618   }
619 
done()620   bool done() const { return mapLength_ == 0; }
621 
622   void operator++(int) {
623     do {
624       MOZ_ASSERT(!done());
625       if (mapLength_ > 1) {
626         mapLength_--;
627       } else if (map_->hasPrevious()) {
628         map_ = map_->asLinked()->previous();
629         mapLength_ = PropMap::Capacity;
630       } else {
631         // Done iterating.
632         map_ = nullptr;
633         mapLength_ = 0;
634         return;
635       }
636       // Dictionary maps can have "holes" for removed properties, so keep going
637       // until we find a non-hole slot.
638     } while (MOZ_UNLIKELY(isDictionary_ && !map_->hasKey(mapLength_ - 1)));
639   }
640 
get()641   PropertyInfoWithKey get() const {
642     MOZ_ASSERT(!done());
643     return map_->getPropertyInfoWithKey(mapLength_ - 1);
644   }
645 
646   PropertyInfoWithKey operator*() const { return get(); }
647 
648   // Fake pointer struct to make operator-> work.
649   // See https://stackoverflow.com/a/52856349.
650   struct FakePtr {
651     PropertyInfoWithKey val_;
652     const PropertyInfoWithKey* operator->() const { return &val_; }
653   };
654   FakePtr operator->() const { return {get()}; }
655 };
656 
657 }  // namespace js
658 
659 // JS::ubi::Nodes can point to Shapes and BaseShapes; they're js::gc::Cell
660 // instances that occupy a compartment.
661 namespace JS {
662 namespace ubi {
663 
664 template <>
665 class Concrete<js::Shape> : TracerConcrete<js::Shape> {
666  protected:
Concrete(js::Shape * ptr)667   explicit Concrete(js::Shape* ptr) : TracerConcrete<js::Shape>(ptr) {}
668 
669  public:
construct(void * storage,js::Shape * ptr)670   static void construct(void* storage, js::Shape* ptr) {
671     new (storage) Concrete(ptr);
672   }
673 
674   Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
675 
typeName()676   const char16_t* typeName() const override { return concreteTypeName; }
677   static const char16_t concreteTypeName[];
678 };
679 
680 template <>
681 class Concrete<js::BaseShape> : TracerConcrete<js::BaseShape> {
682  protected:
Concrete(js::BaseShape * ptr)683   explicit Concrete(js::BaseShape* ptr) : TracerConcrete<js::BaseShape>(ptr) {}
684 
685  public:
construct(void * storage,js::BaseShape * ptr)686   static void construct(void* storage, js::BaseShape* ptr) {
687     new (storage) Concrete(ptr);
688   }
689 
690   Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
691 
typeName()692   const char16_t* typeName() const override { return concreteTypeName; }
693   static const char16_t concreteTypeName[];
694 };
695 
696 }  // namespace ubi
697 }  // namespace JS
698 
699 #endif /* vm_Shape_h */
700