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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 // This program is used by the DMD xpcshell test. It is run under DMD and
8 // produces some output. The xpcshell test then post-processes and checks this
9 // output.
10 //
11 // Note that this file does not have "Test" or "test" in its name, because that
12 // will cause the build system to not record breakpad symbols for it, which
13 // will stop the post-processing (which includes stack fixing) from working
14 // correctly.
15
16 #include <errno.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19
20 #include "mozilla/Assertions.h"
21 #include "mozilla/JSONWriter.h"
22 #include "mozilla/UniquePtr.h"
23 #include "DMD.h"
24
25 using mozilla::JSONWriter;
26 using mozilla::MakeUnique;
27 using namespace mozilla::dmd;
28
29 DMDFuncs::Singleton DMDFuncs::sSingleton;
30
31 class FpWriteFunc : public mozilla::JSONWriteFunc {
32 public:
FpWriteFunc(const char * aFilename)33 explicit FpWriteFunc(const char* aFilename) {
34 mFp = fopen(aFilename, "w");
35 if (!mFp) {
36 fprintf(stderr, "SmokeDMD: can't create %s file: %s\n", aFilename,
37 strerror(errno));
38 exit(1);
39 }
40 }
41
~FpWriteFunc()42 ~FpWriteFunc() { fclose(mFp); }
43
Write(const char * aStr)44 void Write(const char* aStr) override { fputs(aStr, mFp); }
45
46 private:
47 FILE* mFp;
48 };
49
50 // This stops otherwise-unused variables from being optimized away.
UseItOrLoseIt(void * aPtr,int aSeven)51 static void UseItOrLoseIt(void* aPtr, int aSeven) {
52 char buf[64];
53 int n = sprintf(buf, "%p\n", aPtr);
54 if (n == 20 + aSeven) {
55 fprintf(stderr, "well, that is surprising");
56 }
57 }
58
59 // This function checks that heap blocks that have the same stack trace but
60 // different (or no) reporters get aggregated separately.
Foo(int aSeven)61 void Foo(int aSeven) {
62 char* a[6];
63 for (int i = 0; i < aSeven - 1; i++) {
64 a[i] = (char*)malloc(128 - 16 * i);
65 }
66
67 // Oddly, some versions of clang will cause identical stack traces to be
68 // generated for adjacent calls to Report(), which breaks the test. Inserting
69 // the UseItOrLoseIt() calls in between is enough to prevent this.
70
71 Report(a[2]); // reported
72
73 UseItOrLoseIt(a[2], aSeven);
74
75 for (int i = 0; i < aSeven - 5; i++) {
76 Report(a[i]); // reported
77 }
78
79 UseItOrLoseIt(a[2], aSeven);
80
81 Report(a[3]); // reported
82
83 // a[4], a[5] unreported
84 }
85
TestEmpty(const char * aTestName,const char * aMode)86 void TestEmpty(const char* aTestName, const char* aMode) {
87 char filename[128];
88 sprintf(filename, "complete-%s-%s.json", aTestName, aMode);
89 auto f = MakeUnique<FpWriteFunc>(filename);
90
91 char options[128];
92 sprintf(options, "--mode=%s --stacks=full", aMode);
93 ResetEverything(options);
94
95 // Zero for everything.
96 Analyze(Move(f));
97 }
98
TestFull(const char * aTestName,int aNum,const char * aMode,int aSeven)99 void TestFull(const char* aTestName, int aNum, const char* aMode, int aSeven) {
100 char filename[128];
101 sprintf(filename, "complete-%s%d-%s.json", aTestName, aNum, aMode);
102 auto f = MakeUnique<FpWriteFunc>(filename);
103
104 // The --show-dump-stats=yes is there just to give that option some basic
105 // testing, e.g. ensure it doesn't crash. It's hard to test much beyond that.
106 char options[128];
107 sprintf(options, "--mode=%s --stacks=full --show-dump-stats=yes", aMode);
108 ResetEverything(options);
109
110 // Analyze 1: 1 freed, 9 out of 10 unreported.
111 // Analyze 2: still present and unreported.
112 int i;
113 char* a = nullptr;
114 for (i = 0; i < aSeven + 3; i++) {
115 a = (char*)malloc(100);
116 UseItOrLoseIt(a, aSeven);
117 }
118 free(a);
119
120 // A no-op.
121 free(nullptr);
122
123 // Note: 16 bytes is the smallest requested size that gives consistent
124 // behaviour across all platforms with jemalloc.
125 // Analyze 1: reported.
126 // Analyze 2: thrice-reported.
127 char* a2 = (char*)malloc(16);
128 Report(a2);
129
130 // Analyze 1: reported.
131 // Analyze 2: reportedness carries over, due to ReportOnAlloc.
132 char* b = (char*)malloc(10);
133 ReportOnAlloc(b);
134
135 // ReportOnAlloc, then freed.
136 // Analyze 1: freed, irrelevant.
137 // Analyze 2: freed, irrelevant.
138 char* b2 = (char*)malloc(16);
139 ReportOnAlloc(b2);
140 free(b2);
141
142 // Analyze 1: reported 4 times.
143 // Analyze 2: freed, irrelevant.
144 char* c = (char*)calloc(10, 3);
145 Report(c);
146 for (int i = 0; i < aSeven - 4; i++) {
147 Report(c);
148 }
149
150 // Analyze 1: ignored.
151 // Analyze 2: irrelevant.
152 Report((void*)(intptr_t)i);
153
154 // jemalloc rounds this up to 8192.
155 // Analyze 1: reported.
156 // Analyze 2: freed.
157 char* e = (char*)malloc(4096);
158 e = (char*)realloc(e, 7169);
159 Report(e);
160
161 // First realloc is like malloc; second realloc is shrinking.
162 // Analyze 1: reported.
163 // Analyze 2: re-reported.
164 char* e2 = (char*)realloc(nullptr, 1024);
165 e2 = (char*)realloc(e2, 512);
166 Report(e2);
167
168 // First realloc is like malloc; second realloc creates a min-sized block.
169 // XXX: on Windows, second realloc frees the block.
170 // Analyze 1: reported.
171 // Analyze 2: freed, irrelevant.
172 char* e3 = (char*)realloc(nullptr, 1023);
173 // e3 = (char*) realloc(e3, 0);
174 MOZ_ASSERT(e3);
175 Report(e3);
176
177 // Analyze 1: freed, irrelevant.
178 // Analyze 2: freed, irrelevant.
179 char* f1 = (char*)malloc(64);
180 free(f1);
181
182 // Analyze 1: ignored.
183 // Analyze 2: irrelevant.
184 Report((void*)(intptr_t)0x0);
185
186 // Analyze 1: mixture of reported and unreported.
187 // Analyze 2: all unreported.
188 Foo(aSeven);
189
190 // Analyze 1: twice-reported.
191 // Analyze 2: twice-reported.
192 char* g1 = (char*)malloc(77);
193 ReportOnAlloc(g1);
194 ReportOnAlloc(g1);
195
196 // Analyze 1: mixture of reported and unreported.
197 // Analyze 2: all unreported.
198 // Nb: this Foo() call is deliberately not adjacent to the previous one. See
199 // the comment about adjacent calls in Foo() for more details.
200 Foo(aSeven);
201
202 // Analyze 1: twice-reported.
203 // Analyze 2: once-reported.
204 char* g2 = (char*)malloc(78);
205 Report(g2);
206 ReportOnAlloc(g2);
207
208 // Analyze 1: twice-reported.
209 // Analyze 2: once-reported.
210 char* g3 = (char*)malloc(79);
211 ReportOnAlloc(g3);
212 Report(g3);
213
214 // All the odd-ball ones.
215 // Analyze 1: all unreported.
216 // Analyze 2: all freed, irrelevant.
217 // XXX: no memalign on Mac
218 // void* w = memalign(64, 65); // rounds up to 128
219 // UseItOrLoseIt(w, aSeven);
220
221 // XXX: posix_memalign doesn't work on B2G
222 // void* x;
223 // posix_memalign(&y, 128, 129); // rounds up to 256
224 // UseItOrLoseIt(x, aSeven);
225
226 // XXX: valloc doesn't work on Windows.
227 // void* y = valloc(1); // rounds up to 4096
228 // UseItOrLoseIt(y, aSeven);
229
230 // XXX: C11 only
231 // void* z = aligned_alloc(64, 256);
232 // UseItOrLoseIt(z, aSeven);
233
234 if (aNum == 1) {
235 // Analyze 1.
236 Analyze(Move(f));
237 }
238
239 ClearReports();
240
241 //---------
242
243 Report(a2);
244 Report(a2);
245 free(c);
246 free(e);
247 Report(e2);
248 free(e3);
249 // free(w);
250 // free(x);
251 // free(y);
252 // free(z);
253
254 // Do some allocations that will only show up in cumulative mode.
255 for (int i = 0; i < 100; i++) {
256 free(malloc(128));
257 }
258
259 if (aNum == 2) {
260 // Analyze 2.
261 Analyze(Move(f));
262 }
263 }
264
TestPartial(const char * aTestName,const char * aMode,int aSeven)265 void TestPartial(const char* aTestName, const char* aMode, int aSeven) {
266 char filename[128];
267 sprintf(filename, "complete-%s-%s.json", aTestName, aMode);
268 auto f = MakeUnique<FpWriteFunc>(filename);
269
270 char options[128];
271 sprintf(options, "--mode=%s", aMode);
272 ResetEverything(options);
273
274 int kTenThousand = aSeven + 9993;
275 char* s;
276
277 // The output of this function is deterministic but it relies on the
278 // probability and seeds given to the FastBernoulliTrial instance in
279 // ResetBernoulli(). If they change, the output will change too.
280
281 // Expected fraction with stacks: (1 - (1 - 0.003) ** 16) = 0.0469.
282 // So we expect about 0.0469 * 10000 == 469.
283 // We actually get 511.
284 for (int i = 0; i < kTenThousand; i++) {
285 s = (char*)malloc(16);
286 UseItOrLoseIt(s, aSeven);
287 }
288
289 // Expected fraction with stacks: (1 - (1 - 0.003) ** 128) = 0.3193.
290 // So we expect about 0.3193 * 10000 == 3193.
291 // We actually get 3136.
292 for (int i = 0; i < kTenThousand; i++) {
293 s = (char*)malloc(128);
294 UseItOrLoseIt(s, aSeven);
295 }
296
297 // Expected fraction with stacks: (1 - (1 - 0.003) ** 1024) = 0.9539.
298 // So we expect about 0.9539 * 10000 == 9539.
299 // We actually get 9531.
300 for (int i = 0; i < kTenThousand; i++) {
301 s = (char*)malloc(1024);
302 UseItOrLoseIt(s, aSeven);
303 }
304
305 Analyze(Move(f));
306 }
307
TestScan(int aSeven)308 void TestScan(int aSeven) {
309 auto f = MakeUnique<FpWriteFunc>("basic-scan.json");
310
311 ResetEverything("--mode=scan");
312
313 uintptr_t* p = (uintptr_t*)malloc(6 * sizeof(uintptr_t*));
314 UseItOrLoseIt(p, aSeven);
315
316 // Hard-coded values checked by scan-test.py
317 p[0] = 0x123; // outside a block, small value
318 p[1] = 0x0; // null
319 p[2] = (uintptr_t)((uint8_t*)p - 1); // pointer outside a block, but nearby
320 p[3] = (uintptr_t)p; // pointer to start of a block
321 p[4] = (uintptr_t)((uint8_t*)p + 1); // pointer into a block
322 p[5] = 0x0; // trailing null
323
324 Analyze(Move(f));
325 }
326
RunTests()327 void RunTests() {
328 // This test relies on the compiler not doing various optimizations, such as
329 // eliding unused malloc() calls or unrolling loops with fixed iteration
330 // counts. So we compile it with -O0 (or equivalent), which probably prevents
331 // that. We also use the following variable for various loop iteration
332 // counts, just in case compilers might unroll very small loops even with
333 // -O0.
334 int seven = 7;
335
336 // Make sure that DMD is actually running; it is initialized on the first
337 // allocation.
338 int* x = (int*)malloc(100);
339 UseItOrLoseIt(x, seven);
340 MOZ_RELEASE_ASSERT(IsRunning());
341
342 // Please keep this in sync with run_test in test_dmd.js.
343
344 TestEmpty("empty", "live");
345 TestEmpty("empty", "dark-matter");
346 TestEmpty("empty", "cumulative");
347
348 TestFull("full", 1, "live", seven);
349 TestFull("full", 1, "dark-matter", seven);
350
351 TestFull("full", 2, "dark-matter", seven);
352 TestFull("full", 2, "cumulative", seven);
353
354 TestPartial("partial", "live", seven);
355
356 TestScan(seven);
357 }
358
main()359 int main() {
360 RunTests();
361
362 return 0;
363 }
364