Skip to main content

The Sanitizer Mindset

advanced11 min readLesson 177 of 204

ASan, UBSan, TSan and static analysis: what each catches, when to run them, and how to design code so sanitizer findings become ordinary test failures.

The four detectors

  • AddressSanitizer (-fsanitize=address) — heap/stack buffer overflows, use-after-free, use-after-return. Run in every debug test run.
  • UndefinedBehaviorSanitizer (-fsanitize=undefined) — signed overflow, misaligned pointers, invalid enum values, null dereferences (most of them).
  • ThreadSanitizer (-fsanitize=thread) — data races via happens-before analysis. Slower (5–15x) but catches bugs no test output can.
  • Static analysis (-fanalyzer, clang-tidy) — finds null-deref and lifetime paths at compile time, no execution needed.

The CI shape

Debug builds: ASan+UBSan on, fast tests only. Nightly: TSan run of concurrency tests. Release: neither. A finding is a bug — never "probably fine". That policy only works if tests actually execute the risky paths, which is why sanitizer runs pair with coverage thinking.

Designing for sanitizer-friendliness

  • Prefer std::vector + at() in debug paths over raw pointers + manual bounds.
  • Every allocation owner is a type (RAII) — ASan then has no leaks to report.
  • Deterministic tests beat random stress for CI (TSan is the exception: it wants concurrency, run it on the race-prone suites).
  • Fix order: real UB first, then warnings, then style. Do not teach the team to ignore reports.

The graded exercises here simulate the outcome: functions that would trip ASan/UBSan fail their tests by construction (checked vs unchecked implementations diverge on adversarial input).