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)