Yeda AI Tips · #068

Español

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.

SanitizerFlagCatchesTypical slowdown
AddressSanitizer (ASan)-fsanitize=addressHeap/stack/global out-of-bounds, use-after-free, use-after-return, use-after-scope, double-free, invalid free, leaks (experimental)~2x
UndefinedBehaviorSanitizer (UBSan)-fsanitize=undefinedSigned integer overflow, invalid shifts, null/misaligned dereference, out-of-bounds array subscripts, division by zeroLow
ThreadSanitizer (TSan)-fsanitize=threadData 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

Resources

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.

Talk to us · Read the blog