Watchtower.cpp (27327B)
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/Watchtower.h" 8 9 #include "js/CallAndConstruct.h" 10 #include "js/experimental/TypedData.h" 11 #include "vm/Compartment.h" 12 #include "vm/JSContext.h" 13 #include "vm/JSObject.h" 14 #include "vm/NativeObject.h" 15 #include "vm/PlainObject.h" 16 #include "vm/Realm.h" 17 18 #include "vm/Compartment-inl.h" 19 #include "vm/JSObject-inl.h" 20 #include "vm/NativeObject-inl.h" 21 #include "vm/Realm-inl.h" 22 #include "vm/Shape-inl.h" 23 24 using namespace js; 25 26 static bool AddToWatchtowerLog(JSContext* cx, const char* kind, 27 HandleObject obj, HandleValue extra) { 28 // Add an object storing {kind, object, extra} to the log for testing 29 // purposes. 30 31 MOZ_ASSERT(obj->useWatchtowerTestingLog()); 32 33 RootedString kindString(cx, NewStringCopyZ<CanGC>(cx, kind)); 34 if (!kindString) { 35 return false; 36 } 37 38 Rooted<PlainObject*> logObj(cx, NewPlainObjectWithProto(cx, nullptr)); 39 if (!logObj) { 40 return false; 41 } 42 if (!JS_DefineProperty(cx, logObj, "kind", kindString, JSPROP_ENUMERATE)) { 43 return false; 44 } 45 if (!JS_DefineProperty(cx, logObj, "object", obj, JSPROP_ENUMERATE)) { 46 return false; 47 } 48 if (!JS_DefineProperty(cx, logObj, "extra", extra, JSPROP_ENUMERATE)) { 49 return false; 50 } 51 52 if (!cx->runtime()->watchtowerTestingLog->append(logObj)) { 53 ReportOutOfMemory(cx); 54 return false; 55 } 56 57 return true; 58 } 59 60 static bool ReshapeForShadowedProp(JSContext* cx, Handle<NativeObject*> obj, 61 HandleId id) { 62 // |obj| has been used as the prototype of another object. Check if we're 63 // shadowing a property on its proto chain. In this case we need to reshape 64 // that object for shape teleporting to work correctly. 65 // 66 // See also the 'Shape Teleporting Optimization' comment in jit/CacheIR.cpp. 67 68 MOZ_ASSERT(obj->isUsedAsPrototype()); 69 70 // Lookups on integer ids cannot be cached through prototypes. 71 if (id.isInt()) { 72 return true; 73 } 74 75 bool useDictionaryTeleporting = 76 cx->zone()->shapeZone().useDictionaryModeTeleportation(); 77 78 RootedObject proto(cx, obj->staticPrototype()); 79 while (proto) { 80 // Lookups will not be cached through non-native protos. 81 if (!proto->is<NativeObject>()) { 82 break; 83 } 84 85 Handle<NativeObject*> nproto = proto.as<NativeObject>(); 86 87 if (mozilla::Maybe<PropertyInfo> propInfo = nproto->lookup(cx, id)) { 88 if (proto->hasObjectFuse()) { 89 if (auto* objFuse = cx->zone()->objectFuses.get(nproto)) { 90 objFuse->handleTeleportingShadowedProperty(cx, *propInfo); 91 } 92 } 93 if (useDictionaryTeleporting) { 94 JS_LOG(teleporting, Debug, 95 "Shadowed Prop: Dictionary Reshape for Teleporting"); 96 97 return JSObject::reshapeForTeleporting(cx, proto); 98 } 99 100 JS_LOG(teleporting, Info, 101 "Shadowed Prop: Invalidating Reshape for Teleporting"); 102 return JSObject::setInvalidatedTeleporting(cx, proto); 103 } 104 105 proto = proto->staticPrototype(); 106 } 107 108 return true; 109 } 110 111 static void InvalidateMegamorphicCache(JSContext* cx, Handle<NativeObject*> obj, 112 bool invalidateGetPropCache = true) { 113 // The megamorphic cache only checks the receiver object's shape. We need to 114 // invalidate the cache when a prototype object changes its set of properties, 115 // to account for cached properties that are deleted, turned into an accessor 116 // property, or shadowed by another object on the proto chain. 117 118 MOZ_ASSERT(obj->isUsedAsPrototype()); 119 120 if (invalidateGetPropCache) { 121 cx->caches().megamorphicCache.bumpGeneration(); 122 } 123 cx->caches().megamorphicSetPropCache->bumpGeneration(); 124 } 125 126 void MaybePopReturnFuses(JSContext* cx, Handle<NativeObject*> nobj) { 127 GlobalObject* global = &nobj->global(); 128 JSObject* objectProto = &global->getObjectPrototype(); 129 if (nobj == objectProto) { 130 nobj->realm()->realmFuses.objectPrototypeHasNoReturnProperty.popFuse( 131 cx, nobj->realm()->realmFuses); 132 return; 133 } 134 135 JSObject* iteratorProto = global->maybeGetIteratorPrototype(); 136 if (nobj == iteratorProto) { 137 nobj->realm()->realmFuses.iteratorPrototypeHasNoReturnProperty.popFuse( 138 cx, nobj->realm()->realmFuses); 139 return; 140 } 141 142 JSObject* arrayIterProto = global->maybeGetArrayIteratorPrototype(); 143 if (nobj == arrayIterProto) { 144 nobj->realm()->realmFuses.arrayIteratorPrototypeHasNoReturnProperty.popFuse( 145 cx, nobj->realm()->realmFuses); 146 return; 147 } 148 } 149 150 // static 151 bool Watchtower::watchPropertyAddSlow(JSContext* cx, Handle<NativeObject*> obj, 152 HandleId id) { 153 MOZ_ASSERT(watchesPropertyAdd(obj)); 154 155 if (obj->isUsedAsPrototype()) { 156 if (!ReshapeForShadowedProp(cx, obj, id)) { 157 return false; 158 } 159 if (!id.isInt()) { 160 InvalidateMegamorphicCache(cx, obj); 161 } 162 163 if (id == NameToId(cx->names().return_)) { 164 MaybePopReturnFuses(cx, obj); 165 } 166 } 167 168 if (MOZ_UNLIKELY(obj->useWatchtowerTestingLog())) { 169 RootedValue val(cx, IdToValue(id)); 170 if (!AddToWatchtowerLog(cx, "add-prop", obj, val)) { 171 return false; 172 } 173 } 174 175 return true; 176 } 177 178 static bool ReshapeForProtoMutation(JSContext* cx, HandleObject obj) { 179 // To avoid the JIT guarding on each prototype in the proto chain to detect 180 // prototype mutation, we can instead reshape the rest of the proto chain such 181 // that a guard on any of them is sufficient. To avoid excessive reshaping and 182 // invalidation, we apply heuristics to decide when to apply this and when 183 // to require a guard. 184 // 185 // There are two cases: 186 // 187 // (1) The object is not marked IsUsedAsPrototype. This is the common case. 188 // Because shape implies proto, we rely on the caller changing the 189 // object's shape. The JIT guards on this object's shape or prototype so 190 // there's nothing we have to do here for objects on the proto chain. 191 // 192 // (2) The object is marked IsUsedAsPrototype. This implies the object may be 193 // participating in shape teleporting. To invalidate JIT ICs depending on 194 // the proto chain being unchanged, set the InvalidatedTeleporting shape 195 // flag for this object and objects on its proto chain. 196 // 197 // This flag disables future shape teleporting attempts, so next time this 198 // happens the loop below will be a no-op. 199 // 200 // NOTE: We only handle NativeObjects and don't propagate reshapes through 201 // any non-native objects on the chain. 202 // 203 // See Also: 204 // - GeneratePrototypeGuards 205 // - GeneratePrototypeHoleGuards 206 207 MOZ_ASSERT(obj->isUsedAsPrototype()); 208 209 RootedObject pobj(cx, obj); 210 211 bool useDictionaryTeleporting = 212 cx->zone()->shapeZone().useDictionaryModeTeleportation(); 213 214 while (pobj && pobj->is<NativeObject>()) { 215 if (pobj->hasObjectFuse()) { 216 if (auto* objFuse = 217 cx->zone()->objectFuses.get(pobj.as<NativeObject>())) { 218 objFuse->handleTeleportingProtoMutation(cx); 219 } 220 } 221 if (useDictionaryTeleporting) { 222 MOZ_ASSERT(!pobj->hasInvalidatedTeleporting(), 223 "Once we start using invalidation shouldn't do any more " 224 "dictionary mode teleportation"); 225 JS_LOG(teleporting, Debug, 226 "Proto Mutation: Dictionary Reshape for Teleporting"); 227 228 if (!JSObject::reshapeForTeleporting(cx, pobj)) { 229 return false; 230 } 231 } else if (!pobj->hasInvalidatedTeleporting()) { 232 JS_LOG(teleporting, Info, 233 "Proto Mutation: Invalidating Reshape for Teleporting"); 234 235 if (!JSObject::setInvalidatedTeleporting(cx, pobj)) { 236 return false; 237 } 238 } 239 pobj = pobj->staticPrototype(); 240 } 241 242 return true; 243 } 244 245 static constexpr bool IsTypedArrayProtoKey(JSProtoKey protoKey) { 246 switch (protoKey) { 247 #define PROTO_KEY(_, T, N) \ 248 case JSProto_##N##Array: \ 249 return true; 250 JS_FOR_EACH_TYPED_ARRAY(PROTO_KEY) 251 #undef PROTO_KEY 252 default: 253 return false; 254 } 255 } 256 257 static_assert( 258 !IsTypedArrayProtoKey(JSProto_TypedArray), 259 "IsTypedArrayProtoKey(JSProto_TypedArray) is expected to return false"); 260 261 static bool WatchProtoChangeImpl(JSContext* cx, HandleObject obj) { 262 if (!obj->isUsedAsPrototype()) { 263 return true; 264 } 265 if (!ReshapeForProtoMutation(cx, obj)) { 266 return false; 267 } 268 if (obj->is<NativeObject>()) { 269 InvalidateMegamorphicCache(cx, obj.as<NativeObject>()); 270 271 NativeObject* nobj = &obj->as<NativeObject>(); 272 if (nobj == nobj->global().maybeGetArrayIteratorPrototype()) { 273 nobj->realm()->realmFuses.arrayIteratorPrototypeHasIteratorProto.popFuse( 274 cx, nobj->realm()->realmFuses); 275 } 276 277 if (nobj == nobj->global().maybeGetIteratorPrototype()) { 278 nobj->realm()->realmFuses.iteratorPrototypeHasObjectProto.popFuse( 279 cx, nobj->realm()->realmFuses); 280 } 281 282 auto protoKey = StandardProtoKeyOrNull(nobj); 283 if (IsTypedArrayProtoKey(protoKey) && 284 nobj == nobj->global().maybeGetPrototype(protoKey)) { 285 nobj->realm()->realmFuses.optimizeTypedArraySpeciesFuse.popFuse( 286 cx, nobj->realm()->realmFuses); 287 } 288 } 289 290 return true; 291 } 292 293 // static 294 bool Watchtower::watchProtoChangeSlow(JSContext* cx, HandleObject obj) { 295 MOZ_ASSERT(watchesProtoChange(obj)); 296 297 if (!WatchProtoChangeImpl(cx, obj)) { 298 return false; 299 } 300 301 if (MOZ_UNLIKELY(obj->useWatchtowerTestingLog())) { 302 if (!AddToWatchtowerLog(cx, "proto-change", obj, 303 JS::UndefinedHandleValue)) { 304 return false; 305 } 306 } 307 308 return true; 309 } 310 311 static void MaybePopArrayConstructorFuses(JSContext* cx, NativeObject* obj, 312 jsid id) { 313 if (obj != obj->global().maybeGetConstructor(JSProto_Array)) { 314 return; 315 } 316 if (id.isWellKnownSymbol(JS::SymbolCode::species)) { 317 obj->realm()->realmFuses.optimizeArraySpeciesFuse.popFuse( 318 cx, obj->realm()->realmFuses); 319 } 320 } 321 322 static void MaybePopArrayPrototypeFuses(JSContext* cx, NativeObject* obj, 323 jsid id) { 324 if (obj != obj->global().maybeGetArrayPrototype()) { 325 return; 326 } 327 if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) { 328 obj->realm()->realmFuses.arrayPrototypeIteratorFuse.popFuse( 329 cx, obj->realm()->realmFuses); 330 } 331 if (id.isAtom(cx->names().constructor)) { 332 obj->realm()->realmFuses.optimizeArraySpeciesFuse.popFuse( 333 cx, obj->realm()->realmFuses); 334 } 335 } 336 337 static void MaybePopArrayIteratorPrototypeFuses(JSContext* cx, 338 NativeObject* obj, jsid id) { 339 if (obj != obj->global().maybeGetArrayIteratorPrototype()) { 340 return; 341 } 342 if (id.isAtom(cx->names().next)) { 343 obj->realm()->realmFuses.arrayPrototypeIteratorNextFuse.popFuse( 344 cx, obj->realm()->realmFuses); 345 } 346 } 347 348 static void MaybePopMapPrototypeFuses(JSContext* cx, NativeObject* obj, 349 jsid id) { 350 if (obj != obj->global().maybeGetPrototype(JSProto_Map)) { 351 return; 352 } 353 if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) { 354 obj->realm()->realmFuses.optimizeMapObjectIteratorFuse.popFuse( 355 cx, obj->realm()->realmFuses); 356 } 357 if (id.isAtom(cx->names().set)) { 358 obj->realm()->realmFuses.optimizeMapPrototypeSetFuse.popFuse( 359 cx, obj->realm()->realmFuses); 360 } 361 } 362 363 static void MaybePopMapIteratorPrototypeFuses(JSContext* cx, NativeObject* obj, 364 jsid id) { 365 if (obj != obj->global().maybeBuiltinProto( 366 GlobalObject::ProtoKind::MapIteratorProto)) { 367 return; 368 } 369 if (id.isAtom(cx->names().next)) { 370 obj->realm()->realmFuses.optimizeMapObjectIteratorFuse.popFuse( 371 cx, obj->realm()->realmFuses); 372 } 373 } 374 375 static void MaybePopSetPrototypeFuses(JSContext* cx, NativeObject* obj, 376 jsid id) { 377 if (obj != obj->global().maybeGetPrototype(JSProto_Set)) { 378 return; 379 } 380 if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) { 381 obj->realm()->realmFuses.optimizeSetObjectIteratorFuse.popFuse( 382 cx, obj->realm()->realmFuses); 383 } 384 if (id.isAtom(cx->names().add)) { 385 obj->realm()->realmFuses.optimizeSetPrototypeAddFuse.popFuse( 386 cx, obj->realm()->realmFuses); 387 } 388 } 389 390 static void MaybePopSetIteratorPrototypeFuses(JSContext* cx, NativeObject* obj, 391 jsid id) { 392 if (obj != obj->global().maybeBuiltinProto( 393 GlobalObject::ProtoKind::SetIteratorProto)) { 394 return; 395 } 396 if (id.isAtom(cx->names().next)) { 397 obj->realm()->realmFuses.optimizeSetObjectIteratorFuse.popFuse( 398 cx, obj->realm()->realmFuses); 399 } 400 } 401 402 static void MaybePopWeakMapPrototypeFuses(JSContext* cx, NativeObject* obj, 403 jsid id) { 404 if (obj != obj->global().maybeGetPrototype(JSProto_WeakMap)) { 405 return; 406 } 407 if (id.isAtom(cx->names().set)) { 408 obj->realm()->realmFuses.optimizeWeakMapPrototypeSetFuse.popFuse( 409 cx, obj->realm()->realmFuses); 410 } 411 } 412 413 static void MaybePopWeakSetPrototypeFuses(JSContext* cx, NativeObject* obj, 414 jsid id) { 415 if (obj != obj->global().maybeGetPrototype(JSProto_WeakSet)) { 416 return; 417 } 418 if (id.isAtom(cx->names().add)) { 419 obj->realm()->realmFuses.optimizeWeakSetPrototypeAddFuse.popFuse( 420 cx, obj->realm()->realmFuses); 421 } 422 } 423 424 static void MaybePopPromiseConstructorFuses(JSContext* cx, NativeObject* obj, 425 jsid id) { 426 if (obj != obj->global().maybeGetConstructor(JSProto_Promise)) { 427 return; 428 } 429 if (id.isWellKnownSymbol(JS::SymbolCode::species) || 430 id.isAtom(cx->names().resolve)) { 431 obj->realm()->realmFuses.optimizePromiseLookupFuse.popFuse( 432 cx, obj->realm()->realmFuses); 433 } 434 } 435 436 static void MaybePopPromisePrototypeFuses(JSContext* cx, NativeObject* obj, 437 jsid id) { 438 if (obj != obj->global().maybeGetPrototype(JSProto_Promise)) { 439 return; 440 } 441 if (id.isAtom(cx->names().constructor) || id.isAtom(cx->names().then)) { 442 obj->realm()->realmFuses.optimizePromiseLookupFuse.popFuse( 443 cx, obj->realm()->realmFuses); 444 } 445 } 446 447 static void MaybePopRegExpPrototypeFuses(JSContext* cx, NativeObject* obj, 448 jsid id) { 449 if (obj != obj->global().maybeGetPrototype(JSProto_RegExp)) { 450 return; 451 } 452 if (id.isAtom(cx->names().flags) || id.isAtom(cx->names().global) || 453 id.isAtom(cx->names().hasIndices) || id.isAtom(cx->names().ignoreCase) || 454 id.isAtom(cx->names().multiline) || id.isAtom(cx->names().sticky) || 455 id.isAtom(cx->names().unicode) || id.isAtom(cx->names().unicodeSets) || 456 id.isAtom(cx->names().dotAll) || id.isAtom(cx->names().exec) || 457 id.isWellKnownSymbol(JS::SymbolCode::match) || 458 id.isWellKnownSymbol(JS::SymbolCode::matchAll) || 459 id.isWellKnownSymbol(JS::SymbolCode::replace) || 460 id.isWellKnownSymbol(JS::SymbolCode::search) || 461 id.isWellKnownSymbol(JS::SymbolCode::split)) { 462 obj->realm()->realmFuses.optimizeRegExpPrototypeFuse.popFuse( 463 cx, obj->realm()->realmFuses); 464 } 465 } 466 467 static void MaybePopArrayBufferConstructorFuses(JSContext* cx, 468 NativeObject* obj, jsid id) { 469 if (obj != obj->global().maybeGetConstructor(JSProto_ArrayBuffer)) { 470 return; 471 } 472 if (id.isWellKnownSymbol(JS::SymbolCode::species)) { 473 obj->realm()->realmFuses.optimizeArrayBufferSpeciesFuse.popFuse( 474 cx, obj->realm()->realmFuses); 475 } 476 } 477 478 static void MaybePopArrayBufferPrototypeFuses(JSContext* cx, NativeObject* obj, 479 jsid id) { 480 if (obj != obj->global().maybeGetPrototype(JSProto_ArrayBuffer)) { 481 return; 482 } 483 if (id.isAtom(cx->names().constructor)) { 484 obj->realm()->realmFuses.optimizeArrayBufferSpeciesFuse.popFuse( 485 cx, obj->realm()->realmFuses); 486 } 487 } 488 489 static void MaybePopSharedArrayBufferConstructorFuses(JSContext* cx, 490 NativeObject* obj, 491 jsid id) { 492 if (obj != obj->global().maybeGetConstructor(JSProto_SharedArrayBuffer)) { 493 return; 494 } 495 if (id.isWellKnownSymbol(JS::SymbolCode::species)) { 496 obj->realm()->realmFuses.optimizeSharedArrayBufferSpeciesFuse.popFuse( 497 cx, obj->realm()->realmFuses); 498 } 499 } 500 501 static void MaybePopSharedArrayBufferPrototypeFuses(JSContext* cx, 502 NativeObject* obj, 503 jsid id) { 504 if (obj != obj->global().maybeGetPrototype(JSProto_SharedArrayBuffer)) { 505 return; 506 } 507 if (id.isAtom(cx->names().constructor)) { 508 obj->realm()->realmFuses.optimizeSharedArrayBufferSpeciesFuse.popFuse( 509 cx, obj->realm()->realmFuses); 510 } 511 } 512 513 static void MaybePopTypedArrayConstructorFuses(JSContext* cx, NativeObject* obj, 514 jsid id) { 515 if (obj != obj->global().maybeGetConstructor(JSProto_TypedArray)) { 516 return; 517 } 518 if (id.isWellKnownSymbol(JS::SymbolCode::species)) { 519 obj->realm()->realmFuses.optimizeTypedArraySpeciesFuse.popFuse( 520 cx, obj->realm()->realmFuses); 521 } 522 } 523 524 static void MaybePopTypedArrayPrototypeFuses(JSContext* cx, NativeObject* obj, 525 jsid id) { 526 auto protoKey = StandardProtoKeyOrNull(obj); 527 if (protoKey != JSProto_TypedArray && !IsTypedArrayProtoKey(protoKey)) { 528 return; 529 } 530 if (obj != obj->global().maybeGetPrototype(protoKey)) { 531 return; 532 } 533 if (id.isAtom(cx->names().constructor)) { 534 obj->realm()->realmFuses.optimizeTypedArraySpeciesFuse.popFuse( 535 cx, obj->realm()->realmFuses); 536 } 537 } 538 539 static void MaybePopRealmFuses(JSContext* cx, NativeObject* obj, jsid id) { 540 // Handle writes to Array constructor fuse properties. 541 MaybePopArrayConstructorFuses(cx, obj, id); 542 543 // Handle writes to Array.prototype fuse properties. 544 MaybePopArrayPrototypeFuses(cx, obj, id); 545 546 // Handle writes to %ArrayIteratorPrototype% fuse properties. 547 MaybePopArrayIteratorPrototypeFuses(cx, obj, id); 548 549 // Handle writes to Map.prototype fuse properties. 550 MaybePopMapPrototypeFuses(cx, obj, id); 551 552 // Handle writes to %MapIteratorPrototype% fuse properties. 553 MaybePopMapIteratorPrototypeFuses(cx, obj, id); 554 555 // Handle writes to Set.prototype fuse properties. 556 MaybePopSetPrototypeFuses(cx, obj, id); 557 558 // Handle writes to %SetIteratorPrototype% fuse properties. 559 MaybePopSetIteratorPrototypeFuses(cx, obj, id); 560 561 // Handle writes to WeakMap.prototype fuse properties. 562 MaybePopWeakMapPrototypeFuses(cx, obj, id); 563 564 // Handle writes to WeakSet.prototype fuse properties. 565 MaybePopWeakSetPrototypeFuses(cx, obj, id); 566 567 // Handle writes to Promise constructor fuse properties. 568 MaybePopPromiseConstructorFuses(cx, obj, id); 569 570 // Handle writes to Promise.prototype fuse properties. 571 MaybePopPromisePrototypeFuses(cx, obj, id); 572 573 // Handle writes to RegExp.prototype fuse properties. 574 MaybePopRegExpPrototypeFuses(cx, obj, id); 575 576 // Handle writes to ArrayBuffer constructor fuse properties. 577 MaybePopArrayBufferConstructorFuses(cx, obj, id); 578 579 // Handle writes to ArrayBuffer.prototype fuse properties. 580 MaybePopArrayBufferPrototypeFuses(cx, obj, id); 581 582 // Handle writes to SharedArrayBuffer constructor fuse properties. 583 MaybePopSharedArrayBufferConstructorFuses(cx, obj, id); 584 585 // Handle writes to SharedArrayBuffer.prototype fuse properties. 586 MaybePopSharedArrayBufferPrototypeFuses(cx, obj, id); 587 588 // Handle writes to %TypedArray% constructor fuse properties. 589 MaybePopTypedArrayConstructorFuses(cx, obj, id); 590 591 // Handle writes to %TypedArray%.prototype and concrete TypedArray.prototype 592 // fuse properties. 593 MaybePopTypedArrayPrototypeFuses(cx, obj, id); 594 } 595 596 // static 597 bool Watchtower::watchPropertyRemoveSlow(JSContext* cx, 598 Handle<NativeObject*> obj, HandleId id, 599 PropertyInfo propInfo, 600 bool* wasTrackedObjectFuseProp) { 601 MOZ_ASSERT(watchesPropertyRemove(obj)); 602 603 if (obj->isUsedAsPrototype() && !id.isInt()) { 604 InvalidateMegamorphicCache(cx, obj); 605 } 606 607 if (obj->isGenerationCountedGlobal()) { 608 obj->as<GlobalObject>().bumpGenerationCount(); 609 } 610 611 if (MOZ_UNLIKELY(obj->hasRealmFuseProperty())) { 612 MaybePopRealmFuses(cx, obj, id); 613 } 614 if (obj->hasObjectFuse()) { 615 if (auto* objFuse = cx->zone()->objectFuses.get(obj)) { 616 objFuse->handlePropertyRemove(cx, propInfo, wasTrackedObjectFuseProp); 617 } 618 } 619 620 if (MOZ_UNLIKELY(obj->useWatchtowerTestingLog())) { 621 RootedValue val(cx, IdToValue(id)); 622 if (!AddToWatchtowerLog(cx, "remove-prop", obj, val)) { 623 return false; 624 } 625 } 626 627 return true; 628 } 629 630 // static 631 bool Watchtower::watchPropertyFlagsChangeSlow(JSContext* cx, 632 Handle<NativeObject*> obj, 633 HandleId id, 634 PropertyInfo propInfo, 635 PropertyFlags newFlags) { 636 MOZ_ASSERT(watchesPropertyFlagsChange(obj)); 637 MOZ_ASSERT(obj->lookupPure(id).ref() == propInfo); 638 MOZ_ASSERT(propInfo.flags() != newFlags); 639 640 if (obj->isUsedAsPrototype() && !id.isInt()) { 641 InvalidateMegamorphicCache(cx, obj); 642 } 643 644 if (obj->isGenerationCountedGlobal()) { 645 // The global generation counter only cares whether a property 646 // changes from data property to accessor or vice-versa. Changing 647 // the flags on a property doesn't matter. 648 bool wasAccessor = propInfo.isAccessorProperty(); 649 bool isAccessor = newFlags.isAccessorProperty(); 650 if (wasAccessor != isAccessor) { 651 obj->as<GlobalObject>().bumpGenerationCount(); 652 } 653 } 654 655 if (MOZ_UNLIKELY(obj->useWatchtowerTestingLog())) { 656 RootedValue val(cx, IdToValue(id)); 657 if (!AddToWatchtowerLog(cx, "change-prop-flags", obj, val)) { 658 return false; 659 } 660 } 661 662 return true; 663 } 664 665 // static 666 template <AllowGC allowGC> 667 void Watchtower::watchPropertyValueChangeSlow( 668 JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, 669 typename MaybeRooted<PropertyKey, allowGC>::HandleType id, 670 typename MaybeRooted<Value, allowGC>::HandleType value, 671 PropertyInfo propInfo) { 672 MOZ_ASSERT(watchesPropertyValueChange(obj)); 673 674 // Note: this is also called when changing the GetterSetter value of an 675 // accessor property or when redefining a data property as an accessor 676 // property and vice versa. 677 678 // Handle object fuses before the check for no-op changes below. We don't 679 // attach SetProp stubs for constant properties, so if a constant property is 680 // overwritten with the same value, we want to mark it non-constant. 681 // See Watchtower::canOptimizeSetSlotSlow. 682 if (obj->hasObjectFuse()) { 683 if (auto* objFuse = cx->zone()->objectFuses.get(obj)) { 684 objFuse->handlePropertyValueChange(cx, propInfo); 685 } 686 } 687 688 if (propInfo.hasSlot() && obj->getSlot(propInfo.slot()) == value) { 689 // We're not actually changing the property's value. 690 return; 691 } 692 693 if (MOZ_UNLIKELY(obj->hasRealmFuseProperty())) { 694 MaybePopRealmFuses(cx, obj, id); 695 } 696 697 // If we cannot GC, we can't manipulate the log, but we need to be able to 698 // call this in places we cannot GC. 699 if constexpr (allowGC == AllowGC::CanGC) { 700 if (MOZ_UNLIKELY(obj->useWatchtowerTestingLog())) { 701 RootedValue val(cx, IdToValue(id)); 702 if (!AddToWatchtowerLog(cx, "change-prop-value", obj, val)) { 703 // Ignore OOM because this is just a testing feature and infallible 704 // watchPropertyValueChange simplifies the callers. 705 cx->clearPendingException(); 706 } 707 } 708 } 709 } 710 711 template void Watchtower::watchPropertyValueChangeSlow<AllowGC::CanGC>( 712 JSContext* cx, 713 typename MaybeRooted<NativeObject*, AllowGC::CanGC>::HandleType obj, 714 typename MaybeRooted<PropertyKey, AllowGC::CanGC>::HandleType id, 715 typename MaybeRooted<Value, AllowGC::CanGC>::HandleType value, 716 PropertyInfo propInfo); 717 template void Watchtower::watchPropertyValueChangeSlow<AllowGC::NoGC>( 718 JSContext* cx, 719 typename MaybeRooted<NativeObject*, AllowGC::NoGC>::HandleType obj, 720 typename MaybeRooted<PropertyKey, AllowGC::NoGC>::HandleType id, 721 typename MaybeRooted<Value, AllowGC::NoGC>::HandleType value, 722 PropertyInfo propInfo); 723 724 // static 725 SetSlotOptimizable Watchtower::canOptimizeSetSlotSlow(JSContext* cx, 726 NativeObject* obj, 727 PropertyInfo prop) { 728 MOZ_ASSERT(obj->hasObjectFuse()); 729 730 ObjectFuse* objFuse = cx->zone()->objectFuses.getOrCreate(cx, obj); 731 if (!objFuse) { 732 cx->recoverFromOutOfMemory(); 733 return SetSlotOptimizable::No; 734 } 735 736 if (objFuse->canOptimizeSetSlot(prop)) { 737 return SetSlotOptimizable::Yes; 738 } 739 740 // If a property is constant, there's no point in attaching a SetProp IC stub. 741 // The next time we set this property, we have to call into the VM to mark 742 // it NotConstant and potentially pop fuses. After that, we can attach a 743 // regular SetProp IC stub. If we never set this property again, there's no 744 // need to optimize this SetProp. 745 return SetSlotOptimizable::NotYet; 746 } 747 748 // static 749 bool Watchtower::watchFreezeOrSealSlow(JSContext* cx, Handle<NativeObject*> obj, 750 IntegrityLevel level) { 751 MOZ_ASSERT(watchesFreezeOrSeal(obj)); 752 753 // Invalidate the megamorphic set-property cache when freezing a prototype 754 // object. Non-writable prototype properties can't be shadowed (through 755 // SetProp) so this affects the behavior of add-property cache entries. 756 if (level == IntegrityLevel::Frozen && obj->isUsedAsPrototype()) { 757 InvalidateMegamorphicCache(cx, obj, /* invalidateGetPropCache = */ false); 758 } 759 760 if (MOZ_UNLIKELY(obj->useWatchtowerTestingLog())) { 761 if (!AddToWatchtowerLog(cx, "freeze-or-seal", obj, 762 JS::UndefinedHandleValue)) { 763 return false; 764 } 765 } 766 767 return true; 768 } 769 770 // static 771 bool Watchtower::watchObjectSwapSlow(JSContext* cx, HandleObject a, 772 HandleObject b) { 773 MOZ_ASSERT(watchesObjectSwap(a, b)); 774 775 // If we're swapping an object that's used as prototype, we're mutating the 776 // proto chains of other objects. Treat this as a proto change to ensure we 777 // invalidate shape teleporting and megamorphic caches. 778 if (!WatchProtoChangeImpl(cx, a)) { 779 return false; 780 } 781 if (!WatchProtoChangeImpl(cx, b)) { 782 return false; 783 } 784 785 if (a->hasObjectFuse()) { 786 if (auto* objFuse = cx->zone()->objectFuses.get(a.as<NativeObject>())) { 787 objFuse->handleObjectSwap(cx); 788 } 789 } 790 if (b->hasObjectFuse()) { 791 if (auto* objFuse = cx->zone()->objectFuses.get(b.as<NativeObject>())) { 792 objFuse->handleObjectSwap(cx); 793 } 794 } 795 796 // Note: we don't invoke the testing callback for swap because the objects may 797 // not be safe to expose to JS at this point. See bug 1754699. 798 799 return true; 800 }