tor-browser

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

testHashTable.cpp (12482B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "mozilla/HashFunctions.h"
      6 
      7 #include <utility>
      8 
      9 #include "js/HashTable.h"
     10 #include "js/Utility.h"
     11 #include "jsapi-tests/tests.h"
     12 
     13 // #define FUZZ
     14 
     15 using IntMap = js::HashMap<uint32_t, uint32_t, js::DefaultHasher<uint32_t>,
     16                           js::SystemAllocPolicy>;
     17 using IntSet =
     18    js::HashSet<uint32_t, js::DefaultHasher<uint32_t>, js::SystemAllocPolicy>;
     19 
     20 /*
     21 * The rekeying test as conducted by adding only keys masked with 0x0000FFFF
     22 * that are unique. We rekey by shifting left 16 bits.
     23 */
     24 #ifdef FUZZ
     25 const size_t TestSize = 0x0000FFFF / 2;
     26 const size_t TestIterations = SIZE_MAX;
     27 #else
     28 const size_t TestSize = 10000;
     29 const size_t TestIterations = 10;
     30 #endif
     31 
     32 static_assert(TestSize <= 0x0000FFFF / 2);
     33 
     34 struct LowToHigh {
     35  static uint32_t rekey(uint32_t initial) {
     36    if (initial > uint32_t(0x0000FFFF)) {
     37      return initial;
     38    }
     39    return initial << 16;
     40  }
     41 
     42  static bool shouldBeRemoved(uint32_t initial) { return false; }
     43 };
     44 
     45 struct LowToHighWithRemoval {
     46  static uint32_t rekey(uint32_t initial) {
     47    if (initial > uint32_t(0x0000FFFF)) {
     48      return initial;
     49    }
     50    return initial << 16;
     51  }
     52 
     53  static bool shouldBeRemoved(uint32_t initial) {
     54    if (initial >= 0x00010000) {
     55      return (initial >> 16) % 2 == 0;
     56    }
     57    return initial % 2 == 0;
     58  }
     59 };
     60 
     61 static bool MapsAreEqual(IntMap& am, IntMap& bm) {
     62  bool equal = true;
     63  if (am.count() != bm.count()) {
     64    equal = false;
     65    fprintf(stderr, "A.count() == %u and B.count() == %u\n", am.count(),
     66            bm.count());
     67  }
     68  for (auto iter = am.iter(); !iter.done(); iter.next()) {
     69    if (!bm.has(iter.get().key())) {
     70      equal = false;
     71      fprintf(stderr, "B does not have %x which is in A\n", iter.get().key());
     72    }
     73  }
     74  for (auto iter = bm.iter(); !iter.done(); iter.next()) {
     75    if (!am.has(iter.get().key())) {
     76      equal = false;
     77      fprintf(stderr, "A does not have %x which is in B\n", iter.get().key());
     78    }
     79  }
     80  return equal;
     81 }
     82 
     83 static bool SetsAreEqual(IntSet& am, IntSet& bm) {
     84  bool equal = true;
     85  if (am.count() != bm.count()) {
     86    equal = false;
     87    fprintf(stderr, "A.count() == %u and B.count() == %u\n", am.count(),
     88            bm.count());
     89  }
     90  for (auto iter = am.iter(); !iter.done(); iter.next()) {
     91    if (!bm.has(iter.get())) {
     92      equal = false;
     93      fprintf(stderr, "B does not have %x which is in A\n", iter.get());
     94    }
     95  }
     96  for (auto iter = bm.iter(); !iter.done(); iter.next()) {
     97    if (!am.has(iter.get())) {
     98      equal = false;
     99      fprintf(stderr, "A does not have %x which is in B\n", iter.get());
    100    }
    101  }
    102  return equal;
    103 }
    104 
    105 static bool AddLowKeys(IntMap* am, IntMap* bm, int seed) {
    106  size_t i = 0;
    107  srand(seed);
    108  while (i < TestSize) {
    109    uint32_t n = rand() & 0x0000FFFF;
    110    if (!am->has(n)) {
    111      if (bm->has(n)) {
    112        return false;
    113      }
    114 
    115      if (!am->putNew(n, n) || !bm->putNew(n, n)) {
    116        return false;
    117      }
    118      i++;
    119    }
    120  }
    121  return true;
    122 }
    123 
    124 static bool AddLowKeys(IntSet* as, IntSet* bs, int seed) {
    125  size_t i = 0;
    126  srand(seed);
    127  while (i < TestSize) {
    128    uint32_t n = rand() & 0x0000FFFF;
    129    if (!as->has(n)) {
    130      if (bs->has(n)) {
    131        return false;
    132      }
    133      if (!as->putNew(n) || !bs->putNew(n)) {
    134        return false;
    135      }
    136      i++;
    137    }
    138  }
    139  return true;
    140 }
    141 
    142 template <class NewKeyFunction>
    143 static bool SlowRekey(IntMap* m) {
    144  IntMap tmp;
    145 
    146  for (auto iter = m->iter(); !iter.done(); iter.next()) {
    147    if (NewKeyFunction::shouldBeRemoved(iter.get().key())) {
    148      continue;
    149    }
    150    uint32_t hi = NewKeyFunction::rekey(iter.get().key());
    151    if (tmp.has(hi)) {
    152      return false;
    153    }
    154    if (!tmp.putNew(hi, iter.get().value())) {
    155      return false;
    156    }
    157  }
    158 
    159  m->clear();
    160  for (auto iter = tmp.iter(); !iter.done(); iter.next()) {
    161    if (!m->putNew(iter.get().key(), iter.get().value())) {
    162      return false;
    163    }
    164  }
    165 
    166  return true;
    167 }
    168 
    169 template <class NewKeyFunction>
    170 static bool SlowRekey(IntSet* s) {
    171  IntSet tmp;
    172 
    173  for (auto iter = s->iter(); !iter.done(); iter.next()) {
    174    if (NewKeyFunction::shouldBeRemoved(iter.get())) {
    175      continue;
    176    }
    177    uint32_t hi = NewKeyFunction::rekey(iter.get());
    178    if (tmp.has(hi)) {
    179      return false;
    180    }
    181    if (!tmp.putNew(hi)) {
    182      return false;
    183    }
    184  }
    185 
    186  s->clear();
    187  for (auto iter = tmp.iter(); !iter.done(); iter.next()) {
    188    if (!s->putNew(iter.get())) {
    189      return false;
    190    }
    191  }
    192 
    193  return true;
    194 }
    195 
    196 BEGIN_TEST(testHashRekeyManual) {
    197  IntMap am, bm;
    198  for (size_t i = 0; i < TestIterations; ++i) {
    199 #ifdef FUZZ
    200    fprintf(stderr, "map1: %lu\n", i);
    201 #endif
    202    CHECK(AddLowKeys(&am, &bm, i));
    203    CHECK(MapsAreEqual(am, bm));
    204 
    205    for (auto iter = am.modIter(); !iter.done(); iter.next()) {
    206      uint32_t tmp = LowToHigh::rekey(iter.get().key());
    207      if (tmp != iter.get().key()) {
    208        iter.rekey(tmp);
    209      }
    210    }
    211    CHECK(SlowRekey<LowToHigh>(&bm));
    212 
    213    CHECK(MapsAreEqual(am, bm));
    214    am.clear();
    215    bm.clear();
    216  }
    217 
    218  IntSet as, bs;
    219  for (size_t i = 0; i < TestIterations; ++i) {
    220 #ifdef FUZZ
    221    fprintf(stderr, "set1: %lu\n", i);
    222 #endif
    223    CHECK(AddLowKeys(&as, &bs, i));
    224    CHECK(SetsAreEqual(as, bs));
    225 
    226    for (auto iter = as.modIter(); !iter.done(); iter.next()) {
    227      uint32_t tmp = LowToHigh::rekey(iter.get());
    228      if (tmp != iter.get()) {
    229        iter.rekey(tmp);
    230      }
    231    }
    232    CHECK(SlowRekey<LowToHigh>(&bs));
    233 
    234    CHECK(SetsAreEqual(as, bs));
    235    as.clear();
    236    bs.clear();
    237  }
    238 
    239  return true;
    240 }
    241 END_TEST(testHashRekeyManual)
    242 
    243 BEGIN_TEST(testHashRekeyManualRemoval) {
    244  IntMap am, bm;
    245  for (size_t i = 0; i < TestIterations; ++i) {
    246 #ifdef FUZZ
    247    fprintf(stderr, "map2: %lu\n", i);
    248 #endif
    249    CHECK(AddLowKeys(&am, &bm, i));
    250    CHECK(MapsAreEqual(am, bm));
    251 
    252    for (auto iter = am.modIter(); !iter.done(); iter.next()) {
    253      if (LowToHighWithRemoval::shouldBeRemoved(iter.get().key())) {
    254        iter.remove();
    255      } else {
    256        uint32_t tmp = LowToHighWithRemoval::rekey(iter.get().key());
    257        if (tmp != iter.get().key()) {
    258          iter.rekey(tmp);
    259        }
    260      }
    261    }
    262    CHECK(SlowRekey<LowToHighWithRemoval>(&bm));
    263 
    264    CHECK(MapsAreEqual(am, bm));
    265    am.clear();
    266    bm.clear();
    267  }
    268 
    269  IntSet as, bs;
    270  for (size_t i = 0; i < TestIterations; ++i) {
    271 #ifdef FUZZ
    272    fprintf(stderr, "set1: %lu\n", i);
    273 #endif
    274    CHECK(AddLowKeys(&as, &bs, i));
    275    CHECK(SetsAreEqual(as, bs));
    276 
    277    for (auto iter = as.modIter(); !iter.done(); iter.next()) {
    278      if (LowToHighWithRemoval::shouldBeRemoved(iter.get())) {
    279        iter.remove();
    280      } else {
    281        uint32_t tmp = LowToHighWithRemoval::rekey(iter.get());
    282        if (tmp != iter.get()) {
    283          iter.rekey(tmp);
    284        }
    285      }
    286    }
    287    CHECK(SlowRekey<LowToHighWithRemoval>(&bs));
    288 
    289    CHECK(SetsAreEqual(as, bs));
    290    as.clear();
    291    bs.clear();
    292  }
    293 
    294  return true;
    295 }
    296 END_TEST(testHashRekeyManualRemoval)
    297 
    298 // A type that is not copyable, only movable.
    299 struct MoveOnlyType {
    300  uint32_t val;
    301 
    302  explicit MoveOnlyType(uint32_t val) : val(val) {}
    303 
    304  MoveOnlyType(MoveOnlyType&& rhs) { val = rhs.val; }
    305 
    306  MoveOnlyType& operator=(MoveOnlyType&& rhs) {
    307    MOZ_ASSERT(&rhs != this);
    308    this->~MoveOnlyType();
    309    new (this) MoveOnlyType(std::move(rhs));
    310    return *this;
    311  }
    312 
    313  struct HashPolicy {
    314    using Lookup = MoveOnlyType;
    315 
    316    static js::HashNumber hash(const Lookup& lookup) { return lookup.val; }
    317 
    318    static bool match(const MoveOnlyType& existing, const Lookup& lookup) {
    319      return existing.val == lookup.val;
    320    }
    321  };
    322 
    323 private:
    324  MoveOnlyType(const MoveOnlyType&) = delete;
    325  MoveOnlyType& operator=(const MoveOnlyType&) = delete;
    326 };
    327 
    328 BEGIN_TEST(testHashSetOfMoveOnlyType) {
    329  using Set = js::HashSet<MoveOnlyType, MoveOnlyType::HashPolicy,
    330                          js::SystemAllocPolicy>;
    331 
    332  Set set;
    333 
    334  MoveOnlyType a(1);
    335 
    336  CHECK(set.put(std::move(a)));  // This shouldn't generate a compiler error.
    337 
    338  return true;
    339 }
    340 END_TEST(testHashSetOfMoveOnlyType)
    341 
    342 #if defined(DEBUG)
    343 
    344 // Add entries to a HashMap until either we get an OOM, or the table has been
    345 // resized a few times.
    346 static bool GrowUntilResize() {
    347  IntMap m;
    348 
    349  // Add entries until we've resized the table four times.
    350  size_t lastCapacity = m.capacity();
    351  size_t resizes = 0;
    352  uint32_t key = 0;
    353  while (resizes < 4) {
    354    auto p = m.lookupForAdd(key);
    355    if (!p && !m.add(p, key, 0)) {
    356      return false;  // OOM'd in lookupForAdd() or add()
    357    }
    358 
    359    size_t capacity = m.capacity();
    360    if (capacity != lastCapacity) {
    361      resizes++;
    362      lastCapacity = capacity;
    363    }
    364    key++;
    365  }
    366 
    367  return true;
    368 }
    369 
    370 BEGIN_TEST(testHashMapGrowOOM) {
    371  uint32_t timeToFail;
    372  for (timeToFail = 1; timeToFail < 1000; timeToFail++) {
    373    js::oom::simulator.simulateFailureAfter(
    374        js::oom::FailureSimulator::Kind::OOM, timeToFail, js::THREAD_TYPE_MAIN,
    375        false);
    376    GrowUntilResize();
    377  }
    378 
    379  js::oom::simulator.reset();
    380  return true;
    381 }
    382 
    383 END_TEST(testHashMapGrowOOM)
    384 #endif  // defined(DEBUG)
    385 
    386 BEGIN_TEST(testHashTableMovableModIterator) {
    387  IntSet set;
    388 
    389  // Exercise returning a hash table ModIterator object from a function.
    390 
    391  CHECK(set.put(1));
    392  for (auto iter = setModIter(set); !iter.done(); iter.next()) {
    393    iter.remove();
    394  }
    395  CHECK(set.count() == 0);
    396 
    397  // Test moving an ModIterator object explicitly.
    398 
    399  CHECK(set.put(1));
    400  CHECK(set.put(2));
    401  CHECK(set.put(3));
    402  CHECK(set.count() == 3);
    403  {
    404    auto i1 = set.modIter();
    405    CHECK(!i1.done());
    406    i1.remove();
    407    i1.next();
    408 
    409    auto i2 = std::move(i1);
    410    CHECK(!i2.done());
    411    i2.remove();
    412    i2.next();
    413  }
    414 
    415  CHECK(set.count() == 1);
    416  return true;
    417 }
    418 
    419 IntSet::ModIterator setModIter(IntSet& set) { return set.modIter(); }
    420 
    421 END_TEST(testHashTableMovableModIterator)
    422 
    423 BEGIN_TEST(testHashLazyStorage) {
    424  // The following code depends on the current capacity computation, which
    425  // could change in the future.
    426  uint32_t defaultCap = 32;
    427  uint32_t minCap = 4;
    428 
    429  IntSet set;
    430  CHECK(set.capacity() == 0);
    431 
    432  CHECK(set.put(1));
    433  CHECK(set.capacity() == defaultCap);
    434 
    435  set.compact();  // effectively a no-op
    436  CHECK(set.capacity() == minCap);
    437 
    438  set.clear();
    439  CHECK(set.capacity() == minCap);
    440 
    441  set.compact();
    442  CHECK(set.capacity() == 0);
    443 
    444  CHECK(set.putNew(1));
    445  CHECK(set.capacity() == minCap);
    446 
    447  set.clear();
    448  set.compact();
    449  CHECK(set.capacity() == 0);
    450 
    451  auto p = set.lookupForAdd(1);
    452  CHECK(set.capacity() == 0);
    453  CHECK(set.add(p, 1));
    454  CHECK(set.capacity() == minCap);
    455  CHECK(set.has(1));
    456 
    457  set.clear();
    458  set.compact();
    459  CHECK(set.capacity() == 0);
    460 
    461  p = set.lookupForAdd(1);
    462  CHECK(set.putNew(2));
    463  CHECK(set.capacity() == minCap);
    464  CHECK(set.relookupOrAdd(p, 1, 1));
    465  CHECK(set.capacity() == minCap);
    466  CHECK(set.has(1));
    467 
    468  set.clear();
    469  set.compact();
    470  CHECK(set.capacity() == 0);
    471 
    472  CHECK(set.putNew(1));
    473  p = set.lookupForAdd(1);
    474  set.clear();
    475  set.compact();
    476  CHECK(set.count() == 0);
    477  CHECK(set.relookupOrAdd(p, 1, 1));
    478  CHECK(set.count() == 1);
    479  CHECK(set.capacity() == minCap);
    480 
    481  set.clear();
    482  set.compact();
    483  CHECK(set.capacity() == 0);
    484 
    485  CHECK(set.reserve(0));  // a no-op
    486  CHECK(set.capacity() == 0);
    487 
    488  CHECK(set.reserve(1));
    489  CHECK(set.capacity() == minCap);
    490 
    491  CHECK(set.reserve(0));  // a no-op
    492  CHECK(set.capacity() == minCap);
    493 
    494  CHECK(set.reserve(2));  // effectively a no-op
    495  CHECK(set.capacity() == minCap);
    496 
    497  // No need to clear here because we didn't add anything.
    498  set.compact();
    499  CHECK(set.capacity() == 0);
    500 
    501  CHECK(set.reserve(128));
    502  CHECK(set.capacity() == 256);
    503  CHECK(set.reserve(3));  // effectively a no-op
    504  CHECK(set.capacity() == 256);
    505  for (int i = 0; i < 8; i++) {
    506    CHECK(set.putNew(i));
    507  }
    508  CHECK(set.count() == 8);
    509  CHECK(set.capacity() == 256);
    510  set.compact();
    511  CHECK(set.capacity() == 16);
    512  set.compact();  // effectively a no-op
    513  CHECK(set.capacity() == 16);
    514  for (int i = 8; i < 16; i++) {
    515    CHECK(set.putNew(i));
    516  }
    517  CHECK(set.count() == 16);
    518  CHECK(set.capacity() == 32);
    519  set.clear();
    520  CHECK(set.capacity() == 32);
    521  set.compact();
    522  CHECK(set.capacity() == 0);
    523 
    524  // Lowest length for which reserve() will fail.
    525  static const uint32_t toobig = (1 << 29) + 1;
    526  CHECK(!set.reserve(toobig));
    527  CHECK(set.capacity() == 0);  // unchanged
    528  CHECK(set.reserve(16));
    529  CHECK(set.capacity() == 32);
    530 
    531  return true;
    532 }
    533 END_TEST(testHashLazyStorage)