Skip to content
DebugAllocatorLiveCount

DebugAllocatorLiveCount

Description

Number of outstanding allocations (alloc minus free), read from the embedded live map.

Parameters

Name Direction Description
self in DebugAllocator instance, or NULL.

Success

Returns the live-allocation count. No state is touched.

Failure

Returns 0 when self is NULL.

Usage example (Cross-references)

Usage examples (Cross-references)
    // ---------------------------------------------------------------------------
    
    size DebugAllocatorLiveCount(const DebugAllocator *self) {
        if (!self)
            return 0;
    
        bool ok = p1 && p2;
        ok      = ok && DebugAllocatorLiveCount(&dbg) == 2;
        ok      = ok && DebugAllocatorLiveBytes(&dbg) == (64 + 128);
    
        AllocatorFree(adbg, p1);
        ok = ok && DebugAllocatorLiveCount(&dbg) == 1;
        ok = ok && DebugAllocatorLiveBytes(&dbg) == 128;
    
        AllocatorFree(adbg, p2);
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
        ok = ok && DebugAllocatorLiveBytes(&dbg) == 0;
        ok = ok && DebugAllocatorOverflows(&dbg) == 0;
    
        void *p  = AllocatorAlloc(adbg, 0, false);
        bool  ok = (p == NULL) && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        AllocatorFree(adbg, NULL); // no-op, must not crash
        void *p  = AllocatorAlloc(adbg, 32, false);
        bool  ok = (p != NULL) && (DebugAllocatorLiveCount(&dbg) == 1);
        if (p)
            AllocatorFree(adbg, p);
        (void)AllocatorAlloc(adbg, 48, true);
    
        bool ok = DebugAllocatorLiveCount(&dbg) == 2;
        ok      = ok && DebugAllocatorLiveBytes(&dbg) == (32 + 48);
        }
        ok = ok && (DebugAllocatorFreedCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        u8 *grown = (u8 *)AllocatorRealloc(adbg, p, 200);
        ok        = ok && (grown != NULL) && (grown[0] == 'h') && (grown[15] == '!');
        ok        = ok && (DebugAllocatorLiveCount(&dbg) == 1);
        ok        = ok && (DebugAllocatorLiveBytes(&dbg) == 200);
    
        void *p   = AllocatorAlloc(adbg, 24, false);
        bool  ok  = (p != NULL) && (DebugAllocatorLiveCount(&dbg) == 1);
        void *nil = AllocatorRealloc(adbg, p, 0);
        ok        = ok && (nil == NULL) && (DebugAllocatorLiveCount(&dbg) == 0);
        bool  ok  = (p != NULL) && (DebugAllocatorLiveCount(&dbg) == 1);
        void *nil = AllocatorRealloc(adbg, p, 0);
        ok        = ok && (nil == NULL) && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
    
        void *p  = AllocatorRealloc(adbg, NULL, 32);
        bool  ok = (p != NULL) && (DebugAllocatorLiveCount(&dbg) == 1);
        if (p)
            AllocatorFree(adbg, p);
    
        void *p  = AllocatorAlloc(adbg, 64, true);
        bool  ok = (p != NULL) && (DebugAllocatorLiveCount(&dbg) == 1);
        ok       = ok && (DebugAllocatorLiveBytes(&dbg) == 64);
        ok       = ok && (((u64)p & 0xfff) == 0); // page-aligned
    
        AllocatorFree(adbg, p);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
    
        ok = ok && (DebugAllocatorOverflows(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        ok       = ok && (AllocatorFailedAllocations(adbg) == 1);
        // No live record / bytes for a failed alloc.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
            ok    = ok && (ps[i] != NULL);
        }
        ok = ok && (DebugAllocatorLiveCount(&dbg) == N);
    
        // Every pointer must resolve to its own live record (hash + probe
            ps[i] = NULL;
        }
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
        ok   = ok && (r == 0);
        // The allocation is untouched -- still one live record of 64 bytes.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 1);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 64);
        ok         = ok && (grown[50] == 0x5A);
        ok         = ok && (grown[63] == 0x6B);
        ok         = ok && (DebugAllocatorLiveCount(&dbg) == 1);
        ok         = ok && (DebugAllocatorLiveBytes(&dbg) == 200);
        for (u32 i = 0; i < 8; i++)
            ok = ok && (shrunk[i] == (u8)(i + 1));
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 1);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 8);
        // With four registered specs (plus the auto --help spec) left_col is
        // populated; the 414 loop must free each. A removed Deinit leaks them.
        bool ok = (rc == ARG_RUN_HELP) && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
    // A test passes iff `ok` held AND the DebugAllocator has no outstanding
    // allocations after the test released everything it owns.
    #define LEAK_CLEAN(dbg) (DebugAllocatorLiveCount(&(dbg)) == 0 && DebugAllocatorLiveBytes(&(dbg)) == 0)
    
    static bool test_graph_reserve_clear(void) {
        bool result = GraphReserve(&graph, 8);
    
        size before = DebugAllocatorLiveCount(&dbg);
    
        g_fail_copy           = true;
        g_fail_copy           = false;
    
        size after = DebugAllocatorLiveCount(&dbg);
    
        result = result && (failed_id == 0);
        result      = result && (GraphCommitChanges(&graph) == 1);
    
        size before = DebugAllocatorLiveCount(&dbg);
    
        g_fail_copy           = true;
        g_fail_copy           = false;
    
        size after = DebugAllocatorLiveCount(&dbg);
    
        result = result && (failed_id == 0);
    // Convenience: a test passes iff `ok` held AND the DebugAllocator has no
    // outstanding allocations after the test released everything it owns.
    #define LEAK_CLEAN(dbg) (DebugAllocatorLiveCount(&(dbg)) == 0 && DebugAllocatorLiveBytes(&(dbg)) == 0)
    
    // Leak-only config: live-count tracking, NO per-alloc stack-trace / canary /
    // Convenience: a test passes iff `ok` held AND the DebugAllocator has no
    // outstanding allocations after the test released everything it owns.
    #define LEAK_CLEAN(dbg) (DebugAllocatorLiveCount(&(dbg)) == 0 && DebugAllocatorLiveBytes(&(dbg)) == 0)
    
    // Leak-only config: live-count tracking, NO per-alloc stack-trace / canary /
    // Convenience: a test passes iff `ok` held AND the DebugAllocator has no
    // outstanding allocations after the test released everything it owns.
    #define LEAK_CLEAN(dbg) (DebugAllocatorLiveCount(&(dbg)) == 0 && DebugAllocatorLiveBytes(&(dbg)) == 0)
    
    // Leak-only config: live-count tracking, NO per-alloc stack-trace / canary /
        // Real code frees the temp clone inside rotate; with the Deinit
        // removed it survives as a live allocation after bv is torn down.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        // Real code frees the temp clone inside rotate; with the Deinit
        // removed it survives as a live allocation after bv is torn down.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        // The rendered bv_str borrows bv's DebugAllocator. Real code frees it;
        // the mutant leaks it, so the live count stays non-zero after teardown.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        // bv_str (the render of bv) borrows bv's DebugAllocator. Real code
        // frees it; the mutant leaks it.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DefaultAllocatorDeinit(&palloc);
    }
    
    #define LEAK_CLEAN(dbg) (DebugAllocatorLiveCount(&(dbg)) == 0 && DebugAllocatorLiveBytes(&(dbg)) == 0)
    
    #define LEAK_CFG                                                                                                       \
        // After releasing every Int the test created, nothing the function
        // allocated internally should remain outstanding.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        IntDeinit(&inverse);
    
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
        DebugAllocatorDeinit(&dbg);
        return ok;
        IntDeinit(&check);
    
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
        DebugAllocatorDeinit(&dbg);
        return ok;
        IntDeinit(&check);
    
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
        DebugAllocatorDeinit(&dbg);
        return ok;
        IntDeinit(&r);
    
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
        DebugAllocatorDeinit(&dbg);
        return ok;
        IntDeinit(&next);
    
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
        DebugAllocatorDeinit(&dbg);
        return ok;
        IntDeinit(&value);
    
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
        DebugAllocatorDeinit(&dbg);
        return ok;
    }
    
    #define LEAK_CLEAN(dbg) (DebugAllocatorLiveCount(&(dbg)) == 0 && DebugAllocatorLiveBytes(&(dbg)) == 0)
    
    #define LEAK_CFG                                                                                                       \
        IntDeinit(&parsed);
    
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
        DebugAllocatorDeinit(&dbg);
        return ok;
    #define LEAK_WRITE_EPILOGUE()                                                                                          \
        StrDeinit(&out);                                                                                                   \
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);                           \
        DebugAllocatorDeinit(&dbg);                                                                                        \
        return ok
    static bool dbg_no_leak(DebugAllocator *dbg, Str *out) {
        StrDeinit(out);
        bool ok = (DebugAllocatorLiveCount(dbg) == 0);
        DebugAllocatorDeinit(dbg);
        return ok;
        bool           ok  = StrAppendFmt(&out, "{x}", s) && (StrLen(&out) > 0);
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        StrDeinit(&s);
        ok = ok && StrAppendFmt(&out, "{}", f) && (StrLen(&out) > 0);
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        FloatDeinit(&f);
    
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
        ok = ok && StrAppendFmt(&out, "{}", s) && (StrLen(&out) > 0);
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        StrDeinit(&s);
        ok = ok && StrAppendFmt(&out, "{}", v) && (StrLen(&out) > 0);
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        IntDeinit(&v);
        ok = ok && StrAppendFmt(&out, "{}", v) && (StrLen(&out) > 0);
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        FloatDeinit(&v);
        ok = ok && StrAppendFmt(&out, "{}", v) && (StrLen(&out) > 0);
        StrDeinit(&out);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        BitVecDeinit(&v);
        if (s)
            AllocatorFree(&dbg.base, s);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        bool ok = (BitVecToInteger(&bv) == 1) && (BitVecLen(&bv) == 4); // min-width clamp
        BitVecDeinit(&bv);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        bool ok = (BitVecToInteger(&bv) == 1) && (BitVecLen(&bv) == 3);
        BitVecDeinit(&bv);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        bool ok = (BitVecLen(&bv) == 5) && (BitVecToInteger(&bv) == 13);
        BitVecDeinit(&bv);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        StrDeinit(&t);
        IntDeinit(&v);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        StrDeinit(&t);
        FloatDeinit(&f);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        StrDeinit(&t);
        FloatDeinit(&f);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        Zstr           p   = input;
        StrReadFmt(p, fmt, s); // reader frees s on the unterminated/error branch
        bool ok = (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        StrReadFmt(p, "{}", bv);
        BitVecDeinit(&bv);
        bool ok = (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        Zstr           p   = "aaaaaaaaaaaaaaaaaaaaaaaa\\q"; // 24 'a' + invalid \q
        StrReadFmt(p, "{}", s);
        bool ok = (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        StrReadFmt(p, "{}", fv);
        FloatDeinit(&fv);
        bool ok = (DebugAllocatorLiveCount(&dbg) == 0) && (DebugAllocatorLiveBytes(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
    
        IntDeinit(&value);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        FloatDeinit(&value);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
        // Each piece is long enough to force its own heap buffer.
        Str  s      = StrInitFromZstr("alphaaa,betaaaa,gammaaa,deltaaa", &dbg);
        size before = DebugAllocatorLiveCount(&dbg);
    
        Strs parts = StrSplit(&s, ",");
        // Sanity: the split really produced multiple heap-backed elements,
        // otherwise the leak would be unobservable.
        bool produced = VecLen(&parts) == 4 && DebugAllocatorLiveCount(&dbg) > before;
    
        VecDeinit(&parts); // invokes str_deinit per element on real code.
    
        VecDeinit(&parts); // invokes str_deinit per element on real code.
        size after = DebugAllocatorLiveCount(&dbg);
    
        // Real code: every element buffer (+ backing array) freed -> back to
        StrDeinit(&s);
    
        bool result = (DebugAllocatorLiveCount(&dbg) == 0);
        if (!result) {
            WriteFmt("    FAIL: Expected 0 live allocations after StrDeinit\n");
    
        // Live allocations are non-zero here (clones + table storage).
        bool result = (DebugAllocatorLiveCount(&dbg) > 0);
    
        // Exercise every removal path against the deep-copy map.
    
        // After deinit, every clone and the table storage must be freed.
        result = result && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        DebugAllocator alloc    = DebugAllocatorInit();
        Allocator     *base     = ALLOCATOR_OF(&alloc);
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        char pe_path[1024];
            PdbCacheDeinit(&cache);
            // Teardown returns every allocation (the failed-lookup candidate too).
            ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
        }
        DebugAllocator alloc    = DebugAllocatorInit();
        Allocator     *base     = ALLOCATOR_OF(&alloc);
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        char pe_path[1024];
            PdbCacheDeinit(&cache);
            // The briefly-opened, then-rejected PDB must be freed.
            ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
        }
    
            // Both modules now open and cached; snapshot the live allocations.
            size mid = DebugAllocatorLiveCount(&alloc);
    
            // Re-resolve B at a different IP: must hit B's existing slot.
            Zstr name2 = NULL;
            bool rb2   = PdbCacheResolve(&cache, (Zstr)b_pe, mbase, mbase + 0x1108, &name2, &off);
            size after = DebugAllocatorLiveCount(&alloc);
    
            ok = ra && rb && rb2 && name2 && ZstrCompare(name2, "winproc") == 0 && after == mid;
        DebugAllocator alloc    = DebugAllocatorInit();
        Allocator     *base     = ALLOCATOR_OF(&alloc);
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        char pe_path[1024];
    
            // A populated cache with an opened PE+PDB sits strictly above baseline.
            bool grew = r && DebugAllocatorLiveCount(&alloc) > baseline;
    
            PdbCacheDeinit(&cache);
            PdbCacheDeinit(&cache);
            // Correct teardown releases every cached allocation.
            ok = grew && DebugAllocatorLiveCount(&alloc) == baseline;
        }
        DebugAllocator alloc    = DebugAllocatorInit();
        Allocator     *a        = ALLOCATOR_OF(&alloc);
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        char pe_path[1024];
            ok             = !PdbCacheResolve(&cache, (Zstr)pe_path, 0x140000000ull, 0x140001100ull, &name, NULL);
            PdbCacheDeinit(&cache);
            ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
        }
        DebugAllocator alloc    = DebugAllocatorInit();
        Allocator     *base     = ALLOCATOR_OF(&alloc);
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        char a_pe[1024], a_pdb[1024], b_pe[1024], b_pdb[1024];
    
            // A populated two-entry cache sits strictly above baseline.
            bool grew = ra && rb && DebugAllocatorLiveCount(&alloc) > baseline;
    
            PdbCacheDeinit(&cache);
            PdbCacheDeinit(&cache);
            // Freeing EVERY entry (not just the first) returns to baseline.
            ok = grew && DebugAllocatorLiveCount(&alloc) == baseline;
        }
        u64 plen = StrLen(&path);
    
        size baseline = DebugAllocatorLiveCount(&alloc);
    
        DnsResolver r;
        DnsResolverDeinit(&r);
    
        bool ok = DebugAllocatorLiveCount(&alloc) == baseline;
    
        drop_temp(&path);
        DebugAllocator alloc    = DebugAllocatorInit();
        Allocator     *a        = ALLOCATOR_OF(&alloc);
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        DnsResolver r;
        resolver_init_crafted(&r, a);
    
        bool grew = DebugAllocatorLiveCount(&alloc) > baseline;
    
        DnsResolverDeinit(&r);
        DnsResolverDeinit(&r);
    
        bool back = DebugAllocatorLiveCount(&alloc) == baseline;
        bool ok   = grew && back && r.allocator == NULL;
        DebugAllocator alloc    = DebugAllocatorInit();
        Allocator     *a        = ALLOCATOR_OF(&alloc);
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        DnsResolver r;
        DnsResolverDeinit(&r);
    
        bool back = DebugAllocatorLiveCount(&alloc) == baseline;
    
        DebugAllocatorDeinit(&alloc);
        bool      ok       = mc_write_file(bin_path, mc_bin_buf, bin_size);
    
        size baseline = DebugAllocatorLiveCount(&dbg);
    
        MachoCache cache      = MachoCacheInit(base);
    
        // Cache now holds allocations; live count must have grown.
        ok = ok && DebugAllocatorLiveCount(&dbg) > baseline;
    
        MachoCacheDeinit(&cache);
    
        MachoCacheDeinit(&cache);
        ok = ok && DebugAllocatorLiveCount(&dbg) == baseline;
    
        FileRemove(bin_path);
        ok              = ok && mc_write_file(bin_b, mc_dsym_buf, sz_b);
    
        size baseline = DebugAllocatorLiveCount(&dbg);
    
        MachoCache cache = MachoCacheInit(base);
        ok             = ok && MachoCacheResolve(&cache, bin_b, 0, 0x100000110ull, &name, &off);
        ok             = ok && VecLen(&cache.entries) == 2;
        size after_two = DebugAllocatorLiveCount(&dbg);
    
        // Re-resolve module B: its entry lives at index 1, so the find loop
        ok = ok && MachoCacheResolve(&cache, bin_b, 0, 0x100000110ull, &name, &off);
        ok = ok && VecLen(&cache.entries) == 2;
        ok = ok && DebugAllocatorLiveCount(&dbg) == after_two;
    
        // Re-resolve module A (index 0) too: still a hit, no growth.
        ok = ok && MachoCacheResolve(&cache, bin_a, 0, 0x100000110ull, &name, &off);
        ok = ok && VecLen(&cache.entries) == 2;
        ok = ok && DebugAllocatorLiveCount(&dbg) == after_two;
    
        // Two entries are live; MachoCacheDeinit must free BOTH and return to
        // entry 0 and leaks entry 1.
        MachoCacheDeinit(&cache);
        ok = ok && DebugAllocatorLiveCount(&dbg) == baseline;
    
        FileRemove(bin_a);
        ok                  = ok && mc_write_file(dsym_path, mc_dsym_buf, dsym_size);
    
        size baseline = DebugAllocatorLiveCount(&dbg);
    
        MachoCache cache    = MachoCacheInit(base);
        bool       resolved = MachoCacheResolve(&cache, bin_path, 0, 0x100000208ull, &name, &offset);
        ok                  = ok && resolved && name && ZstrCompare(name, "dsym_only_fn") == 0 && offset == 0x8;
        ok                  = ok && DebugAllocatorLiveCount(&dbg) > baseline;
    
        MachoCacheDeinit(&cache);
    
        MachoCacheDeinit(&cache);
        ok = ok && DebugAllocatorLiveCount(&dbg) == baseline;
    
        FileRemove(bin_path);
        ok                  = ok && mc_write_file(dsym_path, mc_dsym_buf, dsym_size);
    
        size baseline = DebugAllocatorLiveCount(&dbg);
    
        MachoCache cache = MachoCacheInit(base);
    
        MachoCacheDeinit(&cache);
        ok = ok && DebugAllocatorLiveCount(&dbg) == baseline;
    
        FileRemove(bin_path);
        ok                  = ok && mc_write_file(dsym_path, mc_dsym_buf, dsym_size);
    
        size baseline = DebugAllocatorLiveCount(&dbg);
    
        MachoCache cache  = MachoCacheInit(base);
        bool resolved = MachoCacheResolve(&cache, bin_path, 0, low + 0x20, &name, &offset);
        ok            = ok && resolved && name && ZstrCompare(name, fn_name) == 0 && offset == 0x20;
        ok            = ok && DebugAllocatorLiveCount(&dbg) > baseline;
    
        MachoCacheDeinit(&cache);
    
        MachoCacheDeinit(&cache);
        ok = ok && DebugAllocatorLiveCount(&dbg) == baseline;
    
        FileRemove(bin_path);
        ok                  = ok && bl_write_file(dsym_path, bl_dsym_buf, dsym_size);
    
        size baseline = DebugAllocatorLiveCount(&dbg);
    
        MachoCache cache    = MachoCacheInit(base);
        bool       resolved = MachoCacheResolve(&cache, bin_path, 0, low + 0x14, &name, &offset);
        ok                  = ok && resolved && name && ZstrCompare(name, fn_name) == 0 && offset == 0x14;
        ok                  = ok && DebugAllocatorLiveCount(&dbg) > baseline;
    
        MachoCacheDeinit(&cache);
    
        MachoCacheDeinit(&cache);
        ok = ok && DebugAllocatorLiveCount(&dbg) == baseline;
    
        FileRemove(bin_path);
        ResolvedSymbol ra;
        bool           ok    = SymbolResolverResolve(&res, (void *)&sr1_marker_a, &ra) && ra.module_path;
        size           live1 = DebugAllocatorLiveCount(&alloc);
    
        ResolvedSymbol rb;
        ResolvedSymbol rb;
        ok         = ok && SymbolResolverResolve(&res, (void *)&sr1_marker_b, &rb) && rb.module_path;
        size live2 = DebugAllocatorLiveCount(&alloc);
    
        ok = ok && live2 == live1;                                   // no re-open
        ResolvedSymbol rc;
        bool           ok    = SymbolResolverResolve(&res, (void *)&sr1_marker_a, &rc) && rc.module_path;
        size           live1 = DebugAllocatorLiveCount(&alloc);
    
        ResolvedSymbol rd;
        ResolvedSymbol rd;
        ok         = ok && SymbolResolverResolve(&res, (void *)&sr_cache_data_marker, &rd) && rd.module_path;
        size live2 = DebugAllocatorLiveCount(&alloc);
    
        ok = ok && live2 == live1;                                   // fallback hit, no re-open
        u64 other  = 0;
        ok         = ok && find_other_module_addr(ra.module_path, &other);
        size live1 = DebugAllocatorLiveCount(&alloc);
    
        ResolvedSymbol ro;
        ResolvedSymbol ro;
        ok         = ok && SymbolResolverResolve(&res, (void *)other, &ro) && ro.module_path;
        size live2 = DebugAllocatorLiveCount(&alloc);
    
        ok = ok && ZstrCompare(ro.module_path, ra.module_path) != 0; // distinct module
        ResolvedSymbol r1;
        ok         = ok && SymbolResolverResolve(&res, (void *)other, &r1) && r1.module_path;
        size live1 = DebugAllocatorLiveCount(&alloc);
    
        ResolvedSymbol r2;
        ResolvedSymbol r2;
        ok         = ok && SymbolResolverResolve(&res, (void *)other, &r2) && r2.module_path;
        size live2 = DebugAllocatorLiveCount(&alloc);
    
        ok = ok && live2 == live1;                                   // hit on entry[1], no re-open
    bool test_sr_deinit_frees_everything(void) {
        DebugAllocator alloc    = DebugAllocatorInit();
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        SymbolResolver res;
    
        SymbolResolverDeinit(&res);
        ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
    
        DebugAllocatorDeinit(&alloc);
    bool test_sr_deinit_two_modules_frees_all(void) {
        DebugAllocator alloc    = DebugAllocatorInit();
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        SymbolResolver res;
    
        SymbolResolverDeinit(&res);
        ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
    
        DebugAllocatorDeinit(&alloc);
    bool test_sr_findfde_teardown_frees_cfi(void) {
        DebugAllocator alloc    = DebugAllocatorInit();
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        SymbolResolver res;
        u64             base = 0;
        bool            ok   = SymbolResolverFindFde(&res, (void *)&sr1_marker_a, &cfi, &fde, &base);
        ok                   = ok && DebugAllocatorLiveCount(&alloc) > baseline; // CFI was built
    
        SymbolResolverDeinit(&res);
    
        SymbolResolverDeinit(&res);
        ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
    
        DebugAllocatorDeinit(&alloc);
    bool test_sidecardwarf_teardown_frees_sidecar_functions(void) {
        DebugAllocator alloc    = DebugAllocatorInit();
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        SymbolResolver res;
        bool           ok = SymbolResolverResolve(&res, (void *)&scd_marker, &r);
        ok                = ok && r.symbol_name && ZstrFindSubstring(r.symbol_name, "scd_marker") != NULL;
        ok                = ok && DebugAllocatorLiveCount(&alloc) > baseline;
    
        SymbolResolverDeinit(&res);
    
        SymbolResolverDeinit(&res);
        ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
    
        DebugAllocatorDeinit(&alloc);
    bool test_sidecarsub_teardown_frees(void) {
        DebugAllocator alloc    = DebugAllocatorInit();
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        SymbolResolver res;
        bool           ok = SymbolResolverResolve(&res, (void *)&subdir_marker, &r);
        ok                = ok && r.symbol_name && ZstrFindSubstring(r.symbol_name, "subdir_marker") != NULL;
        ok                = ok && DebugAllocatorLiveCount(&alloc) > baseline;
    
        SymbolResolverDeinit(&res);
    
        SymbolResolverDeinit(&res);
        ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
    
        DebugAllocatorDeinit(&alloc);
    bool test_sidecar_teardown_frees_sidecar(void) {
        DebugAllocator alloc    = DebugAllocatorInit();
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        SymbolResolver res;
        // entry actually carries one.
        ok = ok && r.symbol_name && ZstrFindSubstring(r.symbol_name, "sidecar_marker") != NULL;
        ok = ok && DebugAllocatorLiveCount(&alloc) > baseline;
    
        SymbolResolverDeinit(&res);
    
        SymbolResolverDeinit(&res);
        ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
    
        DebugAllocatorDeinit(&alloc);
    bool test_dwarffallback_teardown_frees(void) {
        DebugAllocator alloc    = DebugAllocatorInit();
        size           baseline = DebugAllocatorLiveCount(&alloc);
    
        SymbolResolver res;
        // line table alongside it).
        ok = ok && r.symbol_name && ZstrFindSubstring(r.symbol_name, "df_marker_a") != NULL;
        ok = ok && DebugAllocatorLiveCount(&alloc) > baseline;
    
        SymbolResolverDeinit(&res);
    
        SymbolResolverDeinit(&res);
        ok = ok && DebugAllocatorLiveCount(&alloc) == baseline;
    
        DebugAllocatorDeinit(&alloc);
        DnsResponseDeinit(&resp);
    
        bool match = (ok == false) && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return match;
        DnsResponseDeinit(&resp);
    
        bool match = (ok == false) && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return match;
    
        DnsResponseDeinit(&resp);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        DnsResponseDeinit(&resp);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        DnsResponseDeinit(&resp);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
        // Crux: the fail path must have freed every allocation, including the
        // taken data buffer and the per-stream arrays.
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
    
        DebugAllocatorDeinit(&dbg);
    
        // Everything PdbDeinit owns -- including the name pool -- must be gone.
        ok = ok && DebugAllocatorLiveCount(&dbg) == 0;
    
        DebugAllocatorDeinit(&dbg);
        Allocator     *base = ALLOCATOR_OF(&dbg);
    
        size before = DebugAllocatorLiveCount(&dbg);
    
        ProcMaps maps;
        // The load must have taken at least one live allocation (raw buffer
        // and/or entries vector); otherwise the assertion below is vacuous.
        size during = DebugAllocatorLiveCount(&dbg);
        bool ok     = during > before;
    
        // After Deinit every load allocation is gone again.
        size after = DebugAllocatorLiveCount(&dbg);
        ok         = ok && after == before;
        }
    
        size before = DebugAllocatorLiveCount(&dbg);
    
        ProcMaps m;
    
        // That arm must have freed everything the partial read reserved.
        size after = DebugAllocatorLiveCount(&dbg);
        ok         = ok && (after == before);
        // reclaim the source so only the vec's deep copies remain live.
        HttpHeaderDeinit(&hh);
        ok = ok && (DebugAllocatorLiveCount(&dbg) > 0);
    
        // VecDeinit runs http_header_deinit on the surviving copy.
        // VecDeinit runs http_header_deinit on the surviving copy.
        VecDeinit(&headers);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        Zstr        next = HttpRequestParse(&req, raw);
        bool        ok   = (next != raw) && (StrLen(&req.url) > 0);
        ok               = ok && (DebugAllocatorLiveCount(&dbg) > 0);
    
        HttpRequestDeinit(&req);
    
        HttpRequestDeinit(&req);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        StrDeinit(&second);
        HttpResponseDeinit(&response);
        bool ok = (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
        StrDeinit(&path);
        HttpResponseDeinit(&response);
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
    
        DebugAllocatorDeinit(&dbg);
    
        // The open must have failed AND left no live allocations behind.
        bool ok = !opened && DebugAllocatorLiveCount(&dbg) == 0;
    
        DebugAllocatorDeinit(&dbg);
        }
    
        size       before = DebugAllocatorLiveCount(&dbg);
        DwarfLines lines;
        bool       built = DwarfLinesBuildFromElf(&lines, &elf, base);
        // pool freed by DwarfLinesDeinit -> back to baseline. Mutant (no
        // cu_strings_deinit): the per-CU scratch vectors leak -> after > before.
        size after = DebugAllocatorLiveCount(&dbg);
        ok         = ok && (after == before);
        }
    
        size       before = DebugAllocatorLiveCount(&dbg);
        DwarfLines lines;
        bool       built = DwarfLinesBuildFromElf(&lines, &elf, base);
            ok                      = ok && e && e->file && ZstrCompare(e->file, "source.c") == 0;
            // After the build there must be live allocations to release.
            ok = ok && (DebugAllocatorLiveCount(&dbg) > before);
            DwarfLinesDeinit(&lines);
        }
        // Real Deinit frees entries + string_pool -> baseline. Mutant leaves
        // the string pool buffer live -> after > before.
        size after = DebugAllocatorLiveCount(&dbg);
        ok         = ok && (after == before);
        ElfDeinit(&elf);
    
        bool ok = !built && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        ElfDeinit(&elf);
    
        bool ok = !built && (DebugAllocatorLiveCount(&dbg) == 0);
        DebugAllocatorDeinit(&dbg);
        return ok;
        MapDeinit(&cfg);
    
        ok = DebugAllocatorLiveCount(&dbg) == 0;
        DebugAllocatorDeinit(&dbg);
        return ok;
    
        // The stripped-away old value buffer must already be freed.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        // The parser's local key copy must already be freed.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        // The parser's local value copy must already be freed.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
    
        // Every transient lookup key must already be freed.
        ok = ok && (DebugAllocatorLiveCount(&dbg) == 0);
        ok = ok && (DebugAllocatorLiveBytes(&dbg) == 0);
Last updated on