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/. */
5 #include "mozilla/HashFunctions.h"
9 #include "ds/OrderedHashTable.h"
10 #include "js/HashTable.h"
11 #include "js/Utility.h"
12 #include "jsapi-tests/tests.h"
16 typedef js::HashMap
<uint32_t, uint32_t, js::DefaultHasher
<uint32_t>,
17 js::SystemAllocPolicy
>
19 typedef js::HashSet
<uint32_t, js::DefaultHasher
<uint32_t>,
20 js::SystemAllocPolicy
>
24 * The rekeying test as conducted by adding only keys masked with 0x0000FFFF
25 * that are unique. We rekey by shifting left 16 bits.
28 const size_t TestSize
= 0x0000FFFF / 2;
29 const size_t TestIterations
= SIZE_MAX
;
31 const size_t TestSize
= 10000;
32 const size_t TestIterations
= 10;
35 static_assert(TestSize
<= 0x0000FFFF / 2);
38 static uint32_t rekey(uint32_t initial
) {
39 if (initial
> uint32_t(0x0000FFFF)) {
45 static bool shouldBeRemoved(uint32_t initial
) { return false; }
48 struct LowToHighWithRemoval
{
49 static uint32_t rekey(uint32_t initial
) {
50 if (initial
> uint32_t(0x0000FFFF)) {
56 static bool shouldBeRemoved(uint32_t initial
) {
57 if (initial
>= 0x00010000) {
58 return (initial
>> 16) % 2 == 0;
60 return initial
% 2 == 0;
64 static bool MapsAreEqual(IntMap
& am
, IntMap
& bm
) {
66 if (am
.count() != bm
.count()) {
68 fprintf(stderr
, "A.count() == %u and B.count() == %u\n", am
.count(),
71 for (auto iter
= am
.iter(); !iter
.done(); iter
.next()) {
72 if (!bm
.has(iter
.get().key())) {
74 fprintf(stderr
, "B does not have %x which is in A\n", iter
.get().key());
77 for (auto iter
= bm
.iter(); !iter
.done(); iter
.next()) {
78 if (!am
.has(iter
.get().key())) {
80 fprintf(stderr
, "A does not have %x which is in B\n", iter
.get().key());
86 static bool SetsAreEqual(IntSet
& am
, IntSet
& bm
) {
88 if (am
.count() != bm
.count()) {
90 fprintf(stderr
, "A.count() == %u and B.count() == %u\n", am
.count(),
93 for (auto iter
= am
.iter(); !iter
.done(); iter
.next()) {
94 if (!bm
.has(iter
.get())) {
96 fprintf(stderr
, "B does not have %x which is in A\n", iter
.get());
99 for (auto iter
= bm
.iter(); !iter
.done(); iter
.next()) {
100 if (!am
.has(iter
.get())) {
102 fprintf(stderr
, "A does not have %x which is in B\n", iter
.get());
108 static bool AddLowKeys(IntMap
* am
, IntMap
* bm
, int seed
) {
111 while (i
< TestSize
) {
112 uint32_t n
= rand() & 0x0000FFFF;
118 if (!am
->putNew(n
, n
) || !bm
->putNew(n
, n
)) {
127 static bool AddLowKeys(IntSet
* as
, IntSet
* bs
, int seed
) {
130 while (i
< TestSize
) {
131 uint32_t n
= rand() & 0x0000FFFF;
136 if (!as
->putNew(n
) || !bs
->putNew(n
)) {
145 template <class NewKeyFunction
>
146 static bool SlowRekey(IntMap
* m
) {
149 for (auto iter
= m
->iter(); !iter
.done(); iter
.next()) {
150 if (NewKeyFunction::shouldBeRemoved(iter
.get().key())) {
153 uint32_t hi
= NewKeyFunction::rekey(iter
.get().key());
157 if (!tmp
.putNew(hi
, iter
.get().value())) {
163 for (auto iter
= tmp
.iter(); !iter
.done(); iter
.next()) {
164 if (!m
->putNew(iter
.get().key(), iter
.get().value())) {
172 template <class NewKeyFunction
>
173 static bool SlowRekey(IntSet
* s
) {
176 for (auto iter
= s
->iter(); !iter
.done(); iter
.next()) {
177 if (NewKeyFunction::shouldBeRemoved(iter
.get())) {
180 uint32_t hi
= NewKeyFunction::rekey(iter
.get());
184 if (!tmp
.putNew(hi
)) {
190 for (auto iter
= tmp
.iter(); !iter
.done(); iter
.next()) {
191 if (!s
->putNew(iter
.get())) {
199 BEGIN_TEST(testHashRekeyManual
) {
201 for (size_t i
= 0; i
< TestIterations
; ++i
) {
203 fprintf(stderr
, "map1: %lu\n", i
);
205 CHECK(AddLowKeys(&am
, &bm
, i
));
206 CHECK(MapsAreEqual(am
, bm
));
208 for (auto iter
= am
.modIter(); !iter
.done(); iter
.next()) {
209 uint32_t tmp
= LowToHigh::rekey(iter
.get().key());
210 if (tmp
!= iter
.get().key()) {
214 CHECK(SlowRekey
<LowToHigh
>(&bm
));
216 CHECK(MapsAreEqual(am
, bm
));
222 for (size_t i
= 0; i
< TestIterations
; ++i
) {
224 fprintf(stderr
, "set1: %lu\n", i
);
226 CHECK(AddLowKeys(&as
, &bs
, i
));
227 CHECK(SetsAreEqual(as
, bs
));
229 for (auto iter
= as
.modIter(); !iter
.done(); iter
.next()) {
230 uint32_t tmp
= LowToHigh::rekey(iter
.get());
231 if (tmp
!= iter
.get()) {
235 CHECK(SlowRekey
<LowToHigh
>(&bs
));
237 CHECK(SetsAreEqual(as
, bs
));
244 END_TEST(testHashRekeyManual
)
246 BEGIN_TEST(testHashRekeyManualRemoval
) {
248 for (size_t i
= 0; i
< TestIterations
; ++i
) {
250 fprintf(stderr
, "map2: %lu\n", i
);
252 CHECK(AddLowKeys(&am
, &bm
, i
));
253 CHECK(MapsAreEqual(am
, bm
));
255 for (auto iter
= am
.modIter(); !iter
.done(); iter
.next()) {
256 if (LowToHighWithRemoval::shouldBeRemoved(iter
.get().key())) {
259 uint32_t tmp
= LowToHighWithRemoval::rekey(iter
.get().key());
260 if (tmp
!= iter
.get().key()) {
265 CHECK(SlowRekey
<LowToHighWithRemoval
>(&bm
));
267 CHECK(MapsAreEqual(am
, bm
));
273 for (size_t i
= 0; i
< TestIterations
; ++i
) {
275 fprintf(stderr
, "set1: %lu\n", i
);
277 CHECK(AddLowKeys(&as
, &bs
, i
));
278 CHECK(SetsAreEqual(as
, bs
));
280 for (auto iter
= as
.modIter(); !iter
.done(); iter
.next()) {
281 if (LowToHighWithRemoval::shouldBeRemoved(iter
.get())) {
284 uint32_t tmp
= LowToHighWithRemoval::rekey(iter
.get());
285 if (tmp
!= iter
.get()) {
290 CHECK(SlowRekey
<LowToHighWithRemoval
>(&bs
));
292 CHECK(SetsAreEqual(as
, bs
));
299 END_TEST(testHashRekeyManualRemoval
)
301 // A type that is not copyable, only movable.
302 struct MoveOnlyType
{
305 explicit MoveOnlyType(uint32_t val
) : val(val
) {}
307 MoveOnlyType(MoveOnlyType
&& rhs
) { val
= rhs
.val
; }
309 MoveOnlyType
& operator=(MoveOnlyType
&& rhs
) {
310 MOZ_ASSERT(&rhs
!= this);
311 this->~MoveOnlyType();
312 new (this) MoveOnlyType(std::move(rhs
));
317 typedef MoveOnlyType Lookup
;
319 static js::HashNumber
hash(const Lookup
& lookup
) { return lookup
.val
; }
321 static bool match(const MoveOnlyType
& existing
, const Lookup
& lookup
) {
322 return existing
.val
== lookup
.val
;
327 MoveOnlyType(const MoveOnlyType
&) = delete;
328 MoveOnlyType
& operator=(const MoveOnlyType
&) = delete;
331 BEGIN_TEST(testHashSetOfMoveOnlyType
) {
332 typedef js::HashSet
<MoveOnlyType
, MoveOnlyType::HashPolicy
,
333 js::SystemAllocPolicy
>
340 CHECK(set
.put(std::move(a
))); // This shouldn't generate a compiler error.
344 END_TEST(testHashSetOfMoveOnlyType
)
348 // Add entries to a HashMap until either we get an OOM, or the table has been
349 // resized a few times.
350 static bool GrowUntilResize() {
353 // Add entries until we've resized the table four times.
354 size_t lastCapacity
= m
.capacity();
357 while (resizes
< 4) {
358 auto p
= m
.lookupForAdd(key
);
359 if (!p
&& !m
.add(p
, key
, 0)) {
360 return false; // OOM'd in lookupForAdd() or add()
363 size_t capacity
= m
.capacity();
364 if (capacity
!= lastCapacity
) {
366 lastCapacity
= capacity
;
374 BEGIN_TEST(testHashMapGrowOOM
) {
376 for (timeToFail
= 1; timeToFail
< 1000; timeToFail
++) {
377 js::oom::simulator
.simulateFailureAfter(
378 js::oom::FailureSimulator::Kind::OOM
, timeToFail
, js::THREAD_TYPE_MAIN
,
383 js::oom::simulator
.reset();
387 END_TEST(testHashMapGrowOOM
)
388 #endif // defined(DEBUG)
390 BEGIN_TEST(testHashTableMovableModIterator
) {
393 // Exercise returning a hash table ModIterator object from a function.
396 for (auto iter
= setModIter(set
); !iter
.done(); iter
.next()) {
399 CHECK(set
.count() == 0);
401 // Test moving an ModIterator object explicitly.
406 CHECK(set
.count() == 3);
408 auto i1
= set
.modIter();
413 auto i2
= std::move(i1
);
419 CHECK(set
.count() == 1);
423 IntSet::ModIterator
setModIter(IntSet
& set
) { return set
.modIter(); }
425 END_TEST(testHashTableMovableModIterator
)
427 BEGIN_TEST(testHashLazyStorage
) {
428 // The following code depends on the current capacity computation, which
429 // could change in the future.
430 uint32_t defaultCap
= 32;
434 CHECK(set
.capacity() == 0);
437 CHECK(set
.capacity() == defaultCap
);
439 set
.compact(); // effectively a no-op
440 CHECK(set
.capacity() == minCap
);
443 CHECK(set
.capacity() == minCap
);
446 CHECK(set
.capacity() == 0);
448 CHECK(set
.putNew(1));
449 CHECK(set
.capacity() == minCap
);
453 CHECK(set
.capacity() == 0);
455 auto p
= set
.lookupForAdd(1);
456 CHECK(set
.capacity() == 0);
457 CHECK(set
.add(p
, 1));
458 CHECK(set
.capacity() == minCap
);
463 CHECK(set
.capacity() == 0);
465 p
= set
.lookupForAdd(1);
466 CHECK(set
.putNew(2));
467 CHECK(set
.capacity() == minCap
);
468 CHECK(set
.relookupOrAdd(p
, 1, 1));
469 CHECK(set
.capacity() == minCap
);
474 CHECK(set
.capacity() == 0);
476 CHECK(set
.putNew(1));
477 p
= set
.lookupForAdd(1);
480 CHECK(set
.count() == 0);
481 CHECK(set
.relookupOrAdd(p
, 1, 1));
482 CHECK(set
.count() == 1);
483 CHECK(set
.capacity() == minCap
);
487 CHECK(set
.capacity() == 0);
489 CHECK(set
.reserve(0)); // a no-op
490 CHECK(set
.capacity() == 0);
492 CHECK(set
.reserve(1));
493 CHECK(set
.capacity() == minCap
);
495 CHECK(set
.reserve(0)); // a no-op
496 CHECK(set
.capacity() == minCap
);
498 CHECK(set
.reserve(2)); // effectively a no-op
499 CHECK(set
.capacity() == minCap
);
501 // No need to clear here because we didn't add anything.
503 CHECK(set
.capacity() == 0);
505 CHECK(set
.reserve(128));
506 CHECK(set
.capacity() == 256);
507 CHECK(set
.reserve(3)); // effectively a no-op
508 CHECK(set
.capacity() == 256);
509 for (int i
= 0; i
< 8; i
++) {
510 CHECK(set
.putNew(i
));
512 CHECK(set
.count() == 8);
513 CHECK(set
.capacity() == 256);
515 CHECK(set
.capacity() == 16);
516 set
.compact(); // effectively a no-op
517 CHECK(set
.capacity() == 16);
518 for (int i
= 8; i
< 16; i
++) {
519 CHECK(set
.putNew(i
));
521 CHECK(set
.count() == 16);
522 CHECK(set
.capacity() == 32);
524 CHECK(set
.capacity() == 32);
526 CHECK(set
.capacity() == 0);
528 // Lowest length for which reserve() will fail.
529 static const uint32_t toobig
= (1 << 29) + 1;
530 CHECK(!set
.reserve(toobig
));
531 CHECK(set
.capacity() == 0); // unchanged
532 CHECK(set
.reserve(16));
533 CHECK(set
.capacity() == 32);
537 END_TEST(testHashLazyStorage
)
539 BEGIN_TEST(testOrderedHashSetWithoutInit
) {
541 struct NonzeroUint32HashPolicy
{
542 using Lookup
= uint32_t;
543 static js::HashNumber
hash(const Lookup
& v
,
544 const mozilla::HashCodeScrambler
& hcs
) {
545 return mozilla::HashGeneric(v
);
547 static bool match(const uint32_t& k
, const Lookup
& l
) { return k
== l
; }
548 static bool isEmpty(const uint32_t& v
) { return v
== 0; }
549 static void makeEmpty(uint32_t* v
) { *v
= 0; }
552 using OHS
= js::OrderedHashSet
<uint32_t, NonzeroUint32HashPolicy
,
553 js::SystemAllocPolicy
>;
555 OHS
set(js::SystemAllocPolicy(), mozilla::HashCodeScrambler(17, 42));
556 CHECK(set
.count() == 0);
558 // This test passes if the set is safely destructible even when |init()| is
564 END_TEST(testOrderedHashSetWithoutInit
)