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