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