asan: keep MarkedArgumentBuffer inline storage on the real stack

Merged
JA
7mo ago
+30-19

What

Fixes an intermittent UBSan crash in release-asan CI under BUN_GARBAGE_COLLECTOR_LEVEL=1:

Structure.h:1138:25: runtime error: member call on null pointer of type 'JSC::ClassInfo'

seen in test/js/bun/md/md-edge-cases.test.ts (and reproducible deterministically with Bun.markdown.react("a\n\nb") in a loop with Bun.gc(false)).

Why

MarkedArgumentBuffer.wrap allocates a JSC::MarkedArgumentBuffer on the stack inside MarkedArgumentBuffer__run. The first 8 entries live in inline storage that JSC keeps alive via conservative stack scanning. ASAN's detect_stack_use_after_return relocates that local to a heap-backed fake stack the conservative scan does not visit, so values in the inline buffer were unprotected and got collected mid-render.

detect_stack_use_after_return was supposed to be off — src/asan-config.c defined __asan_default_options to disable it — but in CI's split build the C objects are archived into libbun.a, and clang places the ASAN runtime (which already weak-defines that symbol) before user inputs, so an archive member that only provides this symbol is never extracted and the override silently doesn't apply. The disassembled __asan_default_options in CI's release-asan binary returns "".

How

  • SUPPRESS_ASAN on MarkedArgumentBuffer__run keeps args on the real stack regardless of the runtime option (defense in depth — fixes the crash on its own even with detect_stack_use_after_return=1 forced).
  • __asan_default_options moves to src/asan.zig so it lands in bun-zig.o, a direct link input, where the strong definition reliably overrides the runtime's weak default in both full and split builds. src/asan-config.c is removed.

Verification

  • Deterministic repro (looped Markdown.react + Bun.gc(false)): crashed 100% before, passes now — including with detect_stack_use_after_return=1 forced on.
  • md-edge-cases.test.ts under exact CI env (GC level 1, integrity audit 1.0, leak detection on with test/leaksan.supp): 10/10 clean runs.
  • All 1021 markdown tests pass.
  • nm confirms __asan_default_options is exported from bun-zig.o; ASAN verbosity=1 confirms FakeStack is no longer created at startup.