tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }