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