tor-browser

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

testGCWeakCache.cpp (21989B)


      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 */
      4 /* This Source Code Form is subject to the terms of the Mozilla Public
      5 * License, v. 2.0. If a copy of the MPL was not distributed with this
      6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      7 
      8 #include "gc/Policy.h"
      9 #include "gc/Zone.h"
     10 #include "js/GCHashTable.h"
     11 #include "js/RootingAPI.h"
     12 #include "js/SweepingAPI.h"
     13 
     14 #include "jsapi-tests/tests.h"
     15 
     16 using namespace js;
     17 
     18 // Exercise WeakCache<GCHashSet>.
     19 BEGIN_TEST(testWeakCacheSet) {
     20  AutoLeaveZeal leaveZeal(cx);
     21 
     22  // Test using internal APIs.
     23  CHECK(test<HeapPtr<JSObject*>>(cx));
     24 
     25  // Test using APIs available to embedders.
     26  CHECK(test<JS::Heap<JSObject*>>(cx));
     27 
     28  return true;
     29 }
     30 
     31 template <typename T>
     32 bool test(JSContext* cx) {
     33  JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
     34  JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
     35  JS_GC(cx);
     36  JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
     37  JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
     38 
     39  using Set = GCHashSet<T, StableCellHasher<T>, SystemAllocPolicy>;
     40  using Cache = JS::WeakCache<Set>;
     41  Cache cache(JS::GetObjectZone(tenured1));
     42 
     43  CHECK(cache.put(tenured1));
     44  CHECK(cache.put(tenured2));
     45  CHECK(cache.put(nursery1));
     46  CHECK(cache.put(nursery2));
     47  cache.put(nullptr);  // nullptr entries should not be swept.
     48 
     49  // Verify relocation and that we don't sweep too aggressively.
     50  JS_GC(cx);
     51  CHECK(cache.has(tenured1));
     52  CHECK(cache.has(tenured2));
     53  CHECK(cache.has(nursery1));
     54  CHECK(cache.has(nursery2));
     55  CHECK(cache.has(nullptr));
     56  CHECK_EQUAL(cache.count(), size_t(5));
     57 
     58  // Unroot two entries and verify that they get removed.
     59  tenured2 = nursery2 = nullptr;
     60  JS_GC(cx);
     61  CHECK(cache.has(tenured1));
     62  CHECK(cache.has(nursery1));
     63  CHECK(cache.has(nullptr));
     64  CHECK_EQUAL(cache.count(), size_t(3));
     65 
     66  return true;
     67 }
     68 END_TEST(testWeakCacheSet)
     69 
     70 // Exercise WeakCache<GCHashMap> where the value is not a GC thing.
     71 BEGIN_TEST(testWeakCacheMapToNonGCThing) {
     72  AutoLeaveZeal leaveZeal(cx);
     73 
     74  uint32_t x = 0;
     75  CHECK((test<HeapPtr<JSObject*>, uint32_t>(cx, [&x]() { return x++; })));
     76 
     77  CHECK((test<HeapPtr<JSObject*>, UniquePtr<uint32_t>>(
     78      cx, [&x]() { return MakeUnique<uint32_t>(x++); })));
     79 
     80  return true;
     81 }
     82 
     83 template <typename Key, typename Value, typename ValueFunc>
     84 bool test(JSContext* cx, ValueFunc&& valueFunc) {
     85  JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
     86  JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
     87  JS_GC(cx);
     88  JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
     89  JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
     90 
     91  using Map = GCHashMap<Key, Value, StableCellHasher<Key>, SystemAllocPolicy>;
     92  using Cache = JS::WeakCache<Map>;
     93  Cache cache(JS::GetObjectZone(tenured1));
     94 
     95  cache.put(tenured1, valueFunc());
     96  cache.put(tenured2, valueFunc());
     97  cache.put(nursery1, valueFunc());
     98  cache.put(nursery2, valueFunc());
     99  cache.put(nullptr, valueFunc());  // nullptr entries should not be swept.
    100 
    101  JS_GC(cx);
    102  CHECK(cache.has(tenured1));
    103  CHECK(cache.has(tenured2));
    104  CHECK(cache.has(nursery1));
    105  CHECK(cache.has(nursery2));
    106  CHECK(cache.has(nullptr));
    107  CHECK_EQUAL(cache.count(), size_t(5));
    108 
    109  tenured2 = nursery2 = nullptr;
    110  JS_GC(cx);
    111  CHECK(cache.has(tenured1));
    112  CHECK(cache.has(nursery1));
    113  CHECK(cache.has(nullptr));
    114  CHECK_EQUAL(cache.count(), size_t(3));
    115 
    116  return true;
    117 }
    118 END_TEST(testWeakCacheMapToNonGCThing)
    119 
    120 // Exercise WeakCache<GCHashMap> where the value is JSObject pointer.
    121 BEGIN_TEST(testWeakCacheMapToObject) {
    122  AutoLeaveZeal leaveZeal(cx);
    123  AutoGCParameter enableIncremental(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
    124 
    125  CHECK((test<HeapPtr<JSObject*>, HeapPtr<JSObject*>>(cx)));
    126  CHECK((test<JS::Heap<JSObject*>, JS::Heap<JSObject*>>(cx)));
    127  return true;
    128 }
    129 
    130 template <typename Key, typename Value>
    131 bool test(JSContext* cx) {
    132  JS::RootedObject tenured1{cx, JS_NewPlainObject(cx)};
    133  JS::RootedObject tenured2{cx, JS_NewPlainObject(cx)};
    134  JS_GC(cx);
    135  JS::RootedObject nursery1{cx, JS_NewPlainObject(cx)};
    136  JS::RootedObject nursery2{cx, JS_NewPlainObject(cx)};
    137 
    138  using Map = GCHashMap<Key, Value, StableCellHasher<Key>, SystemAllocPolicy>;
    139  using Cache = JS::WeakCache<Map>;
    140  Cache cache(JS::GetObjectZone(tenured1));
    141 
    142  cache.put(tenured1, tenured1);
    143  cache.put(tenured2, tenured2);
    144  cache.put(nursery1, nursery2);  // These do not map to themselves.
    145  cache.put(nursery2, nursery1);
    146  cache.put(nullptr, nullptr);  // nullptr entries should not be swept.
    147 
    148  JS_GC(cx);
    149  CHECK(cache.has(tenured1));
    150  CHECK(cache.has(tenured2));
    151  CHECK(cache.has(nursery1));
    152  CHECK(cache.has(nursery2));
    153  CHECK(cache.has(nullptr));
    154  CHECK_EQUAL(cache.count(), size_t(5));
    155 
    156  tenured2 = nursery2 = nullptr;
    157  JS_GC(cx);
    158  CHECK(cache.has(tenured1));
    159  // nursery1 is gone because value is dead.
    160  CHECK(cache.has(nullptr));
    161  CHECK_EQUAL(cache.count(), size_t(2));
    162 
    163 #ifdef JS_GC_ZEAL
    164  // Test nursery objects are handled correctly if they are present during
    165  // sweeping. For this to happen they must get added during an incremental GC.
    166  JS::SetGCZeal(cx, 10, 100);
    167  for (size_t i = 0; i < 1000; i++) {
    168    Rooted<JSObject*> key(cx);
    169    Rooted<JSObject*> value(cx);
    170    key = JS_NewPlainObject(cx);
    171    CHECK(key);
    172    value = JS_NewPlainObject(cx);
    173    CHECK(value);
    174    CHECK(cache.put(key, value));
    175  }
    176  JS::SetGCZeal(cx, 0, 0);
    177  JS_GC(cx);
    178  CHECK_EQUAL(cache.count(), size_t(2));
    179 #endif  // JS_GC_ZEAL
    180 
    181  return true;
    182 }
    183 END_TEST(testWeakCacheMapToObject)
    184 
    185 // Exercise WeakCache<GCVector>.
    186 BEGIN_TEST(testWeakCacheGCVector) {
    187  AutoLeaveZeal leaveZeal(cx);
    188  CHECK(test<HeapPtr<JSObject*>>(cx));
    189  CHECK(test<JS::Heap<JSObject*>>(cx));
    190  return true;
    191 }
    192 
    193 template <typename Element>
    194 bool test(JSContext* cx) {
    195  // Create two objects tenured and two in the nursery. If zeal is on,
    196  // this may fail and we'll get more tenured objects. That's fine:
    197  // the test will continue to work, it will just not test as much.
    198  JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
    199  JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
    200  JS_GC(cx);
    201  JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
    202  JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
    203 
    204  using Vector = JS::WeakCache<GCVector<Element, 0, SystemAllocPolicy>>;
    205  Vector cache(JS::GetObjectZone(tenured1));
    206 
    207  CHECK(cache.append(tenured1));
    208  CHECK(cache.append(tenured2));
    209  CHECK(cache.append(nursery1));
    210  CHECK(cache.append(nursery2));
    211 
    212  JS_GC(cx);
    213  CHECK(cache.get().length() == 4);
    214  CHECK(cache.get()[0] == tenured1);
    215  CHECK(cache.get()[1] == tenured2);
    216  CHECK(cache.get()[2] == nursery1);
    217  CHECK(cache.get()[3] == nursery2);
    218 
    219  tenured2 = nursery2 = nullptr;
    220  JS_GC(cx);
    221  CHECK(cache.get().length() == 2);
    222  CHECK(cache.get()[0] == tenured1);
    223  CHECK(cache.get()[1] == nursery1);
    224 
    225  return true;
    226 }
    227 END_TEST(testWeakCacheGCVector)
    228 
    229 #ifdef JS_GC_ZEAL
    230 
    231 // A simple structure that embeds an object pointer. We cripple the hash
    232 // implementation so that we can test hash table collisions.
    233 struct ObjectEntry {
    234  HeapPtr<JSObject*> obj;
    235  explicit ObjectEntry(JSObject* o) : obj(o) {}
    236  bool operator==(const ObjectEntry& other) const { return obj == other.obj; }
    237  bool traceWeak(JSTracer* trc) {
    238    return TraceWeakEdge(trc, &obj, "ObjectEntry::obj");
    239  }
    240 };
    241 
    242 namespace js {
    243 template <>
    244 struct StableCellHasher<ObjectEntry> {
    245  using Key = ObjectEntry;
    246  using Lookup = JSObject*;
    247 
    248  static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) {
    249    if (!StableCellHasher<JSObject*>::maybeGetHash(l, hashOut)) {
    250      return false;
    251    }
    252    // Reduce hash code to single bit to generate hash collisions.
    253    *hashOut &= 0x1;
    254    return true;
    255  }
    256  static bool ensureHash(const Lookup& l, HashNumber* hashOut) {
    257    if (!StableCellHasher<JSObject*>::ensureHash(l, hashOut)) {
    258      return false;
    259    }
    260    // Reduce hash code to single bit to generate hash collisions.
    261    *hashOut &= 0x1;
    262    return true;
    263  }
    264  static HashNumber hash(const Lookup& l) {
    265    // Reduce hash code to single bit to generate hash collisions.
    266    return StableCellHasher<HeapPtr<JSObject*>>::hash(l) & 0x1;
    267  }
    268  static bool match(const Key& k, const Lookup& l) {
    269    return StableCellHasher<HeapPtr<JSObject*>>::match(k.obj, l);
    270  }
    271 };
    272 }  // namespace js
    273 
    274 // A structure that contains a pointer to a JSObject but is keyed based on an
    275 // integer. This lets us test replacing dying entries in a set.
    276 struct NumberAndObjectEntry {
    277  uint32_t number;
    278  HeapPtr<JSObject*> obj;
    279 
    280  NumberAndObjectEntry(uint32_t n, JSObject* o) : number(n), obj(o) {}
    281  bool operator==(const NumberAndObjectEntry& other) const {
    282    return number == other.number && obj == other.obj;
    283  }
    284  bool traceWeak(JSTracer* trc) {
    285    return TraceWeakEdge(trc, &obj, "NumberAndObjectEntry::obj");
    286  }
    287 };
    288 
    289 struct NumberAndObjectLookup {
    290  uint32_t number;
    291  HeapPtr<JSObject*> obj;
    292 
    293  NumberAndObjectLookup(uint32_t n, JSObject* o) : number(n), obj(o) {}
    294  MOZ_IMPLICIT NumberAndObjectLookup(const NumberAndObjectEntry& entry)
    295      : number(entry.number), obj(entry.obj) {}
    296 };
    297 
    298 namespace js {
    299 template <>
    300 struct StableCellHasher<NumberAndObjectEntry> {
    301  using Key = NumberAndObjectEntry;
    302  using Lookup = NumberAndObjectLookup;
    303 
    304  static bool maybeGetHash(const Lookup& l, HashNumber* hashOut) {
    305    if (!StableCellHasher<JSObject*>::maybeGetHash(l.obj, hashOut)) {
    306      return false;
    307    }
    308    *hashOut ^= l.number;
    309    return true;
    310  }
    311  static bool ensureHash(const Lookup& l, HashNumber* hashOut) {
    312    if (!StableCellHasher<JSObject*>::ensureHash(l.obj, hashOut)) {
    313      return false;
    314    }
    315    *hashOut ^= l.number;
    316    return true;
    317  }
    318  static HashNumber hash(const Lookup& l) {
    319    return StableCellHasher<HeapPtr<JSObject*>>::hash(l.obj) ^ l.number;
    320  }
    321  static bool match(const Key& k, const Lookup& l) {
    322    return k.number == l.number &&
    323           StableCellHasher<HeapPtr<JSObject*>>::match(k.obj, l.obj);
    324  }
    325 };
    326 }  // namespace js
    327 
    328 BEGIN_TEST(testIncrementalWeakCacheSweeping) {
    329  AutoLeaveZeal nozeal(cx);
    330 
    331  JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
    332  JS::SetGCZeal(cx, 17, 1000000);
    333 
    334  CHECK(TestSet());
    335  CHECK(TestMap());
    336  CHECK(TestReplaceDyingInSet());
    337  CHECK(TestReplaceDyingInMap());
    338  CHECK(TestUniqueIDLookups());
    339 
    340  JS::SetGCZeal(cx, 0, 0);
    341  JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, false);
    342 
    343  return true;
    344 }
    345 
    346 template <typename Cache>
    347 bool GCUntilCacheSweep(JSContext* cx, const Cache& cache) {
    348  CHECK(!IsIncrementalGCInProgress(cx));
    349 
    350  JS::Zone* zone = JS::GetObjectZone(global);
    351  JS::PrepareZoneForGC(cx, zone);
    352  JS::SliceBudget budget(JS::WorkBudget(1));
    353  cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget);
    354 
    355  CHECK(IsIncrementalGCInProgress(cx));
    356  CHECK(zone->isGCSweeping());
    357  CHECK(cache.needsIncrementalBarrier());
    358 
    359  return true;
    360 }
    361 
    362 template <typename Cache>
    363 bool SweepCacheAndFinishGC(JSContext* cx, const Cache& cache) {
    364  CHECK(IsIncrementalGCInProgress(cx));
    365 
    366  PrepareForIncrementalGC(cx);
    367  IncrementalGCSlice(cx, JS::GCReason::API, JS::SliceBudget::unlimited());
    368 
    369  JS::Zone* zone = JS::GetObjectZone(global);
    370  CHECK(!IsIncrementalGCInProgress(cx));
    371  CHECK(!zone->isCollecting());
    372  CHECK(!cache.needsIncrementalBarrier());
    373 
    374  return true;
    375 }
    376 
    377 bool TestSet() {
    378  using ObjectSet =
    379      GCHashSet<HeapPtr<JSObject*>, StableCellHasher<HeapPtr<JSObject*>>,
    380                TempAllocPolicy>;
    381  using Cache = JS::WeakCache<ObjectSet>;
    382  Cache cache(JS::GetObjectZone(global), cx);
    383 
    384  // Sweep empty cache.
    385 
    386  CHECK(cache.empty());
    387  JS_GC(cx);
    388  CHECK(cache.empty());
    389 
    390  // Add an entry while sweeping.
    391 
    392  JS::RootedObject obj1(cx, JS_NewPlainObject(cx));
    393  JS::RootedObject obj2(cx, JS_NewPlainObject(cx));
    394  JS::RootedObject obj3(cx, JS_NewPlainObject(cx));
    395  JS::RootedObject obj4(cx, JS_NewPlainObject(cx));
    396  CHECK(obj1);
    397  CHECK(obj2);
    398  CHECK(obj3);
    399  CHECK(obj4);
    400 
    401  CHECK(!cache.has(obj1));
    402  CHECK(cache.put(obj1));
    403  CHECK(cache.count() == 1);
    404  CHECK(cache.has(obj1));
    405  CHECK(*cache.lookup(obj1) == obj1);
    406 
    407  CHECK(GCUntilCacheSweep(cx, cache));
    408 
    409  CHECK(!cache.has(obj2));
    410  CHECK(cache.put(obj2));
    411  CHECK(cache.has(obj2));
    412  CHECK(*cache.lookup(obj2) == obj2);
    413 
    414  CHECK(SweepCacheAndFinishGC(cx, cache));
    415 
    416  CHECK(cache.count() == 2);
    417  CHECK(cache.has(obj1));
    418  CHECK(cache.has(obj2));
    419 
    420  // Test dying entries are not found while sweeping.
    421 
    422  CHECK(cache.put(obj3));
    423  CHECK(cache.put(obj4));
    424  void* old3 = obj3;
    425  void* old4 = obj4;
    426  obj3 = obj4 = nullptr;
    427 
    428  CHECK(GCUntilCacheSweep(cx, cache));
    429 
    430  CHECK(cache.has(obj1));
    431  CHECK(cache.has(obj2));
    432  CHECK(!cache.has(static_cast<JSObject*>(old3)));
    433  CHECK(!cache.has(static_cast<JSObject*>(old4)));
    434 
    435  size_t count = 0;
    436  for (auto r = cache.all(); !r.empty(); r.popFront()) {
    437    CHECK(r.front() == obj1 || r.front() == obj2);
    438    count++;
    439  }
    440  CHECK(count == 2);
    441 
    442  CHECK(SweepCacheAndFinishGC(cx, cache));
    443 
    444  CHECK(cache.count() == 2);
    445 
    446  // Test lookupForAdd while sweeping.
    447 
    448  obj3 = JS_NewPlainObject(cx);
    449  obj4 = JS_NewPlainObject(cx);
    450  CHECK(obj3);
    451  CHECK(obj4);
    452 
    453  CHECK(cache.lookupForAdd(obj1));
    454  CHECK(*cache.lookupForAdd(obj1) == obj1);
    455 
    456  auto addp = cache.lookupForAdd(obj3);
    457  CHECK(!addp);
    458  CHECK(cache.add(addp, obj3));
    459  CHECK(cache.has(obj3));
    460 
    461  CHECK(GCUntilCacheSweep(cx, cache));
    462 
    463  addp = cache.lookupForAdd(obj4);
    464  CHECK(!addp);
    465  CHECK(cache.add(addp, obj4));
    466  CHECK(cache.has(obj4));
    467 
    468  CHECK(SweepCacheAndFinishGC(cx, cache));
    469 
    470  CHECK(cache.count() == 4);
    471  CHECK(cache.has(obj3));
    472  CHECK(cache.has(obj4));
    473 
    474  // Test remove while sweeping.
    475 
    476  cache.remove(obj3);
    477 
    478  CHECK(GCUntilCacheSweep(cx, cache));
    479 
    480  cache.remove(obj4);
    481 
    482  CHECK(SweepCacheAndFinishGC(cx, cache));
    483 
    484  CHECK(cache.count() == 2);
    485  CHECK(!cache.has(obj3));
    486  CHECK(!cache.has(obj4));
    487 
    488  // Test putNew while sweeping.
    489 
    490  CHECK(GCUntilCacheSweep(cx, cache));
    491 
    492  CHECK(cache.putNew(obj3));
    493  CHECK(cache.putNew(obj4, obj4));
    494 
    495  CHECK(SweepCacheAndFinishGC(cx, cache));
    496 
    497  CHECK(cache.count() == 4);
    498  CHECK(cache.has(obj3));
    499  CHECK(cache.has(obj4));
    500 
    501  cache.clear();
    502 
    503  return true;
    504 }
    505 
    506 bool TestMap() {
    507  using ObjectMap =
    508      GCHashMap<HeapPtr<JSObject*>, uint32_t,
    509                StableCellHasher<HeapPtr<JSObject*>>, TempAllocPolicy>;
    510  using Cache = JS::WeakCache<ObjectMap>;
    511  Cache cache(JS::GetObjectZone(global), cx);
    512 
    513  // Sweep empty cache.
    514 
    515  CHECK(cache.empty());
    516  JS_GC(cx);
    517  CHECK(cache.empty());
    518 
    519  // Add an entry while sweeping.
    520 
    521  JS::RootedObject obj1(cx, JS_NewPlainObject(cx));
    522  JS::RootedObject obj2(cx, JS_NewPlainObject(cx));
    523  JS::RootedObject obj3(cx, JS_NewPlainObject(cx));
    524  JS::RootedObject obj4(cx, JS_NewPlainObject(cx));
    525  CHECK(obj1);
    526  CHECK(obj2);
    527  CHECK(obj3);
    528  CHECK(obj4);
    529 
    530  CHECK(!cache.has(obj1));
    531  CHECK(cache.put(obj1, 1));
    532  CHECK(cache.count() == 1);
    533  CHECK(cache.has(obj1));
    534  CHECK(cache.lookup(obj1)->key() == obj1);
    535 
    536  CHECK(GCUntilCacheSweep(cx, cache));
    537  CHECK(cache.needsIncrementalBarrier());
    538 
    539  CHECK(!cache.has(obj2));
    540  CHECK(cache.put(obj2, 2));
    541  CHECK(cache.has(obj2));
    542  CHECK(cache.lookup(obj2)->key() == obj2);
    543 
    544  CHECK(SweepCacheAndFinishGC(cx, cache));
    545  CHECK(!cache.needsIncrementalBarrier());
    546 
    547  CHECK(cache.count() == 2);
    548  CHECK(cache.has(obj1));
    549  CHECK(cache.has(obj2));
    550 
    551  // Test iteration.
    552 
    553  CHECK(cache.put(obj3, 3));
    554  CHECK(cache.put(obj4, 4));
    555  void* old3 = obj3;
    556  void* old4 = obj4;
    557  obj3 = obj4 = nullptr;
    558 
    559  CHECK(GCUntilCacheSweep(cx, cache));
    560 
    561  CHECK(cache.has(obj1));
    562  CHECK(cache.has(obj2));
    563  CHECK(!cache.has(static_cast<JSObject*>(old3)));
    564  CHECK(!cache.has(static_cast<JSObject*>(old4)));
    565 
    566  size_t count = 0;
    567  for (auto r = cache.all(); !r.empty(); r.popFront()) {
    568    CHECK(r.front().key() == obj1 || r.front().key() == obj2);
    569    count++;
    570  }
    571  CHECK(count == 2);
    572 
    573  CHECK(SweepCacheAndFinishGC(cx, cache));
    574 
    575  CHECK(cache.count() == 2);
    576 
    577  // Test lookupForAdd while sweeping.
    578 
    579  obj3 = JS_NewPlainObject(cx);
    580  obj4 = JS_NewPlainObject(cx);
    581  CHECK(obj3);
    582  CHECK(obj4);
    583 
    584  CHECK(cache.lookupForAdd(obj1));
    585  CHECK(cache.lookupForAdd(obj1)->key() == obj1);
    586 
    587  auto addp = cache.lookupForAdd(obj3);
    588  CHECK(!addp);
    589  CHECK(cache.add(addp, obj3, 3));
    590  CHECK(cache.has(obj3));
    591 
    592  CHECK(GCUntilCacheSweep(cx, cache));
    593 
    594  addp = cache.lookupForAdd(obj4);
    595  CHECK(!addp);
    596  CHECK(cache.add(addp, obj4, 4));
    597  CHECK(cache.has(obj4));
    598 
    599  CHECK(SweepCacheAndFinishGC(cx, cache));
    600 
    601  CHECK(cache.count() == 4);
    602  CHECK(cache.has(obj3));
    603  CHECK(cache.has(obj4));
    604 
    605  // Test remove while sweeping.
    606 
    607  cache.remove(obj3);
    608 
    609  CHECK(GCUntilCacheSweep(cx, cache));
    610 
    611  cache.remove(obj4);
    612 
    613  CHECK(SweepCacheAndFinishGC(cx, cache));
    614 
    615  CHECK(cache.count() == 2);
    616  CHECK(!cache.has(obj3));
    617  CHECK(!cache.has(obj4));
    618 
    619  // Test putNew while sweeping.
    620 
    621  CHECK(GCUntilCacheSweep(cx, cache));
    622 
    623  CHECK(cache.putNew(obj3, 3));
    624  CHECK(cache.putNew(obj4, 4));
    625 
    626  CHECK(SweepCacheAndFinishGC(cx, cache));
    627 
    628  CHECK(cache.count() == 4);
    629  CHECK(cache.has(obj3));
    630  CHECK(cache.has(obj4));
    631 
    632  cache.clear();
    633 
    634  return true;
    635 }
    636 
    637 bool TestReplaceDyingInSet() {
    638  // Test replacing dying entries with ones that have the same key using the
    639  // various APIs.
    640 
    641  using Cache = JS::WeakCache<
    642      GCHashSet<NumberAndObjectEntry, StableCellHasher<NumberAndObjectEntry>,
    643                TempAllocPolicy>>;
    644  Cache cache(JS::GetObjectZone(global), cx);
    645 
    646  RootedObject value1(cx, JS_NewPlainObject(cx));
    647  RootedObject value2(cx, JS_NewPlainObject(cx));
    648  CHECK(value1);
    649  CHECK(value2);
    650 
    651  CHECK(cache.put(NumberAndObjectEntry(1, value1)));
    652  CHECK(cache.put(NumberAndObjectEntry(2, value2)));
    653  CHECK(cache.put(NumberAndObjectEntry(3, value2)));
    654  CHECK(cache.put(NumberAndObjectEntry(4, value2)));
    655  CHECK(cache.put(NumberAndObjectEntry(5, value2)));
    656  CHECK(cache.put(NumberAndObjectEntry(6, value2)));
    657  CHECK(cache.put(NumberAndObjectEntry(7, value2)));
    658 
    659  value2 = nullptr;
    660  CHECK(GCUntilCacheSweep(cx, cache));
    661 
    662  CHECK(!cache.has(NumberAndObjectLookup(2, value1)));
    663  CHECK(!cache.has(NumberAndObjectLookup(3, value1)));
    664  CHECK(!cache.has(NumberAndObjectLookup(4, value1)));
    665  CHECK(!cache.has(NumberAndObjectLookup(5, value1)));
    666  CHECK(!cache.has(NumberAndObjectLookup(6, value1)));
    667 
    668  auto ptr = cache.lookupForAdd(NumberAndObjectLookup(2, value1));
    669  CHECK(!ptr);
    670  CHECK(cache.add(ptr, NumberAndObjectEntry(2, value1)));
    671 
    672  auto ptr2 = cache.lookupForAdd(NumberAndObjectLookup(3, value1));
    673  CHECK(!ptr2);
    674  CHECK(cache.relookupOrAdd(ptr2, NumberAndObjectLookup(3, value1),
    675                            NumberAndObjectEntry(3, value1)));
    676 
    677  CHECK(cache.put(NumberAndObjectEntry(4, value1)));
    678  CHECK(cache.putNew(NumberAndObjectEntry(5, value1)));
    679 
    680  CHECK(cache.putNew(NumberAndObjectLookup(6, value1),
    681                     NumberAndObjectEntry(6, value1)));
    682 
    683  CHECK(SweepCacheAndFinishGC(cx, cache));
    684 
    685  CHECK(cache.count() == 6);
    686  CHECK(cache.has(NumberAndObjectLookup(1, value1)));
    687  CHECK(cache.has(NumberAndObjectLookup(2, value1)));
    688  CHECK(cache.has(NumberAndObjectLookup(3, value1)));
    689  CHECK(cache.has(NumberAndObjectLookup(4, value1)));
    690  CHECK(cache.has(NumberAndObjectLookup(5, value1)));
    691  CHECK(cache.has(NumberAndObjectLookup(6, value1)));
    692 
    693  return true;
    694 }
    695 
    696 bool TestReplaceDyingInMap() {
    697  // Test replacing dying entries with ones that have the same key using the
    698  // various APIs.
    699 
    700  using Cache =
    701      JS::WeakCache<GCHashMap<uint32_t, HeapPtr<JSObject*>,
    702                              DefaultHasher<uint32_t>, TempAllocPolicy>>;
    703  Cache cache(JS::GetObjectZone(global), cx);
    704 
    705  RootedObject value1(cx, JS_NewPlainObject(cx));
    706  RootedObject value2(cx, JS_NewPlainObject(cx));
    707  CHECK(value1);
    708  CHECK(value2);
    709 
    710  CHECK(cache.put(1, value1));
    711  CHECK(cache.put(2, value2));
    712  CHECK(cache.put(3, value2));
    713  CHECK(cache.put(4, value2));
    714  CHECK(cache.put(5, value2));
    715  CHECK(cache.put(6, value2));
    716 
    717  value2 = nullptr;
    718  CHECK(GCUntilCacheSweep(cx, cache));
    719 
    720  CHECK(!cache.has(2));
    721  CHECK(!cache.has(3));
    722  CHECK(!cache.has(4));
    723  CHECK(!cache.has(5));
    724  CHECK(!cache.has(6));
    725 
    726  auto ptr = cache.lookupForAdd(2);
    727  CHECK(!ptr);
    728  CHECK(cache.add(ptr, 2, value1));
    729 
    730  auto ptr2 = cache.lookupForAdd(3);
    731  CHECK(!ptr2);
    732  CHECK(cache.add(ptr2, 3, HeapPtr<JSObject*>()));
    733 
    734  auto ptr3 = cache.lookupForAdd(4);
    735  CHECK(!ptr3);
    736  CHECK(cache.relookupOrAdd(ptr3, 4, value1));
    737 
    738  CHECK(cache.put(5, value1));
    739  CHECK(cache.putNew(6, value1));
    740 
    741  CHECK(SweepCacheAndFinishGC(cx, cache));
    742 
    743  CHECK(cache.count() == 6);
    744  CHECK(cache.lookup(1)->value() == value1);
    745  CHECK(cache.lookup(2)->value() == value1);
    746  CHECK(cache.lookup(3)->value() == nullptr);
    747  CHECK(cache.lookup(4)->value() == value1);
    748  CHECK(cache.lookup(5)->value() == value1);
    749  CHECK(cache.lookup(6)->value() == value1);
    750 
    751  return true;
    752 }
    753 
    754 bool TestUniqueIDLookups() {
    755  // Test hash table lookups during incremental sweeping where the hash is
    756  // generated based on a unique ID. The problem is that the unique ID table
    757  // will have already been swept by this point so looking up a dead pointer
    758  // in the table will fail. This lookup happens if we try to match a live key
    759  // against a dead table entry with the same hash code.
    760 
    761  const size_t DeadFactor = 3;
    762  const size_t ObjectCount = 100;
    763 
    764  using Cache = JS::WeakCache<
    765      GCHashSet<ObjectEntry, StableCellHasher<ObjectEntry>, TempAllocPolicy>>;
    766  Cache cache(JS::GetObjectZone(global), cx);
    767 
    768  Rooted<GCVector<JSObject*, 0, SystemAllocPolicy>> liveObjects(cx);
    769 
    770  for (size_t j = 0; j < ObjectCount; j++) {
    771    JSObject* obj = JS_NewPlainObject(cx);
    772    CHECK(obj);
    773    CHECK(cache.put(obj));
    774    if (j % DeadFactor == 0) {
    775      CHECK(liveObjects.get().append(obj));
    776    }
    777  }
    778 
    779  CHECK(cache.count() == ObjectCount);
    780 
    781  CHECK(GCUntilCacheSweep(cx, cache));
    782 
    783  for (size_t j = 0; j < liveObjects.length(); j++) {
    784    CHECK(cache.has(liveObjects[j]));
    785  }
    786 
    787  CHECK(SweepCacheAndFinishGC(cx, cache));
    788 
    789  CHECK(cache.count() == liveObjects.length());
    790 
    791  return true;
    792 }
    793 
    794 END_TEST(testIncrementalWeakCacheSweeping)
    795 
    796 #endif  // defined JS_GC_ZEAL