diff options
Diffstat (limited to 'memory/replace/dmd/test/SmokeDMD.cpp')
-rw-r--r-- | memory/replace/dmd/test/SmokeDMD.cpp | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/memory/replace/dmd/test/SmokeDMD.cpp b/memory/replace/dmd/test/SmokeDMD.cpp new file mode 100644 index 0000000000..c72e92a543 --- /dev/null +++ b/memory/replace/dmd/test/SmokeDMD.cpp @@ -0,0 +1,378 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This program is used by the DMD xpcshell test. It is run under DMD and +// produces some output. The xpcshell test then post-processes and checks this +// output. +// +// Note that this file does not have "Test" or "test" in its name, because that +// will cause the build system to not record breakpad symbols for it, which +// will stop the post-processing (which includes stack fixing) from working +// correctly. + +// This is required on some systems such as Fedora to allow +// building with -O0 together with --warnings-as-errors due to +// a check in /usr/include/features.h +#undef _FORTIFY_SOURCE + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "mozilla/Assertions.h" +#include "mozilla/JSONWriter.h" +#include "mozilla/Sprintf.h" +#include "mozilla/UniquePtr.h" +#include "DMD.h" + +using mozilla::MakeUnique; +using namespace mozilla::dmd; + +DMDFuncs::Singleton DMDFuncs::sSingleton; + +class FpWriteFunc final : public mozilla::JSONWriteFunc { + public: + explicit FpWriteFunc(const char* aFilename) { + mFp = fopen(aFilename, "w"); + if (!mFp) { + fprintf(stderr, "SmokeDMD: can't create %s file: %s\n", aFilename, + strerror(errno)); + exit(1); + } + } + + ~FpWriteFunc() { fclose(mFp); } + + void Write(const mozilla::Span<const char>& aStr) final { + for (const char c : aStr) { + fputc(c, mFp); + } + } + + private: + FILE* mFp; +}; + +// This stops otherwise-unused variables from being optimized away. +static void UseItOrLoseIt(void* aPtr, int aSeven) { + char buf[64]; + int n = SprintfLiteral(buf, "%p\n", aPtr); + if (n == 20 + aSeven) { + fprintf(stderr, "well, that is surprising"); + } +} + +// This function checks that heap blocks that have the same stack trace but +// different (or no) reporters get aggregated separately. +void Foo(int aSeven) { + char* a[6]; + for (int i = 0; i < aSeven - 1; i++) { + a[i] = (char*)malloc(128 - 16 * i); + UseItOrLoseIt(a[i], aSeven); + } + + // Oddly, some versions of clang will cause identical stack traces to be + // generated for adjacent calls to Report(), which breaks the test. Inserting + // the UseItOrLoseIt() calls in between is enough to prevent this. + + Report(a[2]); // reported + + UseItOrLoseIt(a[2], aSeven); + + for (int i = 0; i < aSeven - 5; i++) { + Report(a[i]); // reported + UseItOrLoseIt(a[i], aSeven); + } + + UseItOrLoseIt(a[2], aSeven); + + Report(a[3]); // reported + + // a[4], a[5] unreported +} + +void TestEmpty(const char* aTestName, const char* aMode) { + char filename[128]; + SprintfLiteral(filename, "complete-%s-%s.json", aTestName, aMode); + auto f = MakeUnique<FpWriteFunc>(filename); + + char options[128]; + SprintfLiteral(options, "--mode=%s --stacks=full", aMode); + ResetEverything(options); + + // Zero for everything. + Analyze(std::move(f)); +} + +void TestFull(const char* aTestName, int aNum, const char* aMode, int aSeven) { + char filename[128]; + SprintfLiteral(filename, "complete-%s%d-%s.json", aTestName, aNum, aMode); + auto f = MakeUnique<FpWriteFunc>(filename); + + // The --show-dump-stats=yes is there just to give that option some basic + // testing, e.g. ensure it doesn't crash. It's hard to test much beyond that. + char options[128]; + SprintfLiteral(options, "--mode=%s --stacks=full --show-dump-stats=yes", + aMode); + ResetEverything(options); + + // Analyze 1: 1 freed, 9 out of 10 unreported. + // Analyze 2: still present and unreported. + int i; + char* a = nullptr; + for (i = 0; i < aSeven + 3; i++) { + a = (char*)malloc(100); + UseItOrLoseIt(a, aSeven); + } + free(a); + + // A no-op. + free(nullptr); + + // Note: 16 bytes is the smallest requested size that gives consistent + // behaviour across all platforms with jemalloc. + // Analyze 1: reported. + // Analyze 2: thrice-reported. + char* a2 = (char*)malloc(16); + Report(a2); + + // Analyze 1: reported. + // Analyze 2: reportedness carries over, due to ReportOnAlloc. + char* b = (char*)malloc(10); + ReportOnAlloc(b); + + // ReportOnAlloc, then freed. + // Analyze 1: freed, irrelevant. + // Analyze 2: freed, irrelevant. + char* b2 = (char*)malloc(16); + ReportOnAlloc(b2); + free(b2); + + // Analyze 1: reported 4 times. + // Analyze 2: freed, irrelevant. + char* c = (char*)calloc(10, 3); + Report(c); + for (int i = 0; i < aSeven - 4; i++) { + Report(c); + } + + // Analyze 1: ignored. + // Analyze 2: irrelevant. + Report((void*)(intptr_t)i); + + // jemalloc rounds this up to 8192. + // Analyze 1: reported. + // Analyze 2: freed. + char* e = (char*)malloc(4096); + e = (char*)realloc(e, 7169); + Report(e); + + // First realloc is like malloc; second realloc is shrinking. + // Analyze 1: reported. + // Analyze 2: re-reported. + char* e2 = (char*)realloc(nullptr, 1024); + e2 = (char*)realloc(e2, 512); + Report(e2); + + // First realloc is like malloc; second realloc creates a min-sized block. + // XXX: on Windows, second realloc frees the block. + // Analyze 1: reported. + // Analyze 2: freed, irrelevant. + char* e3 = (char*)realloc(nullptr, 1023); + // e3 = (char*) realloc(e3, 0); + MOZ_ASSERT(e3); + Report(e3); + + // Analyze 1: freed, irrelevant. + // Analyze 2: freed, irrelevant. + char* f1 = (char*)malloc(64); + UseItOrLoseIt(f1, aSeven); + free(f1); + + // Analyze 1: ignored. + // Analyze 2: irrelevant. + Report((void*)(intptr_t)0x0); + + // Analyze 1: mixture of reported and unreported. + // Analyze 2: all unreported. + Foo(aSeven); + + // Analyze 1: twice-reported. + // Analyze 2: twice-reported. + char* g1 = (char*)malloc(77); + ReportOnAlloc(g1); + ReportOnAlloc(g1); + + // Analyze 1: mixture of reported and unreported. + // Analyze 2: all unreported. + // Nb: this Foo() call is deliberately not adjacent to the previous one. See + // the comment about adjacent calls in Foo() for more details. + Foo(aSeven); + + // Analyze 1: twice-reported. + // Analyze 2: once-reported. + char* g2 = (char*)malloc(78); + Report(g2); + ReportOnAlloc(g2); + + // Analyze 1: twice-reported. + // Analyze 2: once-reported. + char* g3 = (char*)malloc(79); + ReportOnAlloc(g3); + Report(g3); + + // All the odd-ball ones. + // Analyze 1: all unreported. + // Analyze 2: all freed, irrelevant. + // XXX: no memalign on Mac + // void* w = memalign(64, 65); // rounds up to 128 + // UseItOrLoseIt(w, aSeven); + + // XXX: posix_memalign doesn't work on B2G + // void* x; + // posix_memalign(&y, 128, 129); // rounds up to 256 + // UseItOrLoseIt(x, aSeven); + + // XXX: valloc doesn't work on Windows. + // void* y = valloc(1); // rounds up to 4096 + // UseItOrLoseIt(y, aSeven); + + // XXX: C11 only + // void* z = aligned_alloc(64, 256); + // UseItOrLoseIt(z, aSeven); + + if (aNum == 1) { + // Analyze 1. + Analyze(std::move(f)); + } + + ClearReports(); + + //--------- + + Report(a2); + Report(a2); + free(c); + free(e); + Report(e2); + free(e3); + // free(w); + // free(x); + // free(y); + // free(z); + + // Do some allocations that will only show up in cumulative mode. + for (int i = 0; i < 100; i++) { + void* v = malloc(128); + UseItOrLoseIt(v, aSeven); + free(v); + } + + if (aNum == 2) { + // Analyze 2. + Analyze(std::move(f)); + } +} + +void TestPartial(const char* aTestName, const char* aMode, int aSeven) { + char filename[128]; + SprintfLiteral(filename, "complete-%s-%s.json", aTestName, aMode); + auto f = MakeUnique<FpWriteFunc>(filename); + + char options[128]; + SprintfLiteral(options, "--mode=%s", aMode); + ResetEverything(options); + + int kTenThousand = aSeven + 9993; + char* s; + + // The output of this function is deterministic but it relies on the + // probability and seeds given to the FastBernoulliTrial instance in + // ResetBernoulli(). If they change, the output will change too. + + // Expected fraction with stacks: (1 - (1 - 0.003) ** 16) = 0.0469. + // So we expect about 0.0469 * 10000 == 469. + // We actually get 511. + for (int i = 0; i < kTenThousand; i++) { + s = (char*)malloc(16); + UseItOrLoseIt(s, aSeven); + } + + // Expected fraction with stacks: (1 - (1 - 0.003) ** 128) = 0.3193. + // So we expect about 0.3193 * 10000 == 3193. + // We actually get 3136. + for (int i = 0; i < kTenThousand; i++) { + s = (char*)malloc(128); + UseItOrLoseIt(s, aSeven); + } + + // Expected fraction with stacks: (1 - (1 - 0.003) ** 1024) = 0.9539. + // So we expect about 0.9539 * 10000 == 9539. + // We actually get 9531. + for (int i = 0; i < kTenThousand; i++) { + s = (char*)malloc(1024); + UseItOrLoseIt(s, aSeven); + } + + Analyze(std::move(f)); +} + +void TestScan(int aSeven) { + auto f = MakeUnique<FpWriteFunc>("basic-scan.json"); + + ResetEverything("--mode=scan"); + + uintptr_t* p = (uintptr_t*)malloc(6 * sizeof(uintptr_t)); + UseItOrLoseIt(p, aSeven); + + // Hard-coded values checked by scan-test.py + p[0] = 0x123; // outside a block, small value + p[1] = 0x0; // null + p[2] = (uintptr_t)((uint8_t*)p - 1); // pointer outside a block, but nearby + p[3] = (uintptr_t)p; // pointer to start of a block + p[4] = (uintptr_t)((uint8_t*)p + 1); // pointer into a block + p[5] = 0x0; // trailing null + + Analyze(std::move(f)); +} + +void RunTests() { + // This test relies on the compiler not doing various optimizations, such as + // eliding unused malloc() calls or unrolling loops with fixed iteration + // counts. So we compile it with -O0 (or equivalent), which probably prevents + // that. We also use the following variable for various loop iteration + // counts, just in case compilers might unroll very small loops even with + // -O0. + int seven = 7; + + // Make sure that DMD is actually running; it is initialized on the first + // allocation. + int* x = (int*)malloc(100); + UseItOrLoseIt(x, seven); + MOZ_RELEASE_ASSERT(IsRunning()); + + // Please keep this in sync with run_test in test_dmd.js. + + TestEmpty("empty", "live"); + TestEmpty("empty", "dark-matter"); + TestEmpty("empty", "cumulative"); + + TestFull("full", 1, "live", seven); + TestFull("full", 1, "dark-matter", seven); + + TestFull("full", 2, "dark-matter", seven); + TestFull("full", 2, "cumulative", seven); + + TestPartial("partial", "live", seven); + + TestScan(seven); +} + +int main() { + RunTests(); + + return 0; +} |