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