Coverage Report

Created: 2018-09-25 14:53

/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