Skip to content
DebugAllocator

DebugAllocator

Description

DebugAllocator struct. Owns its heap / meta / page backing allocators inline; no external parent / meta to pass at init. Init-by-value via DebugAllocatorInit().

Usage example (Cross-references)

Usage examples (Cross-references)
    // overflows, no double-frees.
    bool test_debug_normal_alloc_free(void) {
        DebugAllocator dbg  = DebugAllocatorInit();
        Allocator     *adbg = ALLOCATOR_OF(&dbg);
    // double-free counter without crashing.
    bool test_debug_catches_double_free(void) {
        DebugAllocator dbg  = DebugAllocatorInit();
        Allocator     *adbg = ALLOCATOR_OF(&dbg);
    // bump the overflow counter.
    bool test_debug_catches_overflow(void) {
        DebugAllocator dbg  = DebugAllocatorInit();
        Allocator     *adbg = ALLOCATOR_OF(&dbg);
    // we query before destroy.
    bool test_debug_leak_count(void) {
        DebugAllocator dbg  = DebugAllocatorInit();
        Allocator     *adbg = ALLOCATOR_OF(&dbg);
        DebugAllocatorConfig cfg = DEBUG_ALLOCATOR_DEFAULTS;
        cfg.force_page_backing   = true;
        DebugAllocator dbg       = DebugAllocatorInitWith(cfg);
        Allocator     *adbg      = ALLOCATOR_OF(&dbg);
    // ---------------------------------------------------------------------------
    
    static DebugAllocator *debug_validate_self(const Allocator *self) {
        if (!self || self->__magic != MISRA_DEBUG_ALLOCATOR_MAGIC) {
            LOG_FATAL("type-confusion: allocator passed to debug_allocator_* is not a DebugAllocator");
    static DebugAllocator *debug_validate_self(const Allocator *self) {
        if (!self || self->__magic != MISRA_DEBUG_ALLOCATOR_MAGIC) {
            LOG_FATAL("type-confusion: allocator passed to debug_allocator_* is not a DebugAllocator");
        }
        DebugAllocator *dbg     = (DebugAllocator *)self;
            LOG_FATAL("type-confusion: allocator passed to debug_allocator_* is not a DebugAllocator");
        }
        DebugAllocator *dbg     = (DebugAllocator *)self;
        u64             cur_tid = debug_current_tid();
        if (dbg->creator_tid != cur_tid) {
        if (dbg->creator_tid != cur_tid) {
            LOG_FATAL(
                "DebugAllocator: cross-thread use detected (created on {x}, called from {x}). "
                "Each thread must use its own DebugAllocator instance.",
                dbg->creator_tid,
            LOG_FATAL(
                "DebugAllocator: cross-thread use detected (created on {x}, called from {x}). "
                "Each thread must use its own DebugAllocator instance.",
                dbg->creator_tid,
                cur_tid
    
    void *debug_allocator_allocate(Allocator *self, size bytes, i8 zeroed) {
        DebugAllocator *dbg = debug_validate_self(self);
        if (!bytes)
            return NULL;
        if (!MapInsertR(&dbg->live, user_p, rec)) {
            AllocatorFree(src, user_p, padded);
            LOG_ERROR("DebugAllocator: failed to record allocation in live map");
            return NULL;
        }
    }
    
    static void debug_record_free_trace(DebugAllocator *dbg, DebugRecord *rec) {
        if (dbg->config.capture_traces && dbg->config.trace_depth > 0) {
            u32 depth = dbg->config.trace_depth;
    
    void debug_allocator_deallocate(Allocator *self, void *ptr, size bytes) {
        DebugAllocator *dbg = debug_validate_self(self);
        if (!ptr)
            return;
                    dbg->double_frees += 1;
                    LOG_ERROR(
                        "DebugAllocator: DOUBLE FREE of {x} (originally {} bytes)",
                        (u64)(uintptr_t)ptr,
                        (u64)freed_rec->requested_size
                }
            }
            LOG_ERROR("DebugAllocator: free of unknown pointer {x}", (u64)(uintptr_t)ptr);
            return;
        }
        if (bytes && bytes != live_rec->requested_size) {
            LOG_ERROR(
                "DebugAllocator: size mismatch on free of {x} (claimed {} bytes, tracked {} bytes)",
                (u64)(uintptr_t)ptr,
                (u64)bytes,
                dbg->overflows += 1;
                LOG_ERROR(
                    "DebugAllocator: BUFFER OVERFLOW past {x} ({} bytes requested)",
                    (u64)(uintptr_t)ptr,
                    (u64)live_rec->requested_size
            size rounded   = (padded + page_size - 1) & ~(page_size - 1);
            if (!PageProtect(ptr, rounded, PAGE_PROT_NONE)) {
                LOG_ERROR("DebugAllocator: PageProtect(PROT_NONE) failed on {x}", (u64)(uintptr_t)ptr);
            }
        } else {
    
    void *debug_allocator_reallocate(Allocator *self, void *ptr, size old_size, size new_size) {
        DebugAllocator *dbg = debug_validate_self(self);
        if (new_size == 0) {
            debug_allocator_deallocate(self, ptr, old_size);
    // ---------------------------------------------------------------------------
    
    void DebugAllocatorDeinit(DebugAllocator *self) {
        if (!self || self->base.__magic != MISRA_DEBUG_ALLOCATOR_MAGIC)
            return;
        if (self->creator_tid != cur_tid) {
            LOG_FATAL(
                "DebugAllocator: Deinit called from a different thread (created on {x}, called from {x}).",
                self->creator_tid,
                cur_tid
        // Report leaks for anything still in `live`.
        if (self->live.allocator && self->live.length > 0) {
            LOG_ERROR("DebugAllocator: {} live allocation(s) at deinit time:", (u64)self->live.length);
            MapForeachPairPtr(&self->live, key_ptr, val_ptr) {
                LOG_ERROR("  leaked {x} ({} bytes)", (u64)(uintptr_t)*key_ptr, (u64)val_ptr->requested_size);
    // ---------------------------------------------------------------------------
    
    size DebugAllocatorLiveCount(const DebugAllocator *self) {
        if (!self)
            return 0;
    }
    
    size DebugAllocatorLiveBytes(const DebugAllocator *self) {
        if (!self)
            return 0;
    }
    
    size DebugAllocatorDoubleFrees(const DebugAllocator *self) {
        if (!self)
            return 0;
    }
    
    size DebugAllocatorOverflows(const DebugAllocator *self) {
        if (!self)
            return 0;
    }
    
    void DebugAllocatorReportLeaks(DebugAllocator *self, Str *out) {
        if (!self || !out)
            return;
            return;
    
        StrWriteFmt(out, "DebugAllocator: {} live allocation(s):\n", (u64)self->live.length);
        MapForeachPairPtr(&self->live, key_ptr, val_ptr) {
            StrWriteFmt(out, "  leak: {x} ({} bytes)\n", (u64)(uintptr_t)*key_ptr, (u64)val_ptr->requested_size);
        typedef struct SlabAllocator   SlabAllocator;
        typedef struct BudgetAllocator BudgetAllocator;
        typedef struct DebugAllocator  DebugAllocator;
    
        // `zeroed` uses `i8` (signed char) directly instead of `bool` to
            SlabAllocator *: (Allocator *)(allocator_ptr),                                                                 \
            BudgetAllocator *: (Allocator *)(allocator_ptr),                                                               \
            DebugAllocator *: (Allocator *)(allocator_ptr)                                                                 \
        )
            u64                  bytes_in_use;
            u64                  creator_tid;
        } DebugAllocator;
    
        // Vtable functions, exposed because the Init macro stamps them
        /// `page`-managed pages) and clears the struct.
        ///
        void DebugAllocatorDeinit(DebugAllocator *self);
    
        /// Number of outstanding allocations (alloc minus free).
    
        /// Number of outstanding allocations (alloc minus free).
        size DebugAllocatorLiveCount(const DebugAllocator *self);
        /// Total user-requested bytes still outstanding.
        size DebugAllocatorLiveBytes(const DebugAllocator *self);
        size DebugAllocatorLiveCount(const DebugAllocator *self);
        /// Total user-requested bytes still outstanding.
        size DebugAllocatorLiveBytes(const DebugAllocator *self);
        /// Number of double-free events caught.
        size DebugAllocatorDoubleFrees(const DebugAllocator *self);
        size DebugAllocatorLiveBytes(const DebugAllocator *self);
        /// Number of double-free events caught.
        size DebugAllocatorDoubleFrees(const DebugAllocator *self);
        /// Number of canary-corruption events caught.
        size DebugAllocatorOverflows(const DebugAllocator *self);
        size DebugAllocatorDoubleFrees(const DebugAllocator *self);
        /// Number of canary-corruption events caught.
        size DebugAllocatorOverflows(const DebugAllocator *self);
        /// Append a human-readable leak report to `out`.
        void DebugAllocatorReportLeaks(DebugAllocator *self, Str *out);
        size DebugAllocatorOverflows(const DebugAllocator *self);
        /// Append a human-readable leak report to `out`.
        void DebugAllocatorReportLeaks(DebugAllocator *self, Str *out);
    
    #ifdef __cplusplus
    
    #define DebugAllocatorInitWith(_cfg)                                                                                   \
        ((DebugAllocator) {                                                                                                \
            .base =                                                                                                        \
                {.allocate    = debug_allocator_allocate,                                                                  \
    
    #if MISRA_DEFAULT_ALLOC_DEBUG
        typedef DebugAllocator DefaultAllocator;
    #else
    typedef HeapAllocator DefaultAllocator;
Last updated on