cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
intercept.h File Reference

Opt-in, translation-unit-scoped malloc/calloc/realloc/free interception onto the full-GC epoch pipeline. More...

#include <stddef.h>
#include <stdlib.h>
Include dependency graph for intercept.h:

Go to the source code of this file.

Functions

void * cwist_malloc_shim (size_t size)
 malloc() replacement: real libc malloc() (uninitialized memory, exactly like malloc - unlike cwist_alloc(), this does not zero), registered with the full-GC pending-sweep list when cwist_full_gc_enabled(); a plain, untracked passthrough otherwise.
 
void * cwist_calloc_shim (size_t nmemb, size_t size)
 calloc() replacement: real libc calloc() (zeroed), registered with the full-GC pending-sweep list when cwist_full_gc_enabled(); a plain, untracked passthrough otherwise.
 
void * cwist_realloc_shim (void *ptr, size_t size)
 realloc() replacement. Tracking follows the incoming pointer: if ptr was tracked (shimmed-and-full-GC-was-on at allocation time), the old registration is removed and the new pointer is re-registered (a moved block must not leave a stale entry pointing at freed memory, and should stay reclaimable exactly as the original was). If ptr was untracked (allocated before full-GC was enabled, already disowned, or is NULL), the result is left untracked too - matching cwist_realloc()'s existing behavior of not opting a pointer into tracking on its own.
 
void cwist_free_shim (void *ptr)
 free() replacement: untracks ptr first if it was tracked (safe no-op if it wasn't - same contract as cwist_free()), then calls the real libc free().
 

Detailed Description

Opt-in, translation-unit-scoped malloc/calloc/realloc/free interception onto the full-GC epoch pipeline.

Design rationale (see docs/GC.md section 5 for the full writeup): this is deliberately a header-level macro redefinition, not a link-level -Wl,--wrap=malloc. A link-level wrap intercepts every malloc reference in the final binary, including vendored dependencies (BoringSSL, lsquic, cnats, sqlite3) that never see CWIST's headers and have no reason to expect a non-libc allocator underneath them - concretely, BoringSSL calls OPENSSL_cleanse() on secret key material before freeing it, on the assumption of plain heap semantics; routing that through an epoch-deferred GC arena would be a security regression, not a performance footnote.

A #define malloc cwist_malloc_shim here only ever takes effect in a translation unit that (a) includes this header and (b) defines CWIST_INTERCEPT_MALLOC before doing so. Vendored dependencies do neither, structurally: they are built as separate compilation units that never include a CWIST header. This excludes them by construction, at the cost of not extending coverage beyond code that opts in.

Usage (typically a handler translation unit that wants "write malloc by habit and it still evaporates under cwist_full_gc(true)"):

#define CWIST_INTERCEPT_MALLOC
#include <cwist/app.h>
static void handle(cwist_http_request *req, cwist_http_response *res) {
char *buf = malloc(256); // -> cwist_malloc_shim() under the hood
...
// no free(buf): under cwist_full_gc(true), this evaporates when
// the current thread's pending-sweep list is flushed/thread exits,
// exactly like a forgotten cwist_alloc() would. free(buf), if
// called, still works normally either way.
}
Top-level application header.
Opt-in, translation-unit-scoped malloc/calloc/realloc/free interception onto the full-GC epoch pipeli...
size_t cap
Definition otel.c:56
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82

When cwist_full_gc(true) was never called (the default), every shim function below falls straight through to the real libc call after one relaxed atomic load (cwist_full_gc_enabled(), the same check cwist_alloc()/cwist_free() already pay on every call) - no tracking, no extra bookkeeping. See tests/bench_malloc_intercept.c for the measured overhead of that fallback path, both with the toggle on and off, against a plain uninterecepted malloc/free baseline.

A pointer that legitimately needs to outlive the thread/scope that allocated it (cached in a connection pool, hand it to a background job) calls cwist_gc_scope_disown(ptr) once at the handoff point, exactly as it would for a cwist_alloc() pointer - see include/cwist/core/mem/gc.h and tests/test_full_gc_ownership_handoff.c. No separate escape API is needed for shimmed pointers.

Definition in file intercept.h.

Function Documentation

◆ cwist_calloc_shim()

void * cwist_calloc_shim ( size_t  nmemb,
size_t  size 
)

calloc() replacement: real libc calloc() (zeroed), registered with the full-GC pending-sweep list when cwist_full_gc_enabled(); a plain, untracked passthrough otherwise.

◆ cwist_free_shim()

void cwist_free_shim ( void *  ptr)

free() replacement: untracks ptr first if it was tracked (safe no-op if it wasn't - same contract as cwist_free()), then calls the real libc free().

◆ cwist_malloc_shim()

void * cwist_malloc_shim ( size_t  size)

malloc() replacement: real libc malloc() (uninitialized memory, exactly like malloc - unlike cwist_alloc(), this does not zero), registered with the full-GC pending-sweep list when cwist_full_gc_enabled(); a plain, untracked passthrough otherwise.

◆ cwist_realloc_shim()

void * cwist_realloc_shim ( void *  ptr,
size_t  size 
)

realloc() replacement. Tracking follows the incoming pointer: if ptr was tracked (shimmed-and-full-GC-was-on at allocation time), the old registration is removed and the new pointer is re-registered (a moved block must not leave a stale entry pointing at freed memory, and should stay reclaimable exactly as the original was). If ptr was untracked (allocated before full-GC was enabled, already disowned, or is NULL), the result is left untracked too - matching cwist_realloc()'s existing behavior of not opting a pointer into tracking on its own.