Chapter 28 — One Race, Two Detectors; One Real Use-After-Free
The same data race is checked under ThreadSanitizer and Valgrind Helgrind, two structurally different detectors, and a real heap-use-after-free is caught by AddressSanitizer.
The full source for this example lives in
examples/concurrency/28-sanitizers/— clone the repo,cdin, and./demo.sh.
Chapter reference: Doc 28 — Sanitizers and Dynamic Analysis
What it shows
The same racy, non-atomic counter from chapter 27 is built once plain, for Valgrind Helgrind to run against, and once with -fsanitize=thread; both detectors must report the race at the same increment() call site in racy_counter.cpp, and both must stay clean against the atomic --mode fixed build — two structurally different algorithms agreeing the bug is real, not an artifact of either tool’s model. A separate program hands a raw pointer to a detached thread and frees the buffer on the main thread before the worker reads through it; AddressSanitizer must report the heap-use-after-free with both the allocating and freeing call stacks, and must never let the process reach its own completion print. Each sanitizer leg is a hard check whenever the environment lets it run, and a documented soft skip when a shadow-memory reservation or Valgrind’s own startup fails under a restrictive container.
Running it
cd examples/concurrency/28-sanitizers
./demo.sh # build + run (TSan + Helgrind + ASan checks) + verify.lua
./demo.sh --clean # remove the image