Shape.cpp (56405B)
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/Shape-inl.h" 8 9 #include "gc/HashUtil.h" 10 #include "js/friend/WindowProxy.h" // js::IsWindow 11 #include "js/HashTable.h" 12 #include "js/Printer.h" // js::GenericPrinter, js::Fprinter 13 #include "js/UniquePtr.h" 14 #include "vm/JSObject.h" 15 #include "vm/JSONPrinter.h" // js::JSONPrinter 16 #include "vm/ShapeZone.h" 17 #include "vm/Watchtower.h" 18 19 #include "gc/StableCellHasher-inl.h" 20 #include "vm/JSContext-inl.h" 21 #include "vm/JSObject-inl.h" 22 #include "vm/NativeObject-inl.h" 23 24 using namespace js; 25 26 /* static */ 27 bool Shape::replaceShape(JSContext* cx, HandleObject obj, 28 ObjectFlags objectFlags, TaggedProto proto, 29 uint32_t nfixed) { 30 Shape* newShape; 31 switch (obj->shape()->kind()) { 32 case Kind::Shared: { 33 Handle<NativeObject*> nobj = obj.as<NativeObject>(); 34 if (nobj->shape()->propMap()) { 35 Rooted<BaseShape*> base(cx, obj->shape()->base()); 36 if (proto != base->proto()) { 37 Rooted<TaggedProto> protoRoot(cx, proto); 38 base = BaseShape::get(cx, base->clasp(), base->realm(), protoRoot); 39 if (!base) { 40 return false; 41 } 42 } 43 Rooted<SharedPropMap*> map(cx, nobj->sharedShape()->propMap()); 44 uint32_t mapLength = nobj->shape()->propMapLength(); 45 newShape = SharedShape::getPropMapShape(cx, base, nfixed, map, 46 mapLength, objectFlags); 47 } else { 48 newShape = SharedShape::getInitialShape( 49 cx, obj->shape()->getObjectClass(), obj->shape()->realm(), proto, 50 nfixed, objectFlags); 51 } 52 break; 53 } 54 case Kind::Dictionary: { 55 Handle<NativeObject*> nobj = obj.as<NativeObject>(); 56 57 Rooted<BaseShape*> base(cx, nobj->shape()->base()); 58 if (proto != base->proto()) { 59 Rooted<TaggedProto> protoRoot(cx, proto); 60 base = BaseShape::get(cx, nobj->getClass(), nobj->realm(), protoRoot); 61 if (!base) { 62 return false; 63 } 64 } 65 66 Rooted<DictionaryPropMap*> map(cx, nobj->dictionaryShape()->propMap()); 67 uint32_t mapLength = nobj->shape()->propMapLength(); 68 newShape = 69 DictionaryShape::new_(cx, base, objectFlags, nfixed, map, mapLength); 70 break; 71 } 72 case Kind::Proxy: 73 MOZ_ASSERT(nfixed == 0); 74 newShape = 75 ProxyShape::getShape(cx, obj->shape()->getObjectClass(), 76 obj->shape()->realm(), proto, objectFlags); 77 break; 78 case Kind::WasmGC: 79 MOZ_ASSERT(nfixed == 0); 80 const wasm::RecGroup* recGroup = obj->shape()->asWasmGC().recGroup(); 81 newShape = WasmGCShape::getShape(cx, obj->shape()->getObjectClass(), 82 obj->shape()->realm(), proto, recGroup, 83 objectFlags); 84 break; 85 } 86 if (!newShape) { 87 return false; 88 } 89 90 obj->setShape(newShape); 91 return true; 92 } 93 94 /* static */ 95 bool js::NativeObject::toDictionaryMode(JSContext* cx, 96 Handle<NativeObject*> obj) { 97 MOZ_ASSERT(!obj->inDictionaryMode()); 98 MOZ_ASSERT(cx->isInsideCurrentCompartment(obj)); 99 100 RootedTuple<NativeShape*, SharedPropMap*, DictionaryPropMap*, BaseShape*> 101 roots(cx); 102 103 RootedField<NativeShape*> shape(roots, obj->shape()); 104 uint32_t span = obj->slotSpan(); 105 106 uint32_t mapLength = shape->propMapLength(); 107 108 // Clone the shared property map to an unshared dictionary map. 109 RootedField<DictionaryPropMap*> dictMap(roots); 110 111 if (mapLength > 0) { 112 RootedField<SharedPropMap*> map(roots, shape->propMap()->asShared()); 113 dictMap = SharedPropMap::toDictionaryMap(cx, map, mapLength); 114 } else { 115 // Create an empty dictMap 116 dictMap = DictionaryPropMap::createEmpty(cx); 117 } 118 119 if (!dictMap) { 120 return false; 121 } 122 123 // Allocate and use a new dictionary shape. 124 RootedField<BaseShape*> base(roots, shape->base()); 125 shape = DictionaryShape::new_(cx, base, shape->objectFlags(), 126 shape->numFixedSlots(), dictMap, mapLength); 127 if (!shape) { 128 return false; 129 } 130 131 obj->setShape(shape); 132 133 MOZ_ASSERT(obj->inDictionaryMode()); 134 obj->setDictionaryModeSlotSpan(span); 135 136 return true; 137 } 138 139 bool JSObject::reshapeForTeleporting(JSContext* cx, JS::HandleObject obj) { 140 MOZ_ASSERT(obj->isUsedAsPrototype()); 141 MOZ_ASSERT(obj->hasStaticPrototype(), 142 "teleporting as a concept is only applicable to static " 143 "(not dynamically-computed) prototypes"); 144 MOZ_ASSERT(obj->is<NativeObject>()); 145 146 Handle<NativeObject*> nobj = obj.as<NativeObject>(); 147 if (!nobj->inDictionaryMode()) { 148 return NativeObject::toDictionaryMode(cx, nobj); 149 } 150 151 return NativeObject::generateNewDictionaryShape(cx, nobj); 152 } 153 154 namespace js { 155 156 class MOZ_RAII AutoCheckShapeConsistency { 157 #ifdef DEBUG 158 Handle<NativeObject*> obj_; 159 #endif 160 161 public: 162 explicit AutoCheckShapeConsistency(Handle<NativeObject*> obj) 163 #ifdef DEBUG 164 : obj_(obj) 165 #endif 166 { 167 } 168 169 #ifdef DEBUG 170 ~AutoCheckShapeConsistency() { obj_->checkShapeConsistency(); } 171 #endif 172 }; 173 174 } // namespace js 175 176 /* static */ MOZ_ALWAYS_INLINE bool 177 NativeObject::maybeConvertToDictionaryForAdd(JSContext* cx, 178 Handle<NativeObject*> obj) { 179 if (obj->inDictionaryMode()) { 180 return true; 181 } 182 SharedPropMap* map = obj->sharedShape()->propMap(); 183 if (!map) { 184 return true; 185 } 186 if (MOZ_LIKELY(!map->shouldConvertToDictionaryForAdd())) { 187 return true; 188 } 189 return toDictionaryMode(cx, obj); 190 } 191 192 static void AssertValidCustomDataProp(NativeObject* obj, PropertyFlags flags) { 193 // We only support custom data properties on ArrayObject and ArgumentsObject. 194 // The mechanism is deprecated so we don't want to add new uses. 195 MOZ_ASSERT(flags.isCustomDataProperty()); 196 MOZ_ASSERT(!flags.isAccessorProperty()); 197 MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<ArgumentsObject>()); 198 } 199 200 /* static */ 201 bool NativeObject::addCustomDataProperty(JSContext* cx, 202 Handle<NativeObject*> obj, HandleId id, 203 PropertyFlags flags) { 204 MOZ_ASSERT(!id.isVoid()); 205 MOZ_ASSERT(!id.isPrivateName()); 206 MOZ_ASSERT(!obj->containsPure(id)); 207 208 AutoCheckShapeConsistency check(obj); 209 AssertValidCustomDataProp(obj, flags); 210 211 if (!Watchtower::watchPropertyAdd(cx, obj, id)) { 212 return false; 213 } 214 215 if (!maybeConvertToDictionaryForAdd(cx, obj)) { 216 return false; 217 } 218 219 ObjectFlags objectFlags = obj->shape()->objectFlags(); 220 const JSClass* clasp = obj->shape()->getObjectClass(); 221 222 if (obj->inDictionaryMode()) { 223 // First generate a new dictionary shape so that the map can be mutated 224 // without having to worry about OOM conditions. 225 if (!NativeObject::generateNewDictionaryShape(cx, obj)) { 226 return false; 227 } 228 229 Rooted<DictionaryPropMap*> map(cx, obj->dictionaryShape()->propMap()); 230 uint32_t mapLength = obj->shape()->propMapLength(); 231 if (!DictionaryPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags, 232 SHAPE_INVALID_SLOT, &objectFlags)) { 233 return false; 234 } 235 236 obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength); 237 return true; 238 } 239 240 Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap()); 241 uint32_t mapLength = obj->shape()->propMapLength(); 242 if (!SharedPropMap::addCustomDataProperty(cx, clasp, &map, &mapLength, id, 243 flags, &objectFlags)) { 244 return false; 245 } 246 247 Shape* shape = SharedShape::getPropMapShape(cx, obj->shape()->base(), 248 obj->shape()->numFixedSlots(), 249 map, mapLength, objectFlags); 250 if (!shape) { 251 return false; 252 } 253 254 obj->setShape(shape); 255 return true; 256 } 257 258 static ShapeSetForAdd* MakeShapeSetForAdd(SharedShape* shape1, 259 SharedShape* shape2) { 260 MOZ_ASSERT(shape1 != shape2); 261 MOZ_ASSERT(shape1->propMapLength() == shape2->propMapLength()); 262 263 auto hash = MakeUnique<ShapeSetForAdd>(); 264 if (!hash || !hash->reserve(2)) { 265 return nullptr; 266 } 267 268 PropertyInfoWithKey prop = shape1->lastProperty(); 269 hash->putNewInfallible(ShapeForAddHasher::Lookup(prop.key(), prop.flags()), 270 shape1); 271 272 prop = shape2->lastProperty(); 273 hash->putNewInfallible(ShapeForAddHasher::Lookup(prop.key(), prop.flags()), 274 shape2); 275 276 return hash.release(); 277 } 278 279 static MOZ_ALWAYS_INLINE SharedShape* LookupShapeForAdd(Shape* shape, 280 PropertyKey key, 281 PropertyFlags flags, 282 uint32_t* slot) { 283 ShapeCachePtr cache = shape->cache(); 284 285 if (cache.isSingleShapeForAdd()) { 286 SharedShape* newShape = cache.toSingleShapeForAdd(); 287 if (newShape->lastPropertyMatchesForAdd(key, flags, slot)) { 288 return newShape; 289 } 290 return nullptr; 291 } 292 293 if (cache.isShapeSetForAdd()) { 294 ShapeSetForAdd* set = cache.toShapeSetForAdd(); 295 ShapeForAddHasher::Lookup lookup(key, flags); 296 if (auto p = set->lookup(lookup)) { 297 SharedShape* newShape = *p; 298 *slot = newShape->lastProperty().slot(); 299 return newShape; 300 } 301 return nullptr; 302 } 303 304 MOZ_ASSERT(!cache.isForAdd()); 305 return nullptr; 306 } 307 308 // Add shapes with a non-None ShapeCachePtr to the shapesWithCache list so that 309 // these caches can be discarded on GC. 310 static bool RegisterShapeCache(JSContext* cx, Shape* shape) { 311 ShapeCachePtr cache = shape->cache(); 312 if (!cache.isNone()) { 313 // Already registered this shape. 314 return true; 315 } 316 return cx->zone()->shapeZone().shapesWithCache.append(shape); 317 } 318 319 /* static */ 320 bool NativeObject::addProperty(JSContext* cx, Handle<NativeObject*> obj, 321 HandleId id, PropertyFlags flags, 322 uint32_t* slot) { 323 AutoCheckShapeConsistency check(obj); 324 MOZ_ASSERT(!flags.isCustomDataProperty(), 325 "Use addCustomDataProperty for custom data properties"); 326 327 // The object must not contain a property named |id|. The object must be 328 // extensible, but allow private fields and sparsifying dense elements. 329 MOZ_ASSERT(!id.isVoid()); 330 MOZ_ASSERT(!obj->containsPure(id)); 331 MOZ_ASSERT_IF(!id.isPrivateName(), 332 obj->isExtensible() || 333 (id.isInt() && obj->containsDenseElement(id.toInt()))); 334 if (!Watchtower::watchPropertyAdd(cx, obj, id)) { 335 return false; 336 } 337 338 if (!maybeConvertToDictionaryForAdd(cx, obj)) { 339 return false; 340 } 341 342 if (auto* shape = LookupShapeForAdd(obj->shape(), id, flags, slot)) { 343 return obj->setShapeAndAddNewSlot(cx, shape, *slot); 344 } 345 346 if (obj->inDictionaryMode()) { 347 // First generate a new dictionary shape so that the map and shape can be 348 // mutated without having to worry about OOM conditions. 349 if (!NativeObject::generateNewDictionaryShape(cx, obj)) { 350 return false; 351 } 352 if (!allocDictionarySlot(cx, obj, slot)) { 353 return false; 354 } 355 356 ObjectFlags objectFlags = obj->shape()->objectFlags(); 357 const JSClass* clasp = obj->shape()->getObjectClass(); 358 359 Rooted<DictionaryPropMap*> map(cx, obj->shape()->propMap()->asDictionary()); 360 uint32_t mapLength = obj->shape()->propMapLength(); 361 if (!DictionaryPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags, 362 *slot, &objectFlags)) { 363 return false; 364 } 365 366 obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength); 367 return true; 368 } 369 370 ObjectFlags objectFlags = obj->shape()->objectFlags(); 371 const JSClass* clasp = obj->shape()->getObjectClass(); 372 373 Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap()); 374 uint32_t mapLength = obj->shape()->propMapLength(); 375 376 if (!SharedPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags, 377 &objectFlags, slot)) { 378 return false; 379 } 380 381 bool allocatedNewShape; 382 SharedShape* newShape = SharedShape::getPropMapShape( 383 cx, obj->shape()->base(), obj->shape()->numFixedSlots(), map, mapLength, 384 objectFlags, &allocatedNewShape); 385 if (!newShape) { 386 return false; 387 } 388 389 Shape* oldShape = obj->shape(); 390 if (!obj->setShapeAndAddNewSlot(cx, newShape, *slot)) { 391 return false; 392 } 393 394 // Add the new shape to the old shape's shape cache, to optimize this shape 395 // transition. Don't do this if we just allocated a new shape, because that 396 // suggests this may not be a hot transition that would benefit from the 397 // cache. 398 399 if (allocatedNewShape) { 400 return true; 401 } 402 403 if (!RegisterShapeCache(cx, oldShape)) { 404 // Ignore OOM, the cache is just an optimization. 405 return true; 406 } 407 408 ShapeCachePtr& cache = oldShape->cacheRef(); 409 if (!cache.isForAdd()) { 410 cache.setSingleShapeForAdd(newShape); 411 } else if (cache.isSingleShapeForAdd()) { 412 SharedShape* prevShape = cache.toSingleShapeForAdd(); 413 if (ShapeSetForAdd* set = MakeShapeSetForAdd(prevShape, newShape)) { 414 cache.setShapeSetForAdd(set); 415 AddCellMemory(oldShape, sizeof(ShapeSetForAdd), 416 MemoryUse::ShapeSetForAdd); 417 } 418 } else { 419 ShapeForAddHasher::Lookup lookup(id, flags); 420 (void)cache.toShapeSetForAdd()->putNew(lookup, newShape); 421 } 422 423 return true; 424 } 425 426 void Shape::maybeCacheIterator(JSContext* cx, PropertyIteratorObject* iter) { 427 if (!cache().isNone() && !cache().isIterator()) { 428 // If we're already caching other shape data, skip caching the iterator. 429 return; 430 } 431 if (MOZ_UNLIKELY(!RegisterShapeCache(cx, this))) { 432 // Ignore OOM. The cache is just an optimization. 433 return; 434 } 435 cacheRef().setIterator(iter); 436 } 437 438 /* static */ 439 bool NativeObject::addPropertyInReservedSlot(JSContext* cx, 440 Handle<NativeObject*> obj, 441 HandleId id, uint32_t slot, 442 PropertyFlags flags) { 443 AutoCheckShapeConsistency check(obj); 444 MOZ_ASSERT(!flags.isCustomDataProperty(), 445 "Use addCustomDataProperty for custom data properties"); 446 447 // The slot must be a reserved slot. 448 MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(obj->getClass())); 449 450 // The object must not contain a property named |id| and must be extensible. 451 MOZ_ASSERT(!id.isVoid()); 452 MOZ_ASSERT(!obj->containsPure(id)); 453 MOZ_ASSERT(!id.isPrivateName()); 454 MOZ_ASSERT(obj->isExtensible()); 455 456 // The object must not be in dictionary mode. This simplifies the code below. 457 MOZ_ASSERT(!obj->inDictionaryMode()); 458 459 // We don't need to call Watchtower::watchPropertyAdd here because this isn't 460 // used for any watched objects. 461 MOZ_ASSERT(!Watchtower::watchesPropertyAdd(obj)); 462 463 ObjectFlags objectFlags = obj->shape()->objectFlags(); 464 const JSClass* clasp = obj->shape()->getObjectClass(); 465 466 Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap()); 467 uint32_t mapLength = obj->shape()->propMapLength(); 468 if (!SharedPropMap::addPropertyInReservedSlot(cx, clasp, &map, &mapLength, id, 469 flags, slot, &objectFlags)) { 470 return false; 471 } 472 473 Shape* shape = SharedShape::getPropMapShape(cx, obj->shape()->base(), 474 obj->shape()->numFixedSlots(), 475 map, mapLength, objectFlags); 476 if (!shape) { 477 return false; 478 } 479 obj->setShape(shape); 480 481 MOZ_ASSERT(obj->getLastProperty().slot() == slot); 482 return true; 483 } 484 485 /* 486 * Assert some invariants that should hold when changing properties. It's the 487 * responsibility of the callers to ensure these hold. 488 */ 489 static void AssertCanChangeFlags(PropertyInfo prop, PropertyFlags flags) { 490 #ifdef DEBUG 491 if (prop.configurable()) { 492 return; 493 } 494 495 // A non-configurable property must stay non-configurable. 496 MOZ_ASSERT(!flags.configurable()); 497 498 // Reject attempts to turn a non-configurable data property into an accessor 499 // or custom data property. 500 MOZ_ASSERT_IF(prop.isDataProperty(), flags.isDataProperty()); 501 502 // Reject attempts to turn a non-configurable accessor property into a data 503 // property or custom data property. 504 MOZ_ASSERT_IF(prop.isAccessorProperty(), flags.isAccessorProperty()); 505 #endif 506 } 507 508 static void AssertValidArrayIndex(NativeObject* obj, jsid id) { 509 #ifdef DEBUG 510 if (obj->is<ArrayObject>()) { 511 ArrayObject* arr = &obj->as<ArrayObject>(); 512 uint32_t index; 513 if (IdIsIndex(id, &index)) { 514 MOZ_ASSERT(index < arr->length() || arr->lengthIsWritable()); 515 } 516 } 517 #endif 518 } 519 520 /* static */ 521 bool NativeObject::changeProperty(JSContext* cx, Handle<NativeObject*> obj, 522 HandleId id, PropertyFlags flags, 523 uint32_t* slotOut) { 524 MOZ_ASSERT(!id.isVoid()); 525 526 AutoCheckShapeConsistency check(obj); 527 AssertValidArrayIndex(obj, id); 528 MOZ_ASSERT(!flags.isCustomDataProperty(), 529 "Use changeCustomDataPropAttributes for custom data properties"); 530 531 Rooted<PropMap*> map(cx, obj->shape()->propMap()); 532 uint32_t mapLength = obj->shape()->propMapLength(); 533 534 uint32_t propIndex; 535 Rooted<PropMap*> propMap(cx, map->lookup(cx, mapLength, id, &propIndex)); 536 MOZ_ASSERT(propMap); 537 538 ObjectFlags objectFlags = obj->shape()->objectFlags(); 539 540 PropertyInfo oldProp = propMap->getPropertyInfo(propIndex); 541 AssertCanChangeFlags(oldProp, flags); 542 543 if (oldProp.flags() != flags) { 544 if (!Watchtower::watchPropertyFlagsChange(cx, obj, id, oldProp, flags)) { 545 return false; 546 } 547 } 548 549 if (oldProp.isAccessorProperty()) { 550 objectFlags.setFlag(ObjectFlag::HadGetterSetterChange); 551 } 552 553 // If the property flags are not changing, the only thing we have to do is 554 // update the object flags. 555 if (oldProp.flags() == flags) { 556 *slotOut = oldProp.slot(); 557 if (objectFlags == obj->shape()->objectFlags()) { 558 return true; 559 } 560 return Shape::replaceShape(cx, obj, objectFlags, obj->shape()->proto(), 561 obj->shape()->numFixedSlots()); 562 } 563 564 const JSClass* clasp = obj->shape()->getObjectClass(); 565 566 // Rebuilding the new SharedPropMap is linear in the number of properties. 567 // To avoid going quadratic when changing every property in a large object, 568 // we cap the number of properties we will re-add. As a fallback, we can 569 // transition to dictionary mode, which lets us change properties in constant 570 // time. 571 const uint32_t MaxCopiedMaps = 4; 572 bool hasReasonableGap = 573 map->isShared() && map->asShared()->numPreviousMaps() - 574 propMap->asShared()->numPreviousMaps() <= 575 MaxCopiedMaps; 576 577 bool isLast = propMap == map && propIndex == mapLength - 1; 578 bool nonLastCustomProperty = oldProp.isCustomDataProperty() && !isLast; 579 if (map->isShared() && !nonLastCustomProperty && hasReasonableGap) { 580 // To change a property, we get the previous propmap and then call 581 // addProperty to re-add the changed property with the new flags. If it 582 // is not the last property, we have to re-add all the following 583 // properties. 584 MOZ_ASSERT_IF(isLast, obj->getLastProperty().key() == id); 585 586 // "Remove" the changed property. 587 Rooted<SharedPropMap*> resultMap(cx, propMap->asShared()); 588 uint32_t resultMapLength = propIndex + 1; 589 SharedPropMap::getPrevious(&resultMap, &resultMapLength); 590 591 // Re-add the property with the new flags. 592 if (MOZ_LIKELY(oldProp.hasSlot())) { 593 *slotOut = oldProp.slot(); 594 if (!SharedPropMap::addPropertyWithKnownSlot(cx, clasp, &resultMap, 595 &resultMapLength, id, flags, 596 *slotOut, &objectFlags)) { 597 return false; 598 } 599 } else { 600 if (!SharedPropMap::addProperty(cx, clasp, &resultMap, &resultMapLength, 601 id, flags, &objectFlags, slotOut)) { 602 return false; 603 } 604 } 605 606 // Build a new SharedPropMap by re-adding each unchanged property. 607 if (!isLast) { 608 Rooted<PropertyKey> key(cx); 609 610 SharedPropMapAndIndex startAfter(propMap->asShared(), propIndex); 611 SharedPropMapAndIndex end(map->asShared(), mapLength - 1); 612 for (SharedPropMapIter iter(cx, startAfter, end); !iter.done(); 613 iter.next()) { 614 key = iter.key(); 615 PropertyInfo prop = iter.prop(); 616 PropertyFlags flags = prop.flags(); 617 if (prop.isCustomDataProperty()) { 618 if (!SharedPropMap::addCustomDataProperty(cx, clasp, &resultMap, 619 &resultMapLength, key, 620 flags, &objectFlags)) { 621 return false; 622 } 623 } else { 624 if (!SharedPropMap::addPropertyWithKnownSlot( 625 cx, clasp, &resultMap, &resultMapLength, key, flags, 626 prop.slot(), &objectFlags)) { 627 return false; 628 } 629 } 630 } 631 } 632 MOZ_ASSERT(resultMapLength == mapLength); 633 634 SharedShape* newShape = SharedShape::getPropMapShape( 635 cx, obj->shape()->base(), obj->shape()->numFixedSlots(), resultMap, 636 resultMapLength, objectFlags); 637 if (!newShape) { 638 return false; 639 } 640 641 if (MOZ_LIKELY(oldProp.hasSlot())) { 642 MOZ_ASSERT(obj->sharedShape()->slotSpan() == newShape->slotSpan()); 643 obj->setShape(newShape); 644 return true; 645 } 646 return obj->setShapeAndAddNewSlot(cx, newShape, *slotOut); 647 } 648 649 if (map->isShared()) { 650 // We gave up on trying to keep a shared map, either because of 651 // custom data properties or because we would have to copy too 652 // many properties. Switch to dictionary mode and relookup 653 // pointers for the new dictionary map. 654 if (!NativeObject::toDictionaryMode(cx, obj)) { 655 return false; 656 } 657 map = obj->shape()->propMap(); 658 propMap = map->lookup(cx, mapLength, id, &propIndex); 659 MOZ_ASSERT(propMap); 660 } else { 661 if (!NativeObject::generateNewDictionaryShape(cx, obj)) { 662 return false; 663 } 664 } 665 666 // The object has a new dictionary shape (see toDictionaryMode and 667 // generateNewDictionaryShape calls above), so we can mutate the map and shape 668 // in place. 669 670 MOZ_ASSERT(map->isDictionary()); 671 MOZ_ASSERT(propMap->isDictionary()); 672 673 uint32_t slot = oldProp.hasSlot() ? oldProp.slot() : SHAPE_INVALID_SLOT; 674 if (slot == SHAPE_INVALID_SLOT) { 675 if (!allocDictionarySlot(cx, obj, &slot)) { 676 return false; 677 } 678 } 679 680 propMap->asDictionary()->changeProperty(cx, clasp, propIndex, flags, slot, 681 &objectFlags); 682 obj->dictionaryShape()->setObjectFlagsOfNewShape(objectFlags); 683 684 *slotOut = slot; 685 return true; 686 } 687 688 /* static */ 689 bool NativeObject::changeCustomDataPropAttributes(JSContext* cx, 690 Handle<NativeObject*> obj, 691 HandleId id, 692 PropertyFlags flags) { 693 MOZ_ASSERT(!id.isVoid()); 694 695 AutoCheckShapeConsistency check(obj); 696 AssertValidArrayIndex(obj, id); 697 AssertValidCustomDataProp(obj, flags); 698 699 Rooted<PropMap*> map(cx, obj->shape()->propMap()); 700 uint32_t mapLength = obj->shape()->propMapLength(); 701 702 uint32_t propIndex; 703 Rooted<PropMap*> propMap(cx, map->lookup(cx, mapLength, id, &propIndex)); 704 MOZ_ASSERT(propMap); 705 706 PropertyInfo oldProp = propMap->getPropertyInfo(propIndex); 707 MOZ_ASSERT(oldProp.isCustomDataProperty()); 708 AssertCanChangeFlags(oldProp, flags); 709 710 // If the property flags are not changing, we're done. 711 if (oldProp.flags() == flags) { 712 return true; 713 } 714 715 if (!Watchtower::watchPropertyFlagsChange(cx, obj, id, oldProp, flags)) { 716 return false; 717 } 718 719 const JSClass* clasp = obj->shape()->getObjectClass(); 720 ObjectFlags objectFlags = obj->shape()->objectFlags(); 721 722 if (map->isShared()) { 723 // Fast path for changing the last property in a SharedPropMap. Call 724 // getPrevious to "remove" the last property and then call 725 // addCustomDataProperty to re-add the last property with the new flags. 726 if (propMap == map && propIndex == mapLength - 1) { 727 MOZ_ASSERT(obj->getLastProperty().key() == id); 728 729 Rooted<SharedPropMap*> sharedMap(cx, map->asShared()); 730 SharedPropMap::getPrevious(&sharedMap, &mapLength); 731 732 if (!SharedPropMap::addCustomDataProperty( 733 cx, clasp, &sharedMap, &mapLength, id, flags, &objectFlags)) { 734 return false; 735 } 736 737 Shape* newShape = SharedShape::getPropMapShape( 738 cx, obj->shape()->base(), obj->shape()->numFixedSlots(), sharedMap, 739 mapLength, objectFlags); 740 if (!newShape) { 741 return false; 742 } 743 obj->setShape(newShape); 744 return true; 745 } 746 747 // Changing a non-last property. Switch to dictionary mode and relookup 748 // pointers for the new dictionary map. 749 if (!NativeObject::toDictionaryMode(cx, obj)) { 750 return false; 751 } 752 map = obj->shape()->propMap(); 753 propMap = map->lookup(cx, mapLength, id, &propIndex); 754 MOZ_ASSERT(propMap); 755 } else { 756 if (!NativeObject::generateNewDictionaryShape(cx, obj)) { 757 return false; 758 } 759 } 760 761 // The object has a new dictionary shape (see toDictionaryMode and 762 // generateNewDictionaryShape calls above), so we can mutate the map and shape 763 // in place. 764 765 MOZ_ASSERT(map->isDictionary()); 766 MOZ_ASSERT(propMap->isDictionary()); 767 768 propMap->asDictionary()->changePropertyFlags(cx, clasp, propIndex, flags, 769 &objectFlags); 770 obj->dictionaryShape()->setObjectFlagsOfNewShape(objectFlags); 771 return true; 772 } 773 774 void NativeObject::maybeFreeDictionaryPropSlots(JSContext* cx, 775 DictionaryPropMap* map, 776 uint32_t mapLength) { 777 // We can free all non-reserved slots if there are no properties left. We also 778 // handle the case where there's a single slotless property, to support arrays 779 // (array.length is a custom data property). 780 781 MOZ_ASSERT(dictionaryShape()->propMap() == map); 782 MOZ_ASSERT(shape()->propMapLength() == mapLength); 783 784 if (mapLength > 1 || map->previous()) { 785 return; 786 } 787 if (mapLength == 1 && map->getPropertyInfo(0).hasSlot()) { 788 return; 789 } 790 791 uint32_t oldSpan = dictionaryModeSlotSpan(); 792 uint32_t newSpan = JSCLASS_RESERVED_SLOTS(getClass()); 793 if (oldSpan == newSpan) { 794 return; 795 } 796 797 MOZ_ASSERT(newSpan < oldSpan); 798 799 // Trigger write barriers on the old slots before reallocating. 800 prepareSlotRangeForOverwrite(newSpan, oldSpan); 801 invalidateSlotRange(newSpan, oldSpan); 802 803 uint32_t oldCapacity = numDynamicSlots(); 804 uint32_t newCapacity = 805 calculateDynamicSlots(numFixedSlots(), newSpan, getClass()); 806 if (newCapacity < oldCapacity) { 807 shrinkSlots(cx, oldCapacity, newCapacity); 808 } 809 810 setDictionaryModeSlotSpan(newSpan); 811 map->setFreeList(SHAPE_INVALID_SLOT); 812 } 813 814 void NativeObject::setShapeAndRemoveLastSlot(JSContext* cx, 815 SharedShape* newShape, 816 uint32_t slot) { 817 MOZ_ASSERT(!inDictionaryMode()); 818 MOZ_ASSERT(newShape->isShared()); 819 MOZ_ASSERT(newShape->slotSpan() == slot); 820 821 uint32_t numFixed = newShape->numFixedSlots(); 822 if (slot < numFixed) { 823 setFixedSlot(slot, UndefinedValue()); 824 } else { 825 setDynamicSlot(numFixed, slot, UndefinedValue()); 826 uint32_t oldCapacity = numDynamicSlots(); 827 uint32_t newCapacity = calculateDynamicSlots(numFixed, slot, getClass()); 828 MOZ_ASSERT(newCapacity <= oldCapacity); 829 if (newCapacity < oldCapacity) { 830 shrinkSlots(cx, oldCapacity, newCapacity); 831 } 832 } 833 834 setShape(newShape); 835 } 836 837 /* static */ 838 bool NativeObject::removeProperty(JSContext* cx, Handle<NativeObject*> obj, 839 HandleId id) { 840 AutoCheckShapeConsistency check(obj); 841 842 Rooted<PropMap*> map(cx, obj->shape()->propMap()); 843 uint32_t mapLength = obj->shape()->propMapLength(); 844 845 AutoKeepPropMapTables keep(cx); 846 PropMapTable* table; 847 PropMapTable::Ptr ptr; 848 Rooted<PropMap*> propMap(cx); 849 uint32_t propIndex; 850 if (!PropMap::lookupForRemove(cx, map, mapLength, id, keep, propMap.address(), 851 &propIndex, &table, &ptr)) { 852 return false; 853 } 854 855 if (!propMap) { 856 return true; 857 } 858 859 PropertyInfo prop = propMap->getPropertyInfo(propIndex); 860 bool wasTrackedObjectFuseProp = false; 861 if (!Watchtower::watchPropertyRemove(cx, obj, id, prop, 862 &wasTrackedObjectFuseProp)) { 863 return false; 864 } 865 866 // If we're removing an accessor property, ensure the HadGetterSetterChange 867 // object flag is set. This is necessary because the slot holding the 868 // GetterSetter can be changed indirectly by removing the property and then 869 // adding it back with a different GetterSetter value but the same shape. 870 if (prop.isAccessorProperty() && !obj->hadGetterSetterChange()) { 871 if (!NativeObject::setHadGetterSetterChange(cx, obj)) { 872 return false; 873 } 874 } 875 876 if (map->isShared()) { 877 // Fast path for removing the last property from a SharedPropMap. In this 878 // case we can just call getPrevious and then look up a shape for the 879 // resulting map/mapLength. 880 // 881 // Don't reuse a previously-used Shape if the object has an ObjectFuse and 882 // we are removing a tracked property, to avoid the following bug: 883 // 884 // 1) The object has Shape S1 and a property P that was marked NotConstant. 885 // 2) We attach a SetSlot IC stub that guards on the object + S1 and stores 886 // a new value for this property. 887 // 3) We remove property P (we are here now). 888 // 4) We add a new property P, reuse Shape S1, and mark P Constant. 889 // 5) We use the SetSlot IC stub again but this is invalid because P is 890 // still marked Constant. 891 if (propMap == map && propIndex == mapLength - 1 && 892 !wasTrackedObjectFuseProp) { 893 MOZ_ASSERT(obj->getLastProperty().key() == id); 894 895 Rooted<SharedPropMap*> sharedMap(cx, map->asShared()); 896 SharedPropMap::getPrevious(&sharedMap, &mapLength); 897 898 SharedShape* shape = obj->sharedShape(); 899 SharedShape* newShape; 900 if (sharedMap) { 901 newShape = SharedShape::getPropMapShape( 902 cx, shape->base(), shape->numFixedSlots(), sharedMap, mapLength, 903 shape->objectFlags()); 904 } else { 905 newShape = SharedShape::getInitialShape( 906 cx, shape->getObjectClass(), shape->realm(), shape->proto(), 907 shape->numFixedSlots(), shape->objectFlags()); 908 } 909 if (!newShape) { 910 return false; 911 } 912 913 if (MOZ_LIKELY(prop.hasSlot())) { 914 if (MOZ_LIKELY(prop.slot() == newShape->slotSpan())) { 915 obj->setShapeAndRemoveLastSlot(cx, newShape, prop.slot()); 916 return true; 917 } 918 // Uncommon case: the property is stored in a reserved slot. 919 // See NativeObject::addPropertyInReservedSlot. 920 MOZ_ASSERT(prop.slot() < JSCLASS_RESERVED_SLOTS(obj->getClass())); 921 obj->setSlot(prop.slot(), UndefinedValue()); 922 } 923 obj->setShape(newShape); 924 return true; 925 } 926 927 // Removing a non-last property. Switch to dictionary mode and relookup 928 // pointers for the new dictionary map. 929 if (!NativeObject::toDictionaryMode(cx, obj)) { 930 return false; 931 } 932 map = obj->shape()->propMap(); 933 if (!PropMap::lookupForRemove(cx, map, mapLength, id, keep, 934 propMap.address(), &propIndex, &table, 935 &ptr)) { 936 return false; 937 } 938 } else { 939 if (!NativeObject::generateNewDictionaryShape(cx, obj)) { 940 return false; 941 } 942 } 943 944 // The object has a new dictionary shape (see toDictionaryMode and 945 // generateNewDictionaryShape calls above), so we can mutate the map and shape 946 // in place. 947 948 MOZ_ASSERT(map->isDictionary()); 949 MOZ_ASSERT(table); 950 MOZ_ASSERT(prop == ptr->propertyInfo()); 951 952 Rooted<DictionaryPropMap*> dictMap(cx, map->asDictionary()); 953 954 // If the property has a slot, free its slot number. 955 if (prop.hasSlot()) { 956 obj->freeDictionarySlot(prop.slot()); 957 } 958 959 DictionaryPropMap::removeProperty(cx, &dictMap, &mapLength, table, ptr); 960 961 obj->dictionaryShape()->updateNewShape(obj->shape()->objectFlags(), dictMap, 962 mapLength); 963 964 // If we just deleted the last property, consider shrinking the slots. We only 965 // do this if there are a lot of slots, to avoid allocating/freeing dynamic 966 // slots repeatedly. 967 static constexpr size_t MinSlotSpanForFree = 64; 968 if (obj->dictionaryModeSlotSpan() >= MinSlotSpanForFree) { 969 obj->maybeFreeDictionaryPropSlots(cx, dictMap, mapLength); 970 } 971 972 return true; 973 } 974 975 /* static */ 976 bool NativeObject::densifySparseElements(JSContext* cx, 977 Handle<NativeObject*> obj) { 978 AutoCheckShapeConsistency check(obj); 979 MOZ_ASSERT(obj->inDictionaryMode()); 980 981 // First generate a new dictionary shape so that the shape and map can then 982 // be updated infallibly. 983 if (!NativeObject::generateNewDictionaryShape(cx, obj)) { 984 return false; 985 } 986 987 Rooted<DictionaryPropMap*> map(cx, obj->shape()->propMap()->asDictionary()); 988 uint32_t mapLength = obj->shape()->propMapLength(); 989 990 DictionaryPropMap::densifyElements(cx, &map, &mapLength, obj); 991 992 // All indexed properties on the object are now dense. Clear the indexed 993 // flag so that we will not start using sparse indexes again if we need 994 // to grow the object. 995 ObjectFlags objectFlags = obj->shape()->objectFlags(); 996 objectFlags.clearFlag(ObjectFlag::Indexed); 997 998 obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength); 999 1000 obj->maybeFreeDictionaryPropSlots(cx, map, mapLength); 1001 1002 return true; 1003 } 1004 1005 // static 1006 bool NativeObject::freezeOrSealProperties(JSContext* cx, 1007 Handle<NativeObject*> obj, 1008 IntegrityLevel level) { 1009 AutoCheckShapeConsistency check(obj); 1010 1011 if (!Watchtower::watchFreezeOrSeal(cx, obj, level)) { 1012 return false; 1013 } 1014 1015 uint32_t mapLength = obj->shape()->propMapLength(); 1016 MOZ_ASSERT(mapLength > 0); 1017 1018 const JSClass* clasp = obj->shape()->getObjectClass(); 1019 ObjectFlags objectFlags = obj->shape()->objectFlags(); 1020 1021 if (obj->inDictionaryMode()) { 1022 // First generate a new dictionary shape so that the map and shape can be 1023 // updated infallibly. 1024 if (!generateNewDictionaryShape(cx, obj)) { 1025 return false; 1026 } 1027 DictionaryPropMap* map = obj->dictionaryShape()->propMap(); 1028 map->freezeOrSealProperties(cx, level, clasp, mapLength, &objectFlags); 1029 obj->dictionaryShape()->updateNewShape(objectFlags, map, mapLength); 1030 return true; 1031 } 1032 1033 Rooted<SharedPropMap*> map(cx, obj->sharedShape()->propMap()); 1034 if (!SharedPropMap::freezeOrSealProperties(cx, level, clasp, &map, mapLength, 1035 &objectFlags)) { 1036 return false; 1037 } 1038 1039 SharedShape* newShape = SharedShape::getPropMapShape( 1040 cx, obj->shape()->base(), obj->numFixedSlots(), map, mapLength, 1041 objectFlags); 1042 if (!newShape) { 1043 return false; 1044 } 1045 MOZ_ASSERT(obj->sharedShape()->slotSpan() == newShape->slotSpan()); 1046 1047 obj->setShape(newShape); 1048 return true; 1049 } 1050 1051 /* static */ 1052 bool NativeObject::generateNewDictionaryShape(JSContext* cx, 1053 Handle<NativeObject*> obj) { 1054 // Clone the current dictionary shape to a new shape. This ensures ICs and 1055 // other shape guards are properly invalidated before we start mutating the 1056 // map or new shape. 1057 1058 MOZ_ASSERT(obj->inDictionaryMode()); 1059 1060 Shape* shape = DictionaryShape::new_(cx, obj); 1061 if (!shape) { 1062 return false; 1063 } 1064 1065 obj->setShape(shape); 1066 return true; 1067 } 1068 1069 /* static */ 1070 bool JSObject::setFlag(JSContext* cx, HandleObject obj, ObjectFlag flag) { 1071 MOZ_ASSERT(cx->compartment() == obj->compartment()); 1072 1073 if (obj->hasFlag(flag)) { 1074 return true; 1075 } 1076 1077 ObjectFlags objectFlags = obj->shape()->objectFlags(); 1078 objectFlags.setFlag(flag); 1079 1080 uint32_t numFixed = 1081 obj->is<NativeObject>() ? obj->as<NativeObject>().numFixedSlots() : 0; 1082 return Shape::replaceShape(cx, obj, objectFlags, obj->shape()->proto(), 1083 numFixed); 1084 } 1085 1086 static bool SetObjectIsUsedAsPrototype(JSContext* cx, Handle<JSObject*> proto) { 1087 MOZ_ASSERT(!proto->isUsedAsPrototype()); 1088 1089 // Ensure the proto object has a unique id to prevent OOM crashes below. 1090 uint64_t unused; 1091 if (!gc::GetOrCreateUniqueId(proto, &unused)) { 1092 ReportOutOfMemory(cx); 1093 return false; 1094 } 1095 1096 return JSObject::setIsUsedAsPrototype(cx, proto); 1097 } 1098 1099 /* static */ 1100 bool JSObject::setProtoUnchecked(JSContext* cx, HandleObject obj, 1101 Handle<TaggedProto> proto) { 1102 MOZ_ASSERT(cx->compartment() == obj->compartment()); 1103 MOZ_ASSERT(!obj->staticPrototypeIsImmutable()); 1104 MOZ_ASSERT_IF(!obj->is<ProxyObject>(), obj->nonProxyIsExtensible()); 1105 MOZ_ASSERT(obj->shape()->proto() != proto); 1106 1107 // Notify Watchtower of this proto change, so it can properly invalidate shape 1108 // teleporting and other optimizations. 1109 if (!Watchtower::watchProtoChange(cx, obj)) { 1110 return false; 1111 } 1112 1113 if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) { 1114 RootedObject protoObj(cx, proto.toObject()); 1115 if (!SetObjectIsUsedAsPrototype(cx, protoObj)) { 1116 return false; 1117 } 1118 } 1119 1120 uint32_t numFixed = 1121 obj->is<NativeObject>() ? obj->as<NativeObject>().numFixedSlots() : 0; 1122 return Shape::replaceShape(cx, obj, obj->shape()->objectFlags(), proto, 1123 numFixed); 1124 } 1125 1126 /* static */ 1127 bool NativeObject::changeNumFixedSlotsAfterSwap(JSContext* cx, 1128 Handle<NativeObject*> obj, 1129 uint32_t nfixed) { 1130 MOZ_ASSERT(nfixed != obj->shape()->numFixedSlots()); 1131 1132 return Shape::replaceShape(cx, obj, obj->shape()->objectFlags(), 1133 obj->shape()->proto(), nfixed); 1134 } 1135 1136 BaseShape::BaseShape(JSContext* cx, const JSClass* clasp, JS::Realm* realm, 1137 TaggedProto proto) 1138 : TenuredCellWithNonGCPointer(clasp), realm_(realm), proto_(proto) { 1139 #ifdef DEBUG 1140 AssertJSClassInvariants(clasp); 1141 #endif 1142 1143 MOZ_ASSERT_IF(proto.isObject(), 1144 compartment() == proto.toObject()->compartment()); 1145 MOZ_ASSERT_IF(proto.isObject(), proto.toObject()->isUsedAsPrototype()); 1146 1147 // Windows may not appear on prototype chains. 1148 MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject())); 1149 1150 if (MOZ_UNLIKELY(clasp->emulatesUndefined())) { 1151 cx->runtime()->runtimeFuses.ref().hasSeenObjectEmulateUndefinedFuse.popFuse( 1152 cx); 1153 } 1154 1155 #ifdef DEBUG 1156 if (GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal()) { 1157 AssertTargetIsNotGray(global); 1158 } 1159 #endif 1160 } 1161 1162 /* static */ 1163 BaseShape* BaseShape::get(JSContext* cx, const JSClass* clasp, JS::Realm* realm, 1164 Handle<TaggedProto> proto) { 1165 auto& table = cx->zone()->shapeZone().baseShapes; 1166 1167 using Lookup = BaseShapeHasher::Lookup; 1168 1169 auto p = MakeDependentAddPtr(cx, table, Lookup(clasp, realm, proto)); 1170 if (p) { 1171 return *p; 1172 } 1173 1174 BaseShape* nbase = cx->newCell<BaseShape>(cx, clasp, realm, proto); 1175 if (!nbase) { 1176 return nullptr; 1177 } 1178 1179 if (!p.add(cx, table, Lookup(clasp, realm, proto), nbase)) { 1180 return nullptr; 1181 } 1182 1183 return nbase; 1184 } 1185 1186 // static 1187 SharedShape* SharedShape::new_(JSContext* cx, Handle<BaseShape*> base, 1188 ObjectFlags objectFlags, uint32_t nfixed, 1189 Handle<SharedPropMap*> map, uint32_t mapLength) { 1190 return cx->newCell<SharedShape>(base, objectFlags, nfixed, map, mapLength); 1191 } 1192 1193 // static 1194 DictionaryShape* DictionaryShape::new_(JSContext* cx, Handle<BaseShape*> base, 1195 ObjectFlags objectFlags, uint32_t nfixed, 1196 Handle<DictionaryPropMap*> map, 1197 uint32_t mapLength) { 1198 return cx->newCell<DictionaryShape>(base, objectFlags, nfixed, map, 1199 mapLength); 1200 } 1201 1202 DictionaryShape::DictionaryShape(NativeObject* nobj) 1203 : DictionaryShape(nobj->shape()->base(), nobj->shape()->objectFlags(), 1204 nobj->shape()->numFixedSlots(), 1205 nobj->dictionaryShape()->propMap(), 1206 nobj->shape()->propMapLength()) {} 1207 1208 // static 1209 DictionaryShape* DictionaryShape::new_(JSContext* cx, 1210 Handle<NativeObject*> obj) { 1211 return cx->newCell<DictionaryShape>(obj); 1212 } 1213 1214 // static 1215 ProxyShape* ProxyShape::new_(JSContext* cx, Handle<BaseShape*> base, 1216 ObjectFlags objectFlags) { 1217 return cx->newCell<ProxyShape>(base, objectFlags); 1218 } 1219 1220 // static 1221 WasmGCShape* WasmGCShape::new_(JSContext* cx, Handle<BaseShape*> base, 1222 const wasm::RecGroup* recGroup, 1223 ObjectFlags objectFlags) { 1224 WasmGCShape* shape = cx->newCell<WasmGCShape>(base, recGroup, objectFlags); 1225 if (shape) { 1226 shape->init(); 1227 } 1228 return shape; 1229 } 1230 1231 MOZ_ALWAYS_INLINE HashNumber ShapeForAddHasher::hash(const Lookup& l) { 1232 HashNumber hash = HashPropertyKey(l.key); 1233 return mozilla::AddToHash(hash, l.flags.toRaw()); 1234 } 1235 1236 MOZ_ALWAYS_INLINE bool ShapeForAddHasher::match(SharedShape* shape, 1237 const Lookup& l) { 1238 uint32_t slot; 1239 return shape->lastPropertyMatchesForAdd(l.key, l.flags, &slot); 1240 } 1241 1242 #if defined(DEBUG) || defined(JS_JITSPEW) 1243 void BaseShape::dump() const { 1244 Fprinter out(stderr); 1245 dump(out); 1246 } 1247 1248 void BaseShape::dump(js::GenericPrinter& out) const { 1249 js::JSONPrinter json(out); 1250 dump(json); 1251 out.put("\n"); 1252 } 1253 1254 void BaseShape::dump(js::JSONPrinter& json) const { 1255 json.beginObject(); 1256 dumpFields(json); 1257 json.endObject(); 1258 } 1259 1260 void BaseShape::dumpFields(js::JSONPrinter& json) const { 1261 json.formatProperty("address", "(js::BaseShape*)0x%p", this); 1262 1263 json.formatProperty("realm", "(JS::Realm*)0x%p", realm()); 1264 1265 if (proto().isDynamic()) { 1266 json.property("proto", "<dynamic>"); 1267 } else { 1268 JSObject* protoObj = proto().toObjectOrNull(); 1269 if (protoObj) { 1270 json.formatProperty("proto", "(JSObject*)0x%p", protoObj); 1271 } else { 1272 json.nullProperty("proto"); 1273 } 1274 } 1275 } 1276 1277 void Shape::dump() const { 1278 Fprinter out(stderr); 1279 dump(out); 1280 } 1281 1282 void Shape::dump(js::GenericPrinter& out) const { 1283 js::JSONPrinter json(out); 1284 dump(json); 1285 out.put("\n"); 1286 } 1287 1288 void Shape::dump(js::JSONPrinter& json) const { 1289 json.beginObject(); 1290 dumpFields(json); 1291 json.endObject(); 1292 } 1293 1294 template <typename KnownF, typename UnknownF> 1295 void ForEachObjectFlag(ObjectFlags flags, KnownF known, UnknownF unknown) { 1296 uint16_t raw = flags.toRaw(); 1297 for (uint16_t i = 1; i; i = i << 1) { 1298 if (!(raw & i)) { 1299 continue; 1300 } 1301 switch (ObjectFlag(raw & i)) { 1302 case ObjectFlag::IsUsedAsPrototype: 1303 known("IsUsedAsPrototype"); 1304 break; 1305 case ObjectFlag::NotExtensible: 1306 known("NotExtensible"); 1307 break; 1308 case ObjectFlag::Indexed: 1309 known("Indexed"); 1310 break; 1311 case ObjectFlag::HasInterestingSymbol: 1312 known("HasInterestingSymbol"); 1313 break; 1314 case ObjectFlag::HasEnumerable: 1315 known("HasEnumerable"); 1316 break; 1317 case ObjectFlag::FrozenElements: 1318 known("FrozenElements"); 1319 break; 1320 case ObjectFlag::InvalidatedTeleporting: 1321 known("InvalidatedTeleporting"); 1322 break; 1323 case ObjectFlag::ImmutablePrototype: 1324 known("ImmutablePrototype"); 1325 break; 1326 case ObjectFlag::QualifiedVarObj: 1327 known("QualifiedVarObj"); 1328 break; 1329 case ObjectFlag::HasNonWritableOrAccessorPropExclProto: 1330 known("HasNonWritableOrAccessorPropExclProto"); 1331 break; 1332 case ObjectFlag::HadGetterSetterChange: 1333 known("HadGetterSetterChange"); 1334 break; 1335 case ObjectFlag::UseWatchtowerTestingLog: 1336 known("UseWatchtowerTestingLog"); 1337 break; 1338 case ObjectFlag::GenerationCountedGlobal: 1339 known("GenerationCountedGlobal"); 1340 break; 1341 case ObjectFlag::NeedsProxyGetSetResultValidation: 1342 known("NeedsProxyGetSetResultValidation"); 1343 break; 1344 case ObjectFlag::HasRealmFuseProperty: 1345 known("HasRealmFuseProperty"); 1346 break; 1347 case ObjectFlag::HasPreservedWrapper: 1348 known("HasPreservedWrapper"); 1349 break; 1350 case ObjectFlag::HasNonFunctionAccessor: 1351 known("HasNonFunctionAccessor"); 1352 break; 1353 default: 1354 unknown(i); 1355 break; 1356 } 1357 } 1358 } 1359 1360 void Shape::dumpFields(js::JSONPrinter& json) const { 1361 json.formatProperty("address", "(js::Shape*)0x%p", this); 1362 1363 json.beginObjectProperty("base"); 1364 base()->dumpFields(json); 1365 json.endObject(); 1366 1367 switch (kind()) { 1368 case Kind::Shared: 1369 json.property("kind", "Shared"); 1370 break; 1371 case Kind::Dictionary: 1372 json.property("kind", "Dictionary"); 1373 break; 1374 case Kind::Proxy: 1375 json.property("kind", "Proxy"); 1376 break; 1377 case Kind::WasmGC: 1378 json.property("kind", "WasmGC"); 1379 break; 1380 } 1381 1382 json.beginInlineListProperty("objectFlags"); 1383 ForEachObjectFlag( 1384 objectFlags(), [&](const char* name) { json.value("%s", name); }, 1385 [&](uint16_t value) { json.value("Unknown(%04x)", value); }); 1386 json.endInlineList(); 1387 1388 if (isNative()) { 1389 json.property("numFixedSlots", asNative().numFixedSlots()); 1390 json.property("propMapLength", asNative().propMapLength()); 1391 1392 if (asNative().propMap()) { 1393 json.beginObjectProperty("propMap"); 1394 asNative().propMap()->dumpFields(json); 1395 json.endObject(); 1396 } else { 1397 json.nullProperty("propMap"); 1398 } 1399 } 1400 1401 if (isShared()) { 1402 if (getObjectClass()->isNativeObject()) { 1403 json.property("slotSpan", asShared().slotSpan()); 1404 } 1405 } 1406 1407 if (isWasmGC()) { 1408 json.formatProperty("recGroup", "(js::wasm::RecGroup*)0x%p", 1409 asWasmGC().recGroup()); 1410 } 1411 } 1412 1413 void Shape::dumpStringContent(js::GenericPrinter& out) const { 1414 out.printf("<(js::Shape*)0x%p", this); 1415 1416 if (isDictionary()) { 1417 out.put(", dictionary"); 1418 } 1419 1420 out.put(", objectFlags=["); 1421 bool first = true; 1422 ForEachObjectFlag( 1423 objectFlags(), 1424 [&](const char* name) { 1425 if (!first) { 1426 out.put(", "); 1427 } 1428 first = false; 1429 1430 out.put(name); 1431 }, 1432 [&](uint16_t value) { 1433 if (!first) { 1434 out.put(", "); 1435 } 1436 first = false; 1437 1438 out.printf("Unknown(%04x)", value); 1439 }); 1440 out.put("]>"); 1441 } 1442 #endif // defined(DEBUG) || defined(JS_JITSPEW) 1443 1444 /* static */ 1445 SharedShape* SharedShape::getInitialShape(JSContext* cx, const JSClass* clasp, 1446 JS::Realm* realm, TaggedProto proto, 1447 size_t nfixed, 1448 ObjectFlags objectFlags) { 1449 MOZ_ASSERT(cx->compartment() == realm->compartment()); 1450 MOZ_ASSERT_IF(proto.isObject(), 1451 cx->isInsideCurrentCompartment(proto.toObject())); 1452 1453 if (proto.isObject()) { 1454 if (proto.toObject()->isUsedAsPrototype()) { 1455 // Use the cache on the prototype's shape to get to the initial shape. 1456 // This cache has a hit rate of 80-90% on typical workloads and is faster 1457 // than the HashSet lookup below. 1458 JSObject* protoObj = proto.toObject(); 1459 Shape* protoObjShape = protoObj->shape(); 1460 if (protoObjShape->cache().isShapeWithProto()) { 1461 SharedShape* shape = protoObjShape->cache().toShapeWithProto(); 1462 if (shape->numFixedSlots() == nfixed && 1463 shape->objectFlags() == objectFlags && 1464 shape->getObjectClass() == clasp && shape->realm() == realm && 1465 shape->proto() == proto) { 1466 #ifdef DEBUG 1467 // Verify the table lookup below would have resulted in the same 1468 // shape. 1469 using Lookup = InitialShapeHasher::Lookup; 1470 Lookup lookup(clasp, realm, proto, nfixed, objectFlags); 1471 auto p = realm->zone()->shapeZone().initialShapes.lookup(lookup); 1472 MOZ_ASSERT(*p == shape); 1473 #endif 1474 return shape; 1475 } 1476 } 1477 } else { 1478 RootedObject protoObj(cx, proto.toObject()); 1479 if (!SetObjectIsUsedAsPrototype(cx, protoObj)) { 1480 return nullptr; 1481 } 1482 proto = TaggedProto(protoObj); 1483 } 1484 } 1485 1486 auto& table = realm->zone()->shapeZone().initialShapes; 1487 1488 using Lookup = InitialShapeHasher::Lookup; 1489 auto ptr = MakeDependentAddPtr( 1490 cx, table, Lookup(clasp, realm, proto, nfixed, objectFlags)); 1491 if (ptr) { 1492 // Cache the result of this lookup on the prototype's shape. 1493 if (proto.isObject()) { 1494 JSObject* protoObj = proto.toObject(); 1495 Shape* protoShape = protoObj->shape(); 1496 if (!protoShape->cache().isForAdd() && 1497 RegisterShapeCache(cx, protoShape)) { 1498 protoShape->cacheRef().setShapeWithProto(*ptr); 1499 } 1500 } 1501 return *ptr; 1502 } 1503 1504 Rooted<TaggedProto> protoRoot(cx, proto); 1505 Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot)); 1506 if (!nbase) { 1507 return nullptr; 1508 } 1509 1510 SharedShape* shape = 1511 SharedShape::new_(cx, nbase, objectFlags, nfixed, nullptr, 0); 1512 if (!shape) { 1513 return nullptr; 1514 } 1515 1516 Lookup lookup(clasp, realm, protoRoot, nfixed, objectFlags); 1517 if (!ptr.add(cx, table, lookup, shape)) { 1518 return nullptr; 1519 } 1520 1521 return shape; 1522 } 1523 1524 /* static */ 1525 SharedShape* SharedShape::getInitialShape(JSContext* cx, const JSClass* clasp, 1526 JS::Realm* realm, TaggedProto proto, 1527 gc::AllocKind kind, 1528 ObjectFlags objectFlags) { 1529 return getInitialShape(cx, clasp, realm, proto, GetGCKindSlots(kind), 1530 objectFlags); 1531 } 1532 1533 /* static */ 1534 SharedShape* SharedShape::getPropMapShape( 1535 JSContext* cx, BaseShape* base, size_t nfixed, Handle<SharedPropMap*> map, 1536 uint32_t mapLength, ObjectFlags objectFlags, bool* allocatedNewShape) { 1537 MOZ_ASSERT(cx->compartment() == base->compartment()); 1538 MOZ_ASSERT_IF(base->proto().isObject(), 1539 cx->isInsideCurrentCompartment(base->proto().toObject())); 1540 MOZ_ASSERT_IF(base->proto().isObject(), 1541 base->proto().toObject()->isUsedAsPrototype()); 1542 MOZ_ASSERT(map); 1543 MOZ_ASSERT(mapLength > 0); 1544 1545 auto& table = cx->zone()->shapeZone().propMapShapes; 1546 1547 using Lookup = PropMapShapeHasher::Lookup; 1548 auto ptr = MakeDependentAddPtr( 1549 cx, table, Lookup(base, nfixed, map, mapLength, objectFlags)); 1550 if (ptr) { 1551 if (allocatedNewShape) { 1552 *allocatedNewShape = false; 1553 } 1554 return *ptr; 1555 } 1556 1557 Rooted<BaseShape*> baseRoot(cx, base); 1558 SharedShape* shape = 1559 SharedShape::new_(cx, baseRoot, objectFlags, nfixed, map, mapLength); 1560 if (!shape) { 1561 return nullptr; 1562 } 1563 1564 Lookup lookup(baseRoot, nfixed, map, mapLength, objectFlags); 1565 if (!ptr.add(cx, table, lookup, shape)) { 1566 return nullptr; 1567 } 1568 1569 if (allocatedNewShape) { 1570 *allocatedNewShape = true; 1571 } 1572 1573 return shape; 1574 } 1575 1576 /* static */ 1577 SharedShape* SharedShape::getInitialOrPropMapShape( 1578 JSContext* cx, const JSClass* clasp, JS::Realm* realm, TaggedProto proto, 1579 size_t nfixed, Handle<SharedPropMap*> map, uint32_t mapLength, 1580 ObjectFlags objectFlags) { 1581 if (!map) { 1582 MOZ_ASSERT(mapLength == 0); 1583 return getInitialShape(cx, clasp, realm, proto, nfixed, objectFlags); 1584 } 1585 1586 Rooted<TaggedProto> protoRoot(cx, proto); 1587 BaseShape* nbase = BaseShape::get(cx, clasp, realm, protoRoot); 1588 if (!nbase) { 1589 return nullptr; 1590 } 1591 1592 return getPropMapShape(cx, nbase, nfixed, map, mapLength, objectFlags); 1593 } 1594 1595 /* static */ 1596 void SharedShape::insertInitialShape(JSContext* cx, 1597 Handle<SharedShape*> shape) { 1598 using Lookup = InitialShapeHasher::Lookup; 1599 Lookup lookup(shape->getObjectClass(), shape->realm(), shape->proto(), 1600 shape->numFixedSlots(), shape->objectFlags()); 1601 1602 auto& table = cx->zone()->shapeZone().initialShapes; 1603 InitialShapeSet::Ptr p = table.lookup(lookup); 1604 MOZ_ASSERT(p); 1605 1606 // The metadata callback can end up causing redundant changes of the initial 1607 // shape. 1608 SharedShape* initialShape = *p; 1609 if (initialShape == shape) { 1610 return; 1611 } 1612 1613 MOZ_ASSERT(initialShape->numFixedSlots() == shape->numFixedSlots()); 1614 MOZ_ASSERT(initialShape->base() == shape->base()); 1615 MOZ_ASSERT(initialShape->objectFlags() == shape->objectFlags()); 1616 1617 table.replaceKey(p, lookup, shape.get()); 1618 1619 // Purge the prototype's shape cache entry. 1620 if (shape->proto().isObject()) { 1621 JSObject* protoObj = shape->proto().toObject(); 1622 if (protoObj->shape()->cache().isShapeWithProto()) { 1623 protoObj->shape()->cacheRef().setNone(); 1624 } 1625 } 1626 } 1627 1628 /* static */ 1629 ProxyShape* ProxyShape::getShape(JSContext* cx, const JSClass* clasp, 1630 JS::Realm* realm, TaggedProto proto, 1631 ObjectFlags objectFlags) { 1632 MOZ_ASSERT(cx->compartment() == realm->compartment()); 1633 MOZ_ASSERT_IF(proto.isObject(), 1634 cx->isInsideCurrentCompartment(proto.toObject())); 1635 1636 if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) { 1637 RootedObject protoObj(cx, proto.toObject()); 1638 if (!SetObjectIsUsedAsPrototype(cx, protoObj)) { 1639 return nullptr; 1640 } 1641 proto = TaggedProto(protoObj); 1642 } 1643 1644 auto& table = realm->zone()->shapeZone().proxyShapes; 1645 1646 using Lookup = ProxyShapeHasher::Lookup; 1647 auto ptr = 1648 MakeDependentAddPtr(cx, table, Lookup(clasp, realm, proto, objectFlags)); 1649 if (ptr) { 1650 return *ptr; 1651 } 1652 1653 Rooted<TaggedProto> protoRoot(cx, proto); 1654 Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot)); 1655 if (!nbase) { 1656 return nullptr; 1657 } 1658 1659 Rooted<ProxyShape*> shape(cx, ProxyShape::new_(cx, nbase, objectFlags)); 1660 if (!shape) { 1661 return nullptr; 1662 } 1663 1664 Lookup lookup(clasp, realm, protoRoot, objectFlags); 1665 if (!ptr.add(cx, table, lookup, shape)) { 1666 return nullptr; 1667 } 1668 1669 return shape; 1670 } 1671 1672 /* static */ 1673 WasmGCShape* WasmGCShape::getShape(JSContext* cx, const JSClass* clasp, 1674 JS::Realm* realm, TaggedProto proto, 1675 const wasm::RecGroup* recGroup, 1676 ObjectFlags objectFlags) { 1677 MOZ_ASSERT(cx->compartment() == realm->compartment()); 1678 MOZ_ASSERT_IF(proto.isObject(), 1679 cx->isInsideCurrentCompartment(proto.toObject())); 1680 1681 if (proto.isObject() && !proto.toObject()->isUsedAsPrototype()) { 1682 RootedObject protoObj(cx, proto.toObject()); 1683 if (!SetObjectIsUsedAsPrototype(cx, protoObj)) { 1684 return nullptr; 1685 } 1686 proto = TaggedProto(protoObj); 1687 } 1688 1689 auto& table = realm->zone()->shapeZone().wasmGCShapes; 1690 1691 using Lookup = WasmGCShapeHasher::Lookup; 1692 auto ptr = MakeDependentAddPtr( 1693 cx, table, Lookup(clasp, realm, proto, recGroup, objectFlags)); 1694 if (ptr) { 1695 return *ptr; 1696 } 1697 1698 Rooted<TaggedProto> protoRoot(cx, proto); 1699 Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot)); 1700 if (!nbase) { 1701 return nullptr; 1702 } 1703 1704 Rooted<WasmGCShape*> shape( 1705 cx, WasmGCShape::new_(cx, nbase, recGroup, objectFlags)); 1706 if (!shape) { 1707 return nullptr; 1708 } 1709 1710 Lookup lookup(clasp, realm, protoRoot, recGroup, objectFlags); 1711 if (!ptr.add(cx, table, lookup, shape)) { 1712 return nullptr; 1713 } 1714 1715 return shape; 1716 } 1717 1718 JS::ubi::Node::Size JS::ubi::Concrete<js::Shape>::size( 1719 mozilla::MallocSizeOf mallocSizeOf) const { 1720 Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind()); 1721 1722 if (get().cache().isShapeSetForAdd()) { 1723 ShapeSetForAdd* set = get().cache().toShapeSetForAdd(); 1724 size += set->shallowSizeOfIncludingThis(mallocSizeOf); 1725 } 1726 1727 return size; 1728 } 1729 1730 JS::ubi::Node::Size JS::ubi::Concrete<js::BaseShape>::size( 1731 mozilla::MallocSizeOf mallocSizeOf) const { 1732 return js::gc::Arena::thingSize(get().asTenured().getAllocKind()); 1733 }