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