/src/mozilla-central/xpcom/tests/gtest/TestPLDHash.cpp
Line | Count | Source (jump to first uncovered line) |
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 "PLDHashTable.h" |
8 | | #include "nsCOMPtr.h" |
9 | | #include "nsICrashReporter.h" |
10 | | #include "nsServiceManagerUtils.h" |
11 | | #include "gtest/gtest.h" |
12 | | |
13 | | // This test mostly focuses on edge cases. But more coverage of normal |
14 | | // operations wouldn't be a bad thing. |
15 | | |
16 | | #ifdef XP_UNIX |
17 | | #include <unistd.h> |
18 | | #include <sys/types.h> |
19 | | #include <sys/wait.h> |
20 | | |
21 | | // This global variable is defined in toolkit/xre/nsSigHandlers.cpp. |
22 | | extern unsigned int _gdb_sleep_duration; |
23 | | #endif |
24 | | |
25 | | // We can test that certain operations cause expected aborts by forking |
26 | | // and then checking that the child aborted in the expected way (i.e. via |
27 | | // MOZ_CRASH). We skip this for the following configurations. |
28 | | // - On Windows, because it doesn't have fork(). |
29 | | // - On non-DEBUG builds, because the crashes cause the crash reporter to pop |
30 | | // up when running this test locally, which is surprising and annoying. |
31 | | // - On ASAN builds, because ASAN alters the way a MOZ_CRASHing process |
32 | | // terminates, which makes it harder to test if the right thing has occurred. |
33 | | void |
34 | | TestCrashyOperation(void (*aCrashyOperation)()) |
35 | 0 | { |
36 | | #if defined(XP_UNIX) && defined(DEBUG) && !defined(MOZ_ASAN) |
37 | | // We're about to trigger a crash. When it happens don't pause to allow GDB |
38 | | // to be attached. |
39 | | unsigned int old_gdb_sleep_duration = _gdb_sleep_duration; |
40 | | _gdb_sleep_duration = 0; |
41 | | |
42 | | int pid = fork(); |
43 | | ASSERT_NE(pid, -1); |
44 | | |
45 | | if (pid == 0) { |
46 | | // Disable the crashreporter -- writing a crash dump in the child will |
47 | | // prevent the parent from writing a subsequent dump. Crashes here are |
48 | | // expected, so we don't want their stacks to show up in the log anyway. |
49 | | nsCOMPtr<nsICrashReporter> crashreporter = |
50 | | do_GetService("@mozilla.org/toolkit/crash-reporter;1"); |
51 | | if (crashreporter) { |
52 | | crashreporter->SetEnabled(false); |
53 | | } |
54 | | |
55 | | // Child: perform the crashy operation. |
56 | | fprintf(stderr, "TestCrashyOperation: The following crash is expected. Do not panic.\n"); |
57 | | aCrashyOperation(); |
58 | | fprintf(stderr, "TestCrashyOperation: didn't crash?!\n"); |
59 | | ASSERT_TRUE(false); // shouldn't reach here |
60 | | } |
61 | | |
62 | | // Parent: check that child crashed as expected. |
63 | | int status; |
64 | | ASSERT_NE(waitpid(pid, &status, 0), -1); |
65 | | |
66 | | // The path taken here depends on the platform and configuration. |
67 | | ASSERT_TRUE(WIFEXITED(status) || WTERMSIG(status)); |
68 | | if (WIFEXITED(status)) { |
69 | | // This occurs if the ah_crap_handler() is run, i.e. we caught the crash. |
70 | | // It returns the number of the caught signal. |
71 | | int signum = WEXITSTATUS(status); |
72 | | if (signum != SIGSEGV && signum != SIGBUS) { |
73 | | fprintf(stderr, "TestCrashyOperation 'exited' failure: %d\n", signum); |
74 | | ASSERT_TRUE(false); |
75 | | } |
76 | | } else if (WIFSIGNALED(status)) { |
77 | | // This one occurs if we didn't catch the crash. The exit code is the |
78 | | // number of the terminating signal. |
79 | | int signum = WTERMSIG(status); |
80 | | if (signum != SIGSEGV && signum != SIGBUS) { |
81 | | fprintf(stderr, "TestCrashyOperation 'signaled' failure: %d\n", signum); |
82 | | ASSERT_TRUE(false); |
83 | | } |
84 | | } |
85 | | |
86 | | _gdb_sleep_duration = old_gdb_sleep_duration; |
87 | | #endif |
88 | | } |
89 | | |
90 | | void |
91 | | InitCapacityOk_InitialLengthTooBig() |
92 | 0 | { |
93 | 0 | PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub), |
94 | 0 | PLDHashTable::kMaxInitialLength + 1); |
95 | 0 | } |
96 | | |
97 | | void |
98 | | InitCapacityOk_InitialEntryStoreTooBig() |
99 | 0 | { |
100 | 0 | // Try the smallest disallowed power-of-two entry store size, which is 2^32 |
101 | 0 | // bytes (which overflows to 0). (Note that the 2^23 *length* gets converted |
102 | 0 | // to a 2^24 *capacity*.) |
103 | 0 | PLDHashTable t(PLDHashTable::StubOps(), (uint32_t)1 << 8, (uint32_t)1 << 23); |
104 | 0 | } |
105 | | |
106 | | void |
107 | | InitCapacityOk_EntrySizeTooBig() |
108 | 0 | { |
109 | 0 | // Try the smallest disallowed entry size, which is 256 bytes. |
110 | 0 | PLDHashTable t(PLDHashTable::StubOps(), 256); |
111 | 0 | } |
112 | | |
113 | | TEST(PLDHashTableTest, InitCapacityOk) |
114 | 0 | { |
115 | 0 | // Try the largest allowed capacity. With kMaxCapacity==1<<26, this |
116 | 0 | // would allocate (if we added an element) 0.5GB of entry store on 32-bit |
117 | 0 | // platforms and 1GB on 64-bit platforms. |
118 | 0 | PLDHashTable t1(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub), |
119 | 0 | PLDHashTable::kMaxInitialLength); |
120 | 0 |
|
121 | 0 | // Try the largest allowed power-of-two entry store size, which is 2^31 bytes |
122 | 0 | // (Note that the 2^23 *length* gets converted to a 2^24 *capacity*.) |
123 | 0 | PLDHashTable t2(PLDHashTable::StubOps(), (uint32_t)1 << 7, (uint32_t)1 << 23); |
124 | 0 |
|
125 | 0 | // Try a too-large capacity (which aborts). |
126 | 0 | TestCrashyOperation(InitCapacityOk_InitialLengthTooBig); |
127 | 0 |
|
128 | 0 | // Try a large capacity combined with a large entry size that when multiplied |
129 | 0 | // overflow (causing abort). |
130 | 0 | TestCrashyOperation(InitCapacityOk_InitialEntryStoreTooBig); |
131 | 0 |
|
132 | 0 | // Try the largest allowed entry size. |
133 | 0 | PLDHashTable t3(PLDHashTable::StubOps(), 255); |
134 | 0 |
|
135 | 0 | // Try an overly large entry size. |
136 | 0 | TestCrashyOperation(InitCapacityOk_EntrySizeTooBig); |
137 | 0 |
|
138 | 0 | // Ideally we'd also try a large-but-ok capacity that almost but doesn't |
139 | 0 | // quite overflow, but that would result in allocating slightly less than 4 |
140 | 0 | // GiB of entry storage. That would be very likely to fail on 32-bit |
141 | 0 | // platforms, so such a test wouldn't be reliable. |
142 | 0 | } |
143 | | |
144 | | TEST(PLDHashTableTest, LazyStorage) |
145 | 0 | { |
146 | 0 | PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub)); |
147 | 0 |
|
148 | 0 | // PLDHashTable allocates entry storage lazily. Check that all the non-add |
149 | 0 | // operations work appropriately when the table is empty and the storage |
150 | 0 | // hasn't yet been allocated. |
151 | 0 |
|
152 | 0 | ASSERT_EQ(t.Capacity(), 0u); |
153 | 0 | ASSERT_EQ(t.EntrySize(), sizeof(PLDHashEntryStub)); |
154 | 0 | ASSERT_EQ(t.EntryCount(), 0u); |
155 | 0 | ASSERT_EQ(t.Generation(), 0u); |
156 | 0 |
|
157 | 0 | ASSERT_TRUE(!t.Search((const void*)1)); |
158 | 0 |
|
159 | 0 | // No result to check here, but call it to make sure it doesn't crash. |
160 | 0 | t.Remove((const void*)2); |
161 | 0 |
|
162 | 0 | for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { |
163 | 0 | ASSERT_TRUE(false); // shouldn't hit this on an empty table |
164 | 0 | } |
165 | 0 |
|
166 | 0 | ASSERT_EQ(t.ShallowSizeOfExcludingThis(moz_malloc_size_of), 0u); |
167 | 0 | } |
168 | | |
169 | | // A trivial hash function is good enough here. It's also super-fast for the |
170 | | // GrowToMaxCapacity test because we insert the integers 0.., which means it's |
171 | | // collision-free. |
172 | | static PLDHashNumber |
173 | | TrivialHash(const void *key) |
174 | 0 | { |
175 | 0 | return (PLDHashNumber)(size_t)key; |
176 | 0 | } |
177 | | |
178 | | static void |
179 | | TrivialInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) |
180 | 0 | { |
181 | 0 | auto entry = static_cast<PLDHashEntryStub*>(aEntry); |
182 | 0 | entry->key = aKey; |
183 | 0 | } |
184 | | |
185 | | static const PLDHashTableOps trivialOps = { |
186 | | TrivialHash, |
187 | | PLDHashTable::MatchEntryStub, |
188 | | PLDHashTable::MoveEntryStub, |
189 | | PLDHashTable::ClearEntryStub, |
190 | | TrivialInitEntry |
191 | | }; |
192 | | |
193 | | TEST(PLDHashTableTest, MoveSemantics) |
194 | 0 | { |
195 | 0 | PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub)); |
196 | 0 | t1.Add((const void*)88); |
197 | 0 | PLDHashTable t2(&trivialOps, sizeof(PLDHashEntryStub)); |
198 | 0 | t2.Add((const void*)99); |
199 | 0 |
|
200 | 0 | #if defined(__clang__) |
201 | 0 | #pragma clang diagnostic push |
202 | 0 | #pragma clang diagnostic ignored "-Wself-move" |
203 | 0 | #endif |
204 | 0 | t1 = std::move(t1); // self-move |
205 | 0 | #if defined(__clang__) |
206 | 0 | #pragma clang diagnostic pop |
207 | 0 | #endif |
208 | 0 |
|
209 | 0 | t1 = std::move(t2); // empty overwritten with empty |
210 | 0 |
|
211 | 0 | PLDHashTable t3(&trivialOps, sizeof(PLDHashEntryStub)); |
212 | 0 | PLDHashTable t4(&trivialOps, sizeof(PLDHashEntryStub)); |
213 | 0 | t3.Add((const void*)88); |
214 | 0 |
|
215 | 0 | t3 = std::move(t4); // non-empty overwritten with empty |
216 | 0 |
|
217 | 0 | PLDHashTable t5(&trivialOps, sizeof(PLDHashEntryStub)); |
218 | 0 | PLDHashTable t6(&trivialOps, sizeof(PLDHashEntryStub)); |
219 | 0 | t6.Add((const void*)88); |
220 | 0 |
|
221 | 0 | t5 = std::move(t6); // empty overwritten with non-empty |
222 | 0 |
|
223 | 0 | PLDHashTable t7(&trivialOps, sizeof(PLDHashEntryStub)); |
224 | 0 | PLDHashTable t8(std::move(t7)); // new table constructed with uninited |
225 | 0 |
|
226 | 0 | PLDHashTable t9(&trivialOps, sizeof(PLDHashEntryStub)); |
227 | 0 | t9.Add((const void*)88); |
228 | 0 | PLDHashTable t10(std::move(t9)); // new table constructed with inited |
229 | 0 | } |
230 | | |
231 | | TEST(PLDHashTableTest, Clear) |
232 | 0 | { |
233 | 0 | PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub)); |
234 | 0 |
|
235 | 0 | t1.Clear(); |
236 | 0 | ASSERT_EQ(t1.EntryCount(), 0u); |
237 | 0 |
|
238 | 0 | t1.ClearAndPrepareForLength(100); |
239 | 0 | ASSERT_EQ(t1.EntryCount(), 0u); |
240 | 0 |
|
241 | 0 | t1.Add((const void*)77); |
242 | 0 | t1.Add((const void*)88); |
243 | 0 | t1.Add((const void*)99); |
244 | 0 | ASSERT_EQ(t1.EntryCount(), 3u); |
245 | 0 |
|
246 | 0 | t1.Clear(); |
247 | 0 | ASSERT_EQ(t1.EntryCount(), 0u); |
248 | 0 |
|
249 | 0 | t1.Add((const void*)55); |
250 | 0 | t1.Add((const void*)66); |
251 | 0 | t1.Add((const void*)77); |
252 | 0 | t1.Add((const void*)88); |
253 | 0 | t1.Add((const void*)99); |
254 | 0 | ASSERT_EQ(t1.EntryCount(), 5u); |
255 | 0 |
|
256 | 0 | t1.ClearAndPrepareForLength(8192); |
257 | 0 | ASSERT_EQ(t1.EntryCount(), 0u); |
258 | 0 | } |
259 | | |
260 | | TEST(PLDHashTableTest, Iterator) |
261 | 0 | { |
262 | 0 | PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub)); |
263 | 0 |
|
264 | 0 | // Explicitly test the move constructor. We do this because, due to copy |
265 | 0 | // elision, compilers might optimize away move constructor calls for normal |
266 | 0 | // iterator use. |
267 | 0 | { |
268 | 0 | PLDHashTable::Iterator iter1(&t); |
269 | 0 | PLDHashTable::Iterator iter2(std::move(iter1)); |
270 | 0 | } |
271 | 0 |
|
272 | 0 | // Iterate through the empty table. |
273 | 0 | for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) { |
274 | 0 | (void) iter.Get(); |
275 | 0 | ASSERT_TRUE(false); // shouldn't hit this |
276 | 0 | } |
277 | 0 |
|
278 | 0 | // Add three entries. |
279 | 0 | t.Add((const void*)77); |
280 | 0 | t.Add((const void*)88); |
281 | 0 | t.Add((const void*)99); |
282 | 0 |
|
283 | 0 | // Check the iterator goes through each entry once. |
284 | 0 | bool saw77 = false, saw88 = false, saw99 = false; |
285 | 0 | int n = 0; |
286 | 0 | for (auto iter(t.Iter()); !iter.Done(); iter.Next()) { |
287 | 0 | auto entry = static_cast<PLDHashEntryStub*>(iter.Get()); |
288 | 0 | if (entry->key == (const void*)77) { |
289 | 0 | saw77 = true; |
290 | 0 | } |
291 | 0 | if (entry->key == (const void*)88) { |
292 | 0 | saw88 = true; |
293 | 0 | } |
294 | 0 | if (entry->key == (const void*)99) { |
295 | 0 | saw99 = true; |
296 | 0 | } |
297 | 0 | n++; |
298 | 0 | } |
299 | 0 | ASSERT_TRUE(saw77 && saw88 && saw99 && n == 3); |
300 | 0 |
|
301 | 0 | t.Clear(); |
302 | 0 |
|
303 | 0 | // First, we insert 64 items, which results in a capacity of 128, and a load |
304 | 0 | // factor of 50%. |
305 | 0 | for (intptr_t i = 0; i < 64; i++) { |
306 | 0 | t.Add((const void*)i); |
307 | 0 | } |
308 | 0 | ASSERT_EQ(t.EntryCount(), 64u); |
309 | 0 | ASSERT_EQ(t.Capacity(), 128u); |
310 | 0 |
|
311 | 0 | // The first removing iterator does no removing; capacity and entry count are |
312 | 0 | // unchanged. |
313 | 0 | for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) { |
314 | 0 | (void) iter.Get(); |
315 | 0 | } |
316 | 0 | ASSERT_EQ(t.EntryCount(), 64u); |
317 | 0 | ASSERT_EQ(t.Capacity(), 128u); |
318 | 0 |
|
319 | 0 | // The second removing iterator removes 16 items. This reduces the load |
320 | 0 | // factor to 37.5% (48 / 128), which isn't low enough to shrink the table. |
321 | 0 | for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { |
322 | 0 | auto entry = static_cast<PLDHashEntryStub*>(iter.Get()); |
323 | 0 | if ((intptr_t)(entry->key) % 4 == 0) { |
324 | 0 | iter.Remove(); |
325 | 0 | } |
326 | 0 | } |
327 | 0 | ASSERT_EQ(t.EntryCount(), 48u); |
328 | 0 | ASSERT_EQ(t.Capacity(), 128u); |
329 | 0 |
|
330 | 0 | // The third removing iterator removes another 16 items. This reduces |
331 | 0 | // the load factor to 25% (32 / 128), so the table is shrunk. |
332 | 0 | for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { |
333 | 0 | auto entry = static_cast<PLDHashEntryStub*>(iter.Get()); |
334 | 0 | if ((intptr_t)(entry->key) % 2 == 0) { |
335 | 0 | iter.Remove(); |
336 | 0 | } |
337 | 0 | } |
338 | 0 | ASSERT_EQ(t.EntryCount(), 32u); |
339 | 0 | ASSERT_EQ(t.Capacity(), 64u); |
340 | 0 |
|
341 | 0 | // The fourth removing iterator removes all remaining items. This reduces |
342 | 0 | // the capacity to the minimum. |
343 | 0 | for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { |
344 | 0 | iter.Remove(); |
345 | 0 | } |
346 | 0 | ASSERT_EQ(t.EntryCount(), 0u); |
347 | 0 | ASSERT_EQ(t.Capacity(), unsigned(PLDHashTable::kMinCapacity)); |
348 | 0 | } |
349 | | |
350 | | // This test involves resizing a table repeatedly up to 512 MiB in size. On |
351 | | // 32-bit platforms (Win32, Android) it sometimes OOMs, causing the test to |
352 | | // fail. (See bug 931062 and bug 1267227.) Therefore, we only run it on 64-bit |
353 | | // platforms where OOM is much less likely. |
354 | | // |
355 | | // Also, it's slow, and so should always be last. |
356 | | #ifdef HAVE_64BIT_BUILD |
357 | | TEST(PLDHashTableTest, GrowToMaxCapacity) |
358 | 0 | { |
359 | 0 | // This is infallible. |
360 | 0 | PLDHashTable* t = |
361 | 0 | new PLDHashTable(&trivialOps, sizeof(PLDHashEntryStub), 128); |
362 | 0 |
|
363 | 0 | // Keep inserting elements until failure occurs because the table is full. |
364 | 0 | size_t numInserted = 0; |
365 | 0 | while (true) { |
366 | 0 | if (!t->Add((const void*)numInserted, mozilla::fallible)) { |
367 | 0 | break; |
368 | 0 | } |
369 | 0 | numInserted++; |
370 | 0 | } |
371 | 0 |
|
372 | 0 | // We stop when the element count is 96.875% of PLDHashTable::kMaxCapacity |
373 | 0 | // (see MaxLoadOnGrowthFailure()). |
374 | 0 | if (numInserted != |
375 | 0 | PLDHashTable::kMaxCapacity - (PLDHashTable::kMaxCapacity >> 5)) { |
376 | 0 | delete t; |
377 | 0 | ASSERT_TRUE(false); |
378 | 0 | } |
379 | 0 |
|
380 | 0 | delete t; |
381 | 0 | } |
382 | | #endif |
383 | | |