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 #include "vm/PropMap-inl.h"
8 
9 #include "gc/Allocator.h"
10 #include "gc/HashUtil.h"
11 #include "js/GCVector.h"
12 #include "vm/JSObject.h"
13 
14 #include "vm/ObjectFlags-inl.h"
15 
16 using namespace js;
17 
addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,size_t * children,size_t * tables) const18 void PropMap::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
19                                      size_t* children, size_t* tables) const {
20   if (isShared() && asShared()->hasChildrenSet()) {
21     auto* set = asShared()->treeDataRef().children.toChildrenSet();
22     *children += set->shallowSizeOfIncludingThis(mallocSizeOf);
23   }
24   if (canHaveTable() && asLinked()->hasTable()) {
25     *tables += asLinked()->data_.table->sizeOfIncludingThis(mallocSizeOf);
26   }
27 }
28 
29 // static
create(JSContext * cx,Handle<SharedPropMap * > prev,HandleId id,PropertyInfo prop)30 SharedPropMap* SharedPropMap::create(JSContext* cx, Handle<SharedPropMap*> prev,
31                                      HandleId id, PropertyInfo prop) {
32   // If the first property has a slot number <= MaxSlotNumber, all properties
33   // added later will have a slot number <= CompactPropertyInfo::MaxSlotNumber
34   // so we can use a CompactPropMap.
35   static constexpr size_t MaxFirstSlot =
36       CompactPropertyInfo::MaxSlotNumber - (PropMap::Capacity - 1);
37 
38   if (!prev && prop.maybeSlot() <= MaxFirstSlot) {
39     CompactPropMap* map = Allocate<CompactPropMap>(cx);
40     if (!map) {
41       return nullptr;
42     }
43     new (map) CompactPropMap(id, prop);
44     return map;
45   }
46 
47   NormalPropMap* map = Allocate<NormalPropMap>(cx);
48   if (!map) {
49     return nullptr;
50   }
51   new (map) NormalPropMap(prev, id, prop);
52   return map;
53 }
54 
55 // static
createInitial(JSContext * cx,HandleId id,PropertyInfo prop)56 SharedPropMap* SharedPropMap::createInitial(JSContext* cx, HandleId id,
57                                             PropertyInfo prop) {
58   // Lookup or create a shared map based on the first property.
59 
60   using Lookup = InitialPropMapHasher::Lookup;
61 
62   auto& table = cx->zone()->shapeZone().initialPropMaps;
63 
64   auto p = MakeDependentAddPtr(cx, table, Lookup(id, prop));
65   if (p) {
66     return *p;
67   }
68 
69   SharedPropMap* result = create(cx, /* prev = */ nullptr, id, prop);
70   if (!result) {
71     return nullptr;
72   }
73 
74   Lookup lookup(id, prop);
75   if (!p.add(cx, table, lookup, result)) {
76     return nullptr;
77   }
78 
79   return result;
80 }
81 
82 // static
clone(JSContext * cx,Handle<SharedPropMap * > map,uint32_t length)83 SharedPropMap* SharedPropMap::clone(JSContext* cx, Handle<SharedPropMap*> map,
84                                     uint32_t length) {
85   MOZ_ASSERT(length > 0);
86 
87   if (map->isCompact()) {
88     CompactPropMap* clone = Allocate<CompactPropMap>(cx);
89     if (!clone) {
90       return nullptr;
91     }
92     new (clone) CompactPropMap(map->asCompact(), length);
93     return clone;
94   }
95 
96   NormalPropMap* clone = Allocate<NormalPropMap>(cx);
97   if (!clone) {
98     return nullptr;
99   }
100   new (clone) NormalPropMap(map->asNormal(), length);
101   return clone;
102 }
103 
104 // static
toDictionaryMap(JSContext * cx,Handle<SharedPropMap * > map,uint32_t length)105 DictionaryPropMap* SharedPropMap::toDictionaryMap(JSContext* cx,
106                                                   Handle<SharedPropMap*> map,
107                                                   uint32_t length) {
108   // Starting at the last map, clone each shared map to a new dictionary map.
109 
110   Rooted<DictionaryPropMap*> lastDictMap(cx);
111   Rooted<DictionaryPropMap*> nextDictMap(cx);
112 
113   Rooted<SharedPropMap*> sharedMap(cx, map);
114   uint32_t sharedLength = length;
115   while (true) {
116     sharedMap->setHadDictionaryConversion();
117 
118     DictionaryPropMap* dictMap = Allocate<DictionaryPropMap>(cx);
119     if (!dictMap) {
120       return nullptr;
121     }
122     if (sharedMap->isCompact()) {
123       new (dictMap) DictionaryPropMap(sharedMap->asCompact(), sharedLength);
124     } else {
125       new (dictMap) DictionaryPropMap(sharedMap->asNormal(), sharedLength);
126     }
127 
128     if (!lastDictMap) {
129       lastDictMap = dictMap;
130     }
131 
132     if (nextDictMap) {
133       nextDictMap->initPrevious(dictMap);
134     }
135     nextDictMap = dictMap;
136 
137     if (!sharedMap->hasPrevious()) {
138       break;
139     }
140     sharedMap = sharedMap->asNormal()->previous();
141     sharedLength = PropMap::Capacity;
142   }
143 
144   return lastDictMap;
145 }
146 
PropMapChildReadBarrier(SharedPropMap * parent,SharedPropMap * child)147 static MOZ_ALWAYS_INLINE SharedPropMap* PropMapChildReadBarrier(
148     SharedPropMap* parent, SharedPropMap* child) {
149   JS::Zone* zone = child->zone();
150   if (zone->needsIncrementalBarrier()) {
151     // We need a read barrier for the map tree, since these are weak
152     // pointers.
153     ReadBarrier(child);
154     return child;
155   }
156 
157   if (MOZ_LIKELY(!zone->isGCSweepingOrCompacting() ||
158                  !IsAboutToBeFinalizedUnbarriered(&child))) {
159     return child;
160   }
161 
162   // The map we've found is unreachable and due to be finalized, so
163   // remove our weak reference to it and don't use it.
164   MOZ_ASSERT(parent->isMarkedAny());
165   parent->removeChild(zone->runtimeFromMainThread()->defaultFreeOp(), child);
166 
167   return nullptr;
168 }
169 
lookupChild(uint32_t length,HandleId id,PropertyInfo prop)170 SharedPropMap* SharedPropMap::lookupChild(uint32_t length, HandleId id,
171                                           PropertyInfo prop) {
172   MOZ_ASSERT(length > 0);
173 
174   SharedChildrenPtr children = treeDataRef().children;
175   if (children.isNone()) {
176     return nullptr;
177   }
178 
179   if (!hasChildrenSet()) {
180     SharedPropMapAndIndex prevChild = children.toSingleChild();
181     if (prevChild.index() == length - 1) {
182       SharedPropMap* child = prevChild.map();
183       uint32_t newPropIndex = indexOfNextProperty(length - 1);
184       if (child->matchProperty(newPropIndex, id, prop)) {
185         return PropMapChildReadBarrier(this, child);
186       }
187     }
188     return nullptr;
189   }
190 
191   SharedChildrenSet* set = children.toChildrenSet();
192   SharedChildrenHasher::Lookup lookup(id, prop, length - 1);
193   if (auto p = set->lookup(lookup)) {
194     MOZ_ASSERT(p->index() == length - 1);
195     SharedPropMap* child = p->map();
196     return PropMapChildReadBarrier(this, child);
197   }
198   return nullptr;
199 }
200 
addChild(JSContext * cx,SharedPropMapAndIndex child,HandleId id,PropertyInfo prop)201 bool SharedPropMap::addChild(JSContext* cx, SharedPropMapAndIndex child,
202                              HandleId id, PropertyInfo prop) {
203   SharedPropMap* childMap = child.map();
204 
205 #ifdef DEBUG
206   // If the parent map was full, the child map must have the parent as
207   // |previous| map. Else, the parent and child have the same |previous| map.
208   if (childMap->hasPrevious()) {
209     if (child.index() == PropMap::Capacity - 1) {
210       MOZ_ASSERT(childMap->asLinked()->previous() == this);
211     } else {
212       MOZ_ASSERT(childMap->asLinked()->previous() == asLinked()->previous());
213     }
214   } else {
215     MOZ_ASSERT(!hasPrevious());
216   }
217 #endif
218 
219   SharedChildrenPtr& childrenRef = treeDataRef().children;
220 
221   if (childrenRef.isNone()) {
222     childrenRef.setSingleChild(child);
223     childMap->treeDataRef().setParent(this, child.index());
224     return true;
225   }
226 
227   SharedChildrenHasher::Lookup lookup(id, prop, child.index());
228 
229   if (hasChildrenSet()) {
230     if (!childrenRef.toChildrenSet()->putNew(lookup, child)) {
231       ReportOutOfMemory(cx);
232       return false;
233     }
234   } else {
235     auto hash = MakeUnique<SharedChildrenSet>();
236     if (!hash || !hash->reserve(2)) {
237       ReportOutOfMemory(cx);
238       return false;
239     }
240     SharedPropMapAndIndex firstChild = childrenRef.toSingleChild();
241     SharedPropMap* firstChildMap = firstChild.map();
242     uint32_t firstChildIndex = indexOfNextProperty(firstChild.index());
243     SharedChildrenHasher::Lookup lookupFirst(
244         firstChildMap->getPropertyInfoWithKey(firstChildIndex),
245         firstChild.index());
246     hash->putNewInfallible(lookupFirst, firstChild);
247     hash->putNewInfallible(lookup, child);
248 
249     childrenRef.setChildrenSet(hash.release());
250     setHasChildrenSet();
251     AddCellMemory(this, sizeof(SharedChildrenSet), MemoryUse::PropMapChildren);
252   }
253 
254   childMap->treeDataRef().setParent(this, child.index());
255   return true;
256 }
257 
258 // static
addProperty(JSContext * cx,const JSClass * clasp,MutableHandle<SharedPropMap * > map,uint32_t * mapLength,HandleId id,PropertyFlags flags,ObjectFlags * objectFlags,uint32_t * slot)259 bool SharedPropMap::addProperty(JSContext* cx, const JSClass* clasp,
260                                 MutableHandle<SharedPropMap*> map,
261                                 uint32_t* mapLength, HandleId id,
262                                 PropertyFlags flags, ObjectFlags* objectFlags,
263                                 uint32_t* slot) {
264   MOZ_ASSERT(!flags.isCustomDataProperty());
265 
266   *slot = SharedPropMap::slotSpan(clasp, map, *mapLength);
267 
268   if (MOZ_UNLIKELY(*slot > SHAPE_MAXIMUM_SLOT)) {
269     ReportAllocationOverflow(cx);
270     return false;
271   }
272 
273   *objectFlags =
274       GetObjectFlagsForNewProperty(clasp, *objectFlags, id, flags, cx);
275 
276   PropertyInfo prop = PropertyInfo(flags, *slot);
277   return addPropertyInternal(cx, map, mapLength, id, prop);
278 }
279 
280 // static
addPropertyInReservedSlot(JSContext * cx,const JSClass * clasp,MutableHandle<SharedPropMap * > map,uint32_t * mapLength,HandleId id,PropertyFlags flags,uint32_t slot,ObjectFlags * objectFlags)281 bool SharedPropMap::addPropertyInReservedSlot(
282     JSContext* cx, const JSClass* clasp, MutableHandle<SharedPropMap*> map,
283     uint32_t* mapLength, HandleId id, PropertyFlags flags, uint32_t slot,
284     ObjectFlags* objectFlags) {
285   MOZ_ASSERT(!flags.isCustomDataProperty());
286 
287   MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(clasp));
288   MOZ_ASSERT_IF(map, map->lastUsedSlot(*mapLength) < slot);
289 
290   *objectFlags =
291       GetObjectFlagsForNewProperty(clasp, *objectFlags, id, flags, cx);
292 
293   PropertyInfo prop = PropertyInfo(flags, slot);
294   return addPropertyInternal(cx, map, mapLength, id, prop);
295 }
296 
297 // static
addPropertyWithKnownSlot(JSContext * cx,const JSClass * clasp,MutableHandle<SharedPropMap * > map,uint32_t * mapLength,HandleId id,PropertyFlags flags,uint32_t slot,ObjectFlags * objectFlags)298 bool SharedPropMap::addPropertyWithKnownSlot(JSContext* cx,
299                                              const JSClass* clasp,
300                                              MutableHandle<SharedPropMap*> map,
301                                              uint32_t* mapLength, HandleId id,
302                                              PropertyFlags flags, uint32_t slot,
303                                              ObjectFlags* objectFlags) {
304   MOZ_ASSERT(!flags.isCustomDataProperty());
305 
306   if (MOZ_UNLIKELY(slot < JSCLASS_RESERVED_SLOTS(clasp))) {
307     return addPropertyInReservedSlot(cx, clasp, map, mapLength, id, flags, slot,
308                                      objectFlags);
309   }
310 
311   MOZ_ASSERT(slot == SharedPropMap::slotSpan(clasp, map, *mapLength));
312   MOZ_RELEASE_ASSERT(slot <= SHAPE_MAXIMUM_SLOT);
313 
314   *objectFlags =
315       GetObjectFlagsForNewProperty(clasp, *objectFlags, id, flags, cx);
316 
317   PropertyInfo prop = PropertyInfo(flags, slot);
318   return addPropertyInternal(cx, map, mapLength, id, prop);
319 }
320 
321 // static
addCustomDataProperty(JSContext * cx,const JSClass * clasp,MutableHandle<SharedPropMap * > map,uint32_t * mapLength,HandleId id,PropertyFlags flags,ObjectFlags * objectFlags)322 bool SharedPropMap::addCustomDataProperty(JSContext* cx, const JSClass* clasp,
323                                           MutableHandle<SharedPropMap*> map,
324                                           uint32_t* mapLength, HandleId id,
325                                           PropertyFlags flags,
326                                           ObjectFlags* objectFlags) {
327   MOZ_ASSERT(flags.isCustomDataProperty());
328 
329   // Custom data properties don't have a slot. Copy the last property's slot
330   // number to simplify the implementation of SharedPropMap::slotSpan.
331   uint32_t slot = map ? map->lastUsedSlot(*mapLength) : SHAPE_INVALID_SLOT;
332 
333   *objectFlags =
334       GetObjectFlagsForNewProperty(clasp, *objectFlags, id, flags, cx);
335 
336   PropertyInfo prop = PropertyInfo(flags, slot);
337   return addPropertyInternal(cx, map, mapLength, id, prop);
338 }
339 
340 // static
addPropertyInternal(JSContext * cx,MutableHandle<SharedPropMap * > map,uint32_t * mapLength,HandleId id,PropertyInfo prop)341 bool SharedPropMap::addPropertyInternal(JSContext* cx,
342                                         MutableHandle<SharedPropMap*> map,
343                                         uint32_t* mapLength, HandleId id,
344                                         PropertyInfo prop) {
345   if (!map) {
346     // Adding the first property.
347     MOZ_ASSERT(*mapLength == 0);
348     map.set(SharedPropMap::createInitial(cx, id, prop));
349     if (!map) {
350       return false;
351     }
352     *mapLength = 1;
353     return true;
354   }
355 
356   MOZ_ASSERT(*mapLength > 0);
357 
358   if (*mapLength < PropMap::Capacity) {
359     // Use the next map entry if possible.
360     if (!map->hasKey(*mapLength)) {
361       if (map->canHaveTable()) {
362         JS::AutoCheckCannotGC nogc;
363         if (PropMapTable* table = map->asLinked()->maybeTable(nogc)) {
364           if (!table->add(cx, id, PropMapAndIndex(map, *mapLength))) {
365             return false;
366           }
367         }
368       }
369       map->initProperty(*mapLength, id, prop);
370       *mapLength += 1;
371       return true;
372     }
373     if (map->matchProperty(*mapLength, id, prop)) {
374       *mapLength += 1;
375       return true;
376     }
377 
378     // The next entry can't be used so look up or create a child map. The child
379     // map is a clone of this map up to mapLength, with the new property stored
380     // as the next entry.
381 
382     if (SharedPropMap* child = map->lookupChild(*mapLength, id, prop)) {
383       map.set(child);
384       *mapLength += 1;
385       return true;
386     }
387 
388     SharedPropMap* child = SharedPropMap::clone(cx, map, *mapLength);
389     if (!child) {
390       return false;
391     }
392     child->initProperty(*mapLength, id, prop);
393 
394     SharedPropMapAndIndex childEntry(child, *mapLength - 1);
395     if (!map->addChild(cx, childEntry, id, prop)) {
396       return false;
397     }
398 
399     map.set(child);
400     *mapLength += 1;
401     return true;
402   }
403 
404   // This map is full so look up or create a child map.
405   MOZ_ASSERT(*mapLength == PropMap::Capacity);
406 
407   if (SharedPropMap* child = map->lookupChild(*mapLength, id, prop)) {
408     map.set(child);
409     *mapLength = 1;
410     return true;
411   }
412 
413   SharedPropMap* child = SharedPropMap::create(cx, map, id, prop);
414   if (!child) {
415     return false;
416   }
417 
418   SharedPropMapAndIndex childEntry(child, PropMap::Capacity - 1);
419   if (!map->addChild(cx, childEntry, id, prop)) {
420     return false;
421   }
422 
423   // As an optimization, pass the table to the new child map, unless adding the
424   // property to it OOMs. Measurements indicate this gets rid of a large number
425   // of PropMapTable allocations because we don't need to create a second table
426   // when the parent map won't be used again as last map.
427   if (map->canHaveTable()) {
428     JS::AutoCheckCannotGC nogc;
429     if (PropMapTable* table = map->asLinked()->maybeTable(nogc)) {
430       // Trigger a pre-barrier on the parent map to appease the pre-barrier
431       // verifier, because edges from the table are disappearing (even though
432       // these edges are strictly redundant with the |previous| maps).
433       gc::PreWriteBarrier(map.get());
434       if (table->add(cx, id, PropMapAndIndex(child, 0))) {
435         map->asLinked()->handOffTableTo(child->asLinked());
436       } else {
437         cx->recoverFromOutOfMemory();
438       }
439     }
440   }
441 
442   map.set(child);
443   *mapLength = 1;
444   return true;
445 }
446 
ComputeFlagsForSealOrFreeze(PropertyKey key,PropertyFlags flags,IntegrityLevel level)447 static PropertyFlags ComputeFlagsForSealOrFreeze(PropertyKey key,
448                                                  PropertyFlags flags,
449                                                  IntegrityLevel level) {
450   // Private fields are not visible to SetIntegrityLevel.
451   if (key.isSymbol() && key.toSymbol()->isPrivateName()) {
452     return flags;
453   }
454 
455   // Make all properties non-configurable; if freezing, make data properties
456   // read-only.
457   flags.clearFlag(PropertyFlag::Configurable);
458   if (level == IntegrityLevel::Frozen && flags.isDataDescriptor()) {
459     flags.clearFlag(PropertyFlag::Writable);
460   }
461 
462   return flags;
463 }
464 
465 // static
freezeOrSealProperties(JSContext * cx,IntegrityLevel level,const JSClass * clasp,MutableHandle<SharedPropMap * > map,uint32_t mapLength,ObjectFlags * objectFlags)466 bool SharedPropMap::freezeOrSealProperties(JSContext* cx, IntegrityLevel level,
467                                            const JSClass* clasp,
468                                            MutableHandle<SharedPropMap*> map,
469                                            uint32_t mapLength,
470                                            ObjectFlags* objectFlags) {
471   // Add all maps to a Vector so we can iterate over them in reverse order
472   // (property definition order).
473   JS::RootedVector<SharedPropMap*> maps(cx);
474   {
475     SharedPropMap* curMap = map;
476     while (true) {
477       if (!maps.append(curMap)) {
478         return false;
479       }
480       if (!curMap->hasPrevious()) {
481         break;
482       }
483       curMap = curMap->asNormal()->previous();
484     }
485   }
486 
487   // Build a new SharedPropMap by adding each property with the changed
488   // attributes.
489   Rooted<SharedPropMap*> newMap(cx);
490   uint32_t newMapLength = 0;
491 
492   Rooted<PropertyKey> key(cx);
493   Rooted<SharedPropMap*> curMap(cx);
494 
495   for (size_t i = maps.length(); i > 0; i--) {
496     curMap = maps[i - 1];
497     uint32_t len = (i == 1) ? mapLength : PropMap::Capacity;
498 
499     for (uint32_t j = 0; j < len; j++) {
500       key = curMap->getKey(j);
501       PropertyInfo prop = curMap->getPropertyInfo(j);
502       PropertyFlags flags =
503           ComputeFlagsForSealOrFreeze(key, prop.flags(), level);
504 
505       if (prop.isCustomDataProperty()) {
506         if (!addCustomDataProperty(cx, clasp, &newMap, &newMapLength, key,
507                                    flags, objectFlags)) {
508           return false;
509         }
510       } else {
511         if (!addPropertyWithKnownSlot(cx, clasp, &newMap, &newMapLength, key,
512                                       flags, prop.slot(), objectFlags)) {
513           return false;
514         }
515       }
516     }
517   }
518 
519   map.set(newMap);
520   MOZ_ASSERT(newMapLength == mapLength);
521   return true;
522 }
523 
handOffTableTo(LinkedPropMap * next)524 void LinkedPropMap::handOffTableTo(LinkedPropMap* next) {
525   MOZ_ASSERT(hasTable());
526   MOZ_ASSERT(!next->hasTable());
527 
528   next->data_.table = data_.table;
529   data_.table = nullptr;
530 
531   // Note: for tables currently only sizeof(PropMapTable) is tracked.
532   RemoveCellMemory(this, sizeof(PropMapTable), MemoryUse::PropMapTable);
533   AddCellMemory(next, sizeof(PropMapTable), MemoryUse::PropMapTable);
534 }
535 
handOffLastMapStateTo(DictionaryPropMap * newLast)536 void DictionaryPropMap::handOffLastMapStateTo(DictionaryPropMap* newLast) {
537   // A dictionary object's last map contains the table, slot freeList, and
538   // holeCount. These fields always have their initial values for non-last maps.
539 
540   MOZ_ASSERT(this != newLast);
541 
542   if (asLinked()->hasTable()) {
543     asLinked()->handOffTableTo(newLast->asLinked());
544   }
545 
546   MOZ_ASSERT(newLast->freeList_ == SHAPE_INVALID_SLOT);
547   newLast->freeList_ = freeList_;
548   freeList_ = SHAPE_INVALID_SLOT;
549 
550   MOZ_ASSERT(newLast->holeCount_ == 0);
551   newLast->holeCount_ = holeCount_;
552   holeCount_ = 0;
553 }
554 
555 // static
addProperty(JSContext * cx,const JSClass * clasp,MutableHandle<DictionaryPropMap * > map,uint32_t * mapLength,HandleId id,PropertyFlags flags,uint32_t slot,ObjectFlags * objectFlags)556 bool DictionaryPropMap::addProperty(JSContext* cx, const JSClass* clasp,
557                                     MutableHandle<DictionaryPropMap*> map,
558                                     uint32_t* mapLength, HandleId id,
559                                     PropertyFlags flags, uint32_t slot,
560                                     ObjectFlags* objectFlags) {
561   MOZ_ASSERT(map);
562 
563   *objectFlags =
564       GetObjectFlagsForNewProperty(clasp, *objectFlags, id, flags, cx);
565   PropertyInfo prop = PropertyInfo(flags, slot);
566 
567   if (*mapLength < PropMap::Capacity) {
568     JS::AutoCheckCannotGC nogc;
569     if (PropMapTable* table = map->asLinked()->maybeTable(nogc)) {
570       if (!table->add(cx, id, PropMapAndIndex(map, *mapLength))) {
571         return false;
572       }
573     }
574     map->initProperty(*mapLength, id, prop);
575     *mapLength += 1;
576     return true;
577   }
578 
579   DictionaryPropMap* newMap = Allocate<DictionaryPropMap>(cx);
580   if (!newMap) {
581     return false;
582   }
583   new (newMap) DictionaryPropMap(map, id, prop);
584 
585   JS::AutoCheckCannotGC nogc;
586   if (PropMapTable* table = map->asLinked()->maybeTable(nogc)) {
587     if (!table->add(cx, id, PropMapAndIndex(newMap, 0))) {
588       return false;
589     }
590   }
591 
592   MOZ_ASSERT(newMap->previous() == map);
593   map->handOffLastMapStateTo(newMap);
594 
595   map.set(newMap);
596   *mapLength = 1;
597   return true;
598 }
599 
changeProperty(JSContext * cx,const JSClass * clasp,uint32_t index,PropertyFlags flags,uint32_t slot,ObjectFlags * objectFlags)600 void DictionaryPropMap::changeProperty(JSContext* cx, const JSClass* clasp,
601                                        uint32_t index, PropertyFlags flags,
602                                        uint32_t slot,
603                                        ObjectFlags* objectFlags) {
604   MOZ_ASSERT(hasKey(index));
605   *objectFlags = GetObjectFlagsForNewProperty(clasp, *objectFlags,
606                                               getKey(index), flags, cx);
607   linkedData_.propInfos[index] = PropertyInfo(flags, slot);
608 }
609 
freezeOrSealProperties(JSContext * cx,IntegrityLevel level,const JSClass * clasp,uint32_t mapLength,ObjectFlags * objectFlags)610 void DictionaryPropMap::freezeOrSealProperties(JSContext* cx,
611                                                IntegrityLevel level,
612                                                const JSClass* clasp,
613                                                uint32_t mapLength,
614                                                ObjectFlags* objectFlags) {
615   DictionaryPropMap* curMap = this;
616   do {
617     for (uint32_t i = 0; i < mapLength; i++) {
618       if (!curMap->hasKey(i)) {
619         continue;
620       }
621       PropertyKey key = curMap->getKey(i);
622       PropertyFlags flags = curMap->getPropertyInfo(i).flags();
623       flags = ComputeFlagsForSealOrFreeze(key, flags, level);
624       curMap->changePropertyFlags(cx, clasp, i, flags, objectFlags);
625     }
626     curMap = curMap->previous();
627     mapLength = PropMap::Capacity;
628   } while (curMap);
629 }
630 
631 // static
skipTrailingHoles(MutableHandle<DictionaryPropMap * > map,uint32_t * mapLength)632 void DictionaryPropMap::skipTrailingHoles(MutableHandle<DictionaryPropMap*> map,
633                                           uint32_t* mapLength) {
634   // After removing a property, rewind map/mapLength so that the last property
635   // is not a hole. This ensures accessing the last property of a map can always
636   // be done without checking for holes.
637 
638   while (true) {
639     MOZ_ASSERT(*mapLength > 0);
640     do {
641       if (map->hasKey(*mapLength - 1)) {
642         return;
643       }
644       map->decHoleCount();
645       *mapLength -= 1;
646     } while (*mapLength > 0);
647 
648     if (!map->previous()) {
649       // The dictionary map is empty, return the initial map with mapLength 0.
650       MOZ_ASSERT(*mapLength == 0);
651       MOZ_ASSERT(map->holeCount_ == 0);
652       return;
653     }
654 
655     map->handOffLastMapStateTo(map->previous());
656     map.set(map->previous());
657     *mapLength = PropMap::Capacity;
658   }
659 }
660 
661 // static
removeProperty(JSContext * cx,MutableHandle<DictionaryPropMap * > map,uint32_t * mapLength,PropMapTable * table,PropMapTable::Ptr & ptr)662 void DictionaryPropMap::removeProperty(JSContext* cx,
663                                        MutableHandle<DictionaryPropMap*> map,
664                                        uint32_t* mapLength, PropMapTable* table,
665                                        PropMapTable::Ptr& ptr) {
666   MOZ_ASSERT(map);
667   MOZ_ASSERT(*mapLength > 0);
668 
669   JS::AutoCheckCannotGC nogc;
670   MOZ_ASSERT(map->asLinked()->maybeTable(nogc) == table);
671 
672   bool removingLast = (map == ptr->map() && *mapLength - 1 == ptr->index());
673   ptr->map()->asDictionary()->clearProperty(ptr->index());
674   map->incHoleCount();
675   table->remove(ptr);
676 
677   if (removingLast) {
678     skipTrailingHoles(map, mapLength);
679   }
680   maybeCompact(cx, map, mapLength);
681 }
682 
683 // static
densifyElements(JSContext * cx,MutableHandle<DictionaryPropMap * > map,uint32_t * mapLength,NativeObject * obj)684 void DictionaryPropMap::densifyElements(JSContext* cx,
685                                         MutableHandle<DictionaryPropMap*> map,
686                                         uint32_t* mapLength,
687                                         NativeObject* obj) {
688   MOZ_ASSERT(map);
689   MOZ_ASSERT(*mapLength > 0);
690 
691   JS::AutoCheckCannotGC nogc;
692   PropMapTable* table = map->asLinked()->maybeTable(nogc);
693 
694   DictionaryPropMap* currentMap = map;
695   uint32_t currentLen = *mapLength;
696   do {
697     for (uint32_t i = 0; i < currentLen; i++) {
698       PropertyKey key = currentMap->getKey(i);
699       uint32_t index;
700       if (!IdIsIndex(key, &index)) {
701         continue;
702       }
703 
704       // The caller must have checked all sparse elements are plain data
705       // properties.
706       PropertyInfo prop = currentMap->getPropertyInfo(i);
707       MOZ_ASSERT(prop.flags() == PropertyFlags::defaultDataPropFlags);
708 
709       uint32_t slot = prop.slot();
710       Value value = obj->getSlot(slot);
711       obj->setDenseElement(index, value);
712       obj->freeDictionarySlot(slot);
713 
714       if (table) {
715         PropMapTable::Ptr p = table->lookupRaw(key);
716         MOZ_ASSERT(p);
717         table->remove(p);
718       }
719 
720       currentMap->clearProperty(i);
721       map->incHoleCount();
722     }
723     currentMap = currentMap->previous();
724     currentLen = PropMap::Capacity;
725   } while (currentMap);
726 
727   skipTrailingHoles(map, mapLength);
728   maybeCompact(cx, map, mapLength);
729 }
730 
maybeCompact(JSContext * cx,MutableHandle<DictionaryPropMap * > map,uint32_t * mapLength)731 void DictionaryPropMap::maybeCompact(JSContext* cx,
732                                      MutableHandle<DictionaryPropMap*> map,
733                                      uint32_t* mapLength) {
734   // If there are no holes, there's nothing to compact.
735   if (map->holeCount_ == 0) {
736     return;
737   }
738 
739   JS::AutoCheckCannotGC nogc;
740   PropMapTable* table = map->asLinked()->ensureTable(cx, nogc);
741   if (!table) {
742     // Compacting is optional so just return.
743     cx->recoverFromOutOfMemory();
744     return;
745   }
746 
747   // Heuristic: only compact if the number of holes >= the number of (non-hole)
748   // entries.
749   if (map->holeCount_ < table->entryCount()) {
750     return;
751   }
752 
753   // Add all dictionary maps to a Vector so that we can iterate over them in
754   // reverse order (property definition order). If appending to the Vector OOMs,
755   // just return because compacting is optional.
756   Vector<DictionaryPropMap*, 32, SystemAllocPolicy> maps;
757   for (DictionaryPropMap* curMap = map; curMap; curMap = curMap->previous()) {
758     if (!maps.append(curMap)) {
759       return;
760     }
761   }
762 
763   // Use two cursors: readMapCursor/readIndexCursor iterates over all properties
764   // starting at the first one, to search for the next non-hole entry.
765   // writeMapCursor/writeIndexCursor is used to write all non-hole keys.
766   //
767   // At the start of the loop, these cursors point to the next property slot to
768   // read/write.
769 
770   size_t readMapCursorVectorIndex = maps.length() - 1;
771   DictionaryPropMap* readMapCursor = maps[readMapCursorVectorIndex];
772   uint32_t readIndexCursor = 0;
773 
774   size_t writeMapCursorVectorIndex = readMapCursorVectorIndex;
775   DictionaryPropMap* writeMapCursor = readMapCursor;
776   uint32_t writeIndexCursor = 0;
777 
778   mozilla::DebugOnly<uint32_t> numHoles = 0;
779 
780   while (true) {
781     if (readMapCursor->hasKey(readIndexCursor)) {
782       // Found a non-hole entry, copy it to its new position and update the
783       // PropMapTable to point to this new entry. Only do this if the read and
784       // write positions are different from each other.
785       if (readMapCursor != writeMapCursor ||
786           readIndexCursor != writeIndexCursor) {
787         PropertyKey key = readMapCursor->getKey(readIndexCursor);
788         auto p = table->lookupRaw(key);
789         MOZ_ASSERT(p);
790         MOZ_ASSERT(p->map() == readMapCursor);
791         MOZ_ASSERT(p->index() == readIndexCursor);
792 
793         writeMapCursor->keys_[writeIndexCursor] = key;
794         writeMapCursor->linkedData_.propInfos[writeIndexCursor] =
795             readMapCursor->linkedData_.propInfos[readIndexCursor];
796 
797         PropMapAndIndex newEntry(writeMapCursor, writeIndexCursor);
798         table->replaceEntry(p, key, newEntry);
799       }
800       // Advance the write cursor.
801       writeIndexCursor++;
802       if (writeIndexCursor == PropMap::Capacity) {
803         MOZ_ASSERT(writeMapCursorVectorIndex > 0);
804         writeMapCursorVectorIndex--;
805         writeMapCursor = maps[writeMapCursorVectorIndex];
806         writeIndexCursor = 0;
807       }
808     } else {
809       numHoles++;
810     }
811     // Advance the read cursor. If there are no more maps to read from, we're
812     // done.
813     readIndexCursor++;
814     if (readIndexCursor == PropMap::Capacity) {
815       if (readMapCursorVectorIndex == 0) {
816         break;
817       }
818       readMapCursorVectorIndex--;
819       readMapCursor = maps[readMapCursorVectorIndex];
820       readIndexCursor = 0;
821     }
822   }
823 
824   // Sanity check: the read cursor skipped holes between properties and holes
825   // at the end of the last map (these are not included in holeCount_).
826   MOZ_ASSERT(map->holeCount_ + (PropMap::Capacity - *mapLength) == numHoles);
827 
828   // The write cursor points to the next available slot. If this is at the start
829   // of a new map, use the previous map (which must be full) instead.
830   if (writeIndexCursor == 0 && writeMapCursor->previous()) {
831     writeMapCursor = writeMapCursor->previous();
832     *mapLength = PropMap::Capacity;
833   } else {
834     *mapLength = writeIndexCursor;
835   }
836 
837   // Ensure the last map does not have any keys in [mapLength, Capacity).
838   for (uint32_t i = *mapLength; i < PropMap::Capacity; i++) {
839     writeMapCursor->clearProperty(i);
840   }
841 
842   if (writeMapCursor != map) {
843     map->handOffLastMapStateTo(writeMapCursor);
844     map.set(writeMapCursor);
845   }
846   map->holeCount_ = 0;
847 
848   MOZ_ASSERT(*mapLength <= PropMap::Capacity);
849   MOZ_ASSERT_IF(*mapLength == 0, !map->previous());
850   MOZ_ASSERT_IF(!map->previous(), table->entryCount() == *mapLength);
851 }
852 
fixupAfterMovingGC()853 void SharedPropMap::fixupAfterMovingGC() {
854   SharedChildrenPtr& childrenRef = treeDataRef().children;
855   if (childrenRef.isNone()) {
856     return;
857   }
858 
859   if (!hasChildrenSet()) {
860     SharedPropMapAndIndex child = childrenRef.toSingleChild();
861     if (gc::IsForwarded(child.map())) {
862       child = SharedPropMapAndIndex(gc::Forwarded(child.map()), child.index());
863       childrenRef.setSingleChild(child);
864     }
865     return;
866   }
867 
868   SharedChildrenSet* set = childrenRef.toChildrenSet();
869   for (SharedChildrenSet::Enum e(*set); !e.empty(); e.popFront()) {
870     SharedPropMapAndIndex child = e.front();
871     if (IsForwarded(child.map())) {
872       child = SharedPropMapAndIndex(Forwarded(child.map()), child.index());
873       e.mutableFront() = child;
874     }
875   }
876 }
877 
removeChild(JSFreeOp * fop,SharedPropMap * child)878 void SharedPropMap::removeChild(JSFreeOp* fop, SharedPropMap* child) {
879   SharedPropMapAndIndex& parentRef = child->treeDataRef().parent;
880   MOZ_ASSERT(parentRef.map() == this);
881 
882   uint32_t index = parentRef.index();
883   parentRef.setNone();
884 
885   SharedChildrenPtr& childrenRef = treeDataRef().children;
886   MOZ_ASSERT(!childrenRef.isNone());
887 
888   if (!hasChildrenSet()) {
889     MOZ_ASSERT(childrenRef.toSingleChild().map() == child);
890     MOZ_ASSERT(childrenRef.toSingleChild().index() == index);
891     childrenRef.setNone();
892     return;
893   }
894 
895   SharedChildrenSet* set = childrenRef.toChildrenSet();
896   {
897     uint32_t nextIndex = SharedPropMap::indexOfNextProperty(index);
898     SharedChildrenHasher::Lookup lookup(
899         child->getPropertyInfoWithKey(nextIndex), index);
900     auto p = set->lookup(lookup);
901     MOZ_ASSERT(p, "Child must be in children set");
902     set->remove(p);
903   }
904 
905   MOZ_ASSERT(set->count() >= 1);
906 
907   if (set->count() == 1) {
908     // Convert from set form back to single child form.
909     SharedChildrenSet::Range r = set->all();
910     SharedPropMapAndIndex remainingChild = r.front();
911     childrenRef.setSingleChild(remainingChild);
912     clearHasChildrenSet();
913     fop->delete_(this, set, MemoryUse::PropMapChildren);
914   }
915 }
916 
purgeTable(JSFreeOp * fop)917 void LinkedPropMap::purgeTable(JSFreeOp* fop) {
918   MOZ_ASSERT(hasTable());
919   fop->delete_(this, data_.table, MemoryUse::PropMapTable);
920   data_.table = nullptr;
921 }
922 
approximateEntryCount() const923 uint32_t PropMap::approximateEntryCount() const {
924   // Returns a number that's guaranteed to tbe >= the exact number of properties
925   // in this map (including previous maps). This is used to reserve space in the
926   // HashSet when allocating a table for this map.
927 
928   const PropMap* map = this;
929   uint32_t count = 0;
930   JS::AutoCheckCannotGC nogc;
931   while (true) {
932     if (!map->hasPrevious()) {
933       return count + PropMap::Capacity;
934     }
935     if (PropMapTable* table = map->asLinked()->maybeTable(nogc)) {
936       return count + table->entryCount();
937     }
938     count += PropMap::Capacity;
939     map = map->asLinked()->previous();
940   }
941 }
942 
init(JSContext * cx,LinkedPropMap * map)943 bool PropMapTable::init(JSContext* cx, LinkedPropMap* map) {
944   if (!set_.reserve(map->approximateEntryCount())) {
945     ReportOutOfMemory(cx);
946     return false;
947   }
948 
949   PropMap* curMap = map;
950   while (true) {
951     for (uint32_t i = 0; i < PropMap::Capacity; i++) {
952       if (curMap->hasKey(i)) {
953         PropertyKey key = curMap->getKey(i);
954         set_.putNewInfallible(key, PropMapAndIndex(curMap, i));
955       }
956     }
957     if (!curMap->hasPrevious()) {
958       break;
959     }
960     curMap = curMap->asLinked()->previous();
961   }
962 
963   return true;
964 }
965 
trace(JSTracer * trc)966 void PropMapTable::trace(JSTracer* trc) {
967   purgeCache();
968 
969   for (Set::Enum e(set_); !e.empty(); e.popFront()) {
970     PropMap* map = e.front().map();
971     TraceManuallyBarrieredEdge(trc, &map, "PropMapTable map");
972     if (map != e.front().map()) {
973       e.mutableFront() = PropMapAndIndex(map, e.front().index());
974     }
975   }
976 }
977 
978 #ifdef JSGC_HASH_TABLE_CHECKS
checkAfterMovingGC()979 void PropMapTable::checkAfterMovingGC() {
980   for (Set::Enum e(set_); !e.empty(); e.popFront()) {
981     PropMap* map = e.front().map();
982     MOZ_ASSERT(map);
983     CheckGCThingAfterMovingGC(map);
984 
985     PropertyKey key = map->getKey(e.front().index());
986     MOZ_RELEASE_ASSERT(!key.isVoid());
987 
988     auto p = lookupRaw(key);
989     MOZ_RELEASE_ASSERT(p.found() && *p == e.front());
990   }
991 }
992 #endif
993 
994 #ifdef DEBUG
canSkipMarkingTable()995 bool LinkedPropMap::canSkipMarkingTable() {
996   if (!hasTable()) {
997     return true;
998   }
999 
1000   PropMapTable* table = data_.table;
1001   uint32_t count = 0;
1002 
1003   PropMap* map = this;
1004   while (true) {
1005     for (uint32_t i = 0; i < Capacity; i++) {
1006       if (map->hasKey(i)) {
1007         PropertyKey key = map->getKey(i);
1008         PropMapTable::Ptr p = table->lookupRaw(key);
1009         MOZ_ASSERT(*p == PropMapAndIndex(map, i));
1010         count++;
1011       }
1012     }
1013     if (!map->hasPrevious()) {
1014       break;
1015     }
1016     map = map->asLinked()->previous();
1017   }
1018 
1019   return count == table->entryCount();
1020 }
1021 #endif
1022 
createTable(JSContext * cx)1023 bool LinkedPropMap::createTable(JSContext* cx) {
1024   MOZ_ASSERT(canHaveTable());
1025   MOZ_ASSERT(!hasTable());
1026 
1027   UniquePtr<PropMapTable> table = cx->make_unique<PropMapTable>();
1028   if (!table) {
1029     return false;
1030   }
1031 
1032   if (!table->init(cx, this)) {
1033     return false;
1034   }
1035 
1036   data_.table = table.release();
1037   // TODO: The contents of PropMapTable is not currently tracked, only the
1038   // object itself.
1039   AddCellMemory(this, sizeof(PropMapTable), MemoryUse::PropMapTable);
1040   return true;
1041 }
1042 
1043 #ifdef DEBUG
dump(js::GenericPrinter & out) const1044 void PropMap::dump(js::GenericPrinter& out) const {
1045   out.printf("map @ 0x%p\n", this);
1046   out.printf("previous: 0x%p\n",
1047              hasPrevious() ? asLinked()->previous() : nullptr);
1048 
1049   if (canHaveTable()) {
1050     out.printf("table: 0x%p\n", asLinked()->data_.table);
1051   } else {
1052     out.printf("table: (too small for table)\n");
1053   }
1054 
1055   if (isShared()) {
1056     out.printf("type: shared\n");
1057     out.printf("  compact: %s\n", isCompact() ? "yes" : "no");
1058     SharedPropMapAndIndex parent = asShared()->treeDataRef().parent;
1059     if (parent.isNone()) {
1060       out.printf("  parent: (none)\n");
1061     } else {
1062       out.printf("  parent: 0x%p [%u]\n", parent.map(), parent.index());
1063     }
1064   } else {
1065     const DictionaryPropMap* dictMap = asDictionary();
1066     out.printf("type: dictionary\n");
1067     out.printf("  freeList: %u\n", dictMap->freeList_);
1068     out.printf("  holeCount: %u\n", dictMap->holeCount_);
1069   }
1070 
1071   out.printf("properties:\n");
1072   for (uint32_t i = 0; i < Capacity; i++) {
1073     out.printf("  %u: ", i);
1074 
1075     if (!hasKey(i)) {
1076       out.printf("(empty)\n");
1077       continue;
1078     }
1079 
1080     PropertyKey key = getKey(i);
1081     if (key.isInt()) {
1082       out.printf("[%d]", key.toInt());
1083     } else if (key.isAtom()) {
1084       EscapedStringPrinter(out, key.toAtom(), '"');
1085     } else {
1086       MOZ_ASSERT(key.isSymbol());
1087       key.toSymbol()->dump(out);
1088     }
1089 
1090     PropertyInfo prop = getPropertyInfo(i);
1091     out.printf(" slot %u flags 0x%x ", prop.maybeSlot(), prop.flags().toRaw());
1092 
1093     if (!prop.flags().isEmpty()) {
1094       bool first = true;
1095       auto dumpFlag = [&](PropertyFlag flag, const char* name) {
1096         if (!prop.flags().hasFlag(flag)) {
1097           return;
1098         }
1099         if (!first) {
1100           out.putChar(' ');
1101         }
1102         out.put(name);
1103         first = false;
1104       };
1105       out.putChar('(');
1106       dumpFlag(PropertyFlag::Enumerable, "enumerable");
1107       dumpFlag(PropertyFlag::Configurable, "configurable");
1108       dumpFlag(PropertyFlag::Writable, "writable");
1109       dumpFlag(PropertyFlag::AccessorProperty, "accessor");
1110       dumpFlag(PropertyFlag::CustomDataProperty, "custom-data");
1111       out.putChar(')');
1112     }
1113     out.putChar('\n');
1114   }
1115 }
1116 
dump() const1117 void PropMap::dump() const {
1118   Fprinter out(stderr);
1119   dump(out);
1120 }
1121 
checkConsistency(NativeObject * obj) const1122 void PropMap::checkConsistency(NativeObject* obj) const {
1123   const uint32_t mapLength = obj->shape()->propMapLength();
1124   MOZ_ASSERT(mapLength <= PropMap::Capacity);
1125 
1126   JS::AutoCheckCannotGC nogc;
1127   if (isDictionary()) {
1128     // Check dictionary slot free list.
1129     for (uint32_t fslot = asDictionary()->freeList();
1130          fslot != SHAPE_INVALID_SLOT;
1131          fslot = obj->getSlot(fslot).toPrivateUint32()) {
1132       MOZ_ASSERT(fslot < obj->slotSpan());
1133     }
1134 
1135     auto* table = asLinked()->maybeTable(nogc);
1136     const DictionaryPropMap* curMap = asDictionary();
1137     uint32_t numHoles = 0;
1138     do {
1139       // Some fields must only be set for the last dictionary map.
1140       if (curMap != this) {
1141         MOZ_ASSERT(!curMap->asLinked()->hasTable());
1142         MOZ_ASSERT(curMap->holeCount_ == 0);
1143         MOZ_ASSERT(curMap->freeList_ == SHAPE_INVALID_SLOT);
1144       }
1145 
1146       for (uint32_t i = 0; i < PropMap::Capacity; i++) {
1147         if (!curMap->hasKey(i)) {
1148           if (curMap != this || i < mapLength) {
1149             numHoles++;
1150           }
1151           continue;
1152         }
1153 
1154         // The last dictionary map must only have keys up to mapLength.
1155         MOZ_ASSERT_IF(curMap == this, i < mapLength);
1156 
1157         PropertyInfo prop = curMap->getPropertyInfo(i);
1158         MOZ_ASSERT_IF(prop.hasSlot(), prop.slot() < obj->slotSpan());
1159 
1160         // All properties must be in the table.
1161         if (table) {
1162           PropertyKey key = curMap->getKey(i);
1163           auto p = table->lookupRaw(key);
1164           MOZ_ASSERT(p->map() == curMap);
1165           MOZ_ASSERT(p->index() == i);
1166         }
1167       }
1168       curMap = curMap->previous();
1169     } while (curMap);
1170 
1171     MOZ_ASSERT(asDictionary()->holeCount_ == numHoles);
1172     return;
1173   }
1174 
1175   MOZ_ASSERT(mapLength > 0);
1176 
1177   const SharedPropMap* curMap = asShared();
1178   auto* table =
1179       curMap->canHaveTable() ? curMap->asLinked()->maybeTable(nogc) : nullptr;
1180 
1181   // Shared maps without a previous map never have a table.
1182   MOZ_ASSERT_IF(!curMap->hasPrevious(), !curMap->canHaveTable());
1183 
1184   const SharedPropMap* nextMap = nullptr;
1185   mozilla::Maybe<uint32_t> nextSlot;
1186   while (true) {
1187     // Verify numPreviousMaps is set correctly.
1188     MOZ_ASSERT_IF(nextMap && nextMap->numPreviousMaps() != NumPreviousMapsMax,
1189                   curMap->numPreviousMaps() + 1 == nextMap->numPreviousMaps());
1190     MOZ_ASSERT(curMap->hasPrevious() == (curMap->numPreviousMaps() > 0));
1191 
1192     // If a previous map also has a table, it must have fewer entries than the
1193     // last map's table.
1194     if (table && curMap != this && curMap->canHaveTable()) {
1195       if (auto* table2 = curMap->asLinked()->maybeTable(nogc)) {
1196         MOZ_ASSERT(table2->entryCount() < table->entryCount());
1197       }
1198     }
1199 
1200     for (int32_t i = PropMap::Capacity - 1; i >= 0; i--) {
1201       uint32_t index = uint32_t(i);
1202 
1203       // Only the last map can have holes, for entries following mapLength.
1204       if (!curMap->hasKey(index)) {
1205         MOZ_ASSERT(index > 0);
1206         MOZ_ASSERT(curMap == this);
1207         MOZ_ASSERT(index >= mapLength);
1208         continue;
1209       }
1210 
1211       // Check slot numbers are within slot span and never decreasing.
1212       PropertyInfo prop = curMap->getPropertyInfo(i);
1213       if (prop.hasSlot()) {
1214         MOZ_ASSERT_IF((curMap != this || index < mapLength),
1215                       prop.slot() < obj->slotSpan());
1216         MOZ_ASSERT_IF(nextSlot.isSome(), *nextSlot >= prop.slot());
1217         nextSlot = mozilla::Some(prop.slot());
1218       }
1219 
1220       // All properties must be in the table.
1221       if (table) {
1222         PropertyKey key = curMap->getKey(index);
1223         auto p = table->lookupRaw(key);
1224         MOZ_ASSERT(p->map() == curMap);
1225         MOZ_ASSERT(p->index() == index);
1226       }
1227     }
1228 
1229     if (!curMap->hasPrevious()) {
1230       break;
1231     }
1232     nextMap = curMap;
1233     curMap = curMap->asLinked()->previous()->asShared();
1234   }
1235 }
1236 #endif  // DEBUG
1237 
size(mozilla::MallocSizeOf mallocSizeOf) const1238 JS::ubi::Node::Size JS::ubi::Concrete<PropMap>::size(
1239     mozilla::MallocSizeOf mallocSizeOf) const {
1240   Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
1241   size_t children = 0;
1242   size_t tables = 0;
1243   get().addSizeOfExcludingThis(mallocSizeOf, &children, &tables);
1244   return size + children + tables;
1245 }
1246