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