Run Your Native Test Suite Under Sanitizers
For C and C++, chasing line coverage misses the bugs that actually crash you. Run your suite under sanitizers instead. Line coverage tells you a line ran — it says nothing about the use-after-free, the signed overflow, or the data race hiding on that exact line. Sanitizers instrument the runtime, so the same tests you already have start failing on the bugs that matter.
Why line coverage lies to native code
A coverage report answers one question: did this line execute? In managed languages that's a decent proxy for "was this behavior checked." In C and C++ it isn't, because the worst failure modes are silent at execution time: an out-of-bounds read returns garbage that happens to look fine, a use-after-free reads stale-but-plausible memory, undefined behavior gets optimized into something that only misbehaves at -O2, and a data race passes 999 runs out of 1,000. All of those lines are green in the coverage report. The bug ships anyway.
The three sanitizers and what each one buys you
Each sanitizer is a compile-and-link flag plus an instrumented runtime. Same tests, no new assertions.
| Sanitizer | Flag | Catches | Typical slowdown |
|---|---|---|---|
| AddressSanitizer (ASan) | -fsanitize=address | Heap/stack/global out-of-bounds, use-after-free, use-after-return, use-after-scope, double-free, invalid free, leaks (experimental) | ~2x |
| UndefinedBehaviorSanitizer (UBSan) | -fsanitize=undefined | Signed integer overflow, invalid shifts, null/misaligned dereference, out-of-bounds array subscripts, division by zero | Low |
| ThreadSanitizer (TSan) | -fsanitize=thread | Data races | ~5x–15x (5x–10x memory) |
Getting started is one rebuild of your test binaries:
# ASan + UBSan combine in a single build:
clang++ -fsanitize=address,undefined -fno-sanitize-recover=all \
-g -O1 -fno-omit-frame-pointer tests.cc -o tests_asan
./tests_asan
# TSan is a separate build — it can't share a binary with ASan:
clang++ -fsanitize=thread -g -O1 tests.cc -o tests_tsan
./tests_tsan
Three details that matter: use the compiler driver (clang++), not ld, to link so the sanitizer runtimes come along; add -g -fno-omit-frame-pointer so reports have usable stack traces; and pass -fno-sanitize-recover=all so UBSan exits on the first finding instead of printing and continuing — in CI, a warning nobody reads is a bug nobody fixes.
What this catches that coverage never will
Sanitized runs surface use-after-free, out-of-bounds reads, signed overflows, and races that a fully green coverage dashboard would happily ship. And the finds compound: every sanitizer report comes with a stack trace pointing at a reproducible defect. Turn each one into a permanent regression test, and your suite gets stronger with every catch. For native code, a suite that runs clean under ASan, UBSan, and TSan is often a stronger signal than another five points of line coverage.
Power-user moves
- Run sanitizer jobs in CI as separate matrix entries. One ASan+UBSan job, one TSan job. At ~2x slowdown, ASan is cheap enough for every pull request; TSan can run nightly if your suite is long.
- Tune behavior at runtime with environment variables —
ASAN_OPTIONS,UBSAN_OPTIONS,TSAN_OPTIONS— e.g.ASAN_OPTIONS=halt_on_error=1orUBSAN_OPTIONS=print_stacktrace=1. No rebuild needed. - Suppress third-party noise, not your own bugs. All three sanitizers support suppression files for reports in code you don't own. Keep the file in the repo and reviewed — an unreviewed suppression list quietly becomes an ignore-everything list.
- Both major compilers speak these flags. GCC and Clang both support
-fsanitize=address,-fsanitize=undefined, and-fsanitize=thread, so this works whatever your toolchain. - Point your AI coding agent at the sanitizer output. A sanitizer stack trace is exactly the kind of precise, reproducible failure signal that lets an agent fix the real bug instead of guessing.
Resources
- AddressSanitizer — Clang documentation
- UndefinedBehaviorSanitizer — Clang documentation
- ThreadSanitizer — Clang documentation
- AddressSanitizer wiki — google/sanitizers
- Program Instrumentation Options (
-fsanitize=) — GCC manual
Building or hardening a native codebase with AI? Yeda AI designs, audits, and ships production AI-assisted engineering workflows. This article accompanies reel #068 of the Yeda AI Tips series.