Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix F: AddressSanitizer and UndefinedBehaviorSanitizer

This appendix teaches two runtime sanitizers that the book treats as members of the compiler committee. The address sanitizer (ASan) and the undefined‑behavior sanitizer (UBSan) detect errors while a program runs. They are part of the Nix development shell that flake.nix provides.

What ASan instruments

ASan instruments memory access. It detects a heap‑buffer‑overflow, which is a read or write past the end of a heap allocation. It detects a use‑after‑free, which is an access to memory that the program already released. It detects a memory leak, which is an allocation that the program never frees. ASan replaces the allocator and tracks every allocation and free. It checks each memory access against that bookkeeping.

The check happens at runtime, so the program must run to trigger a report. A program that never reaches the bad access stays silent. For this reason the book runs every example under the sanitizers, so a latent bug in an example surfaces during the test run.

What UBSan instruments

UBSan instruments operations whose behavior the standard leaves undefined. It reports signed integer overflow, which is an arithmetic result outside the representable range of a signed type. It reports shift overflow, which is a shift by a negative amount or by more than the width of the type. It reports a null‑pointer dereference, which is an access through a null pointer. UBSan inserts a runtime check before each offending operation and aborts as soon as the operation occurs.

UBSan and ASan complement each other. ASan catches memory errors. UBSan catches arithmetic and type errors. The book enables both together, so a single test run covers both classes of defect.

The flags

The sanitizers are enabled with compile and link flags. The compile flags instrument the code. The link flags attach the sanitizer runtime. The book uses both for each example:

-fsanitize=address -fsanitize=undefined

The same flags appear on the compile line and the link line. The address sanitizer and the undefined‑behavior sanitizer combine under one -fsanitize option. A thread sanitizer also exists, but on this toolchain it is Linux‑only. The book does not use it.

How BOOK_SANITIZE turns them on

The option BOOK_SANITIZE controls the sanitizers. It defaults to ON in the root CMakeLists.txt:

option(BOOK_SANITIZE "Run example tests under AddressSanitizer and UndefinedBehaviorSanitizer" ON)

The book_example function in cmake/BookExample.cmake applies the flags when the option is set:

if(BOOK_SANITIZE)
    target_compile_options(${t} PRIVATE -fsanitize=address -fsanitize=undefined)
    target_link_options(${t} PRIVATE -fsanitize=address -fsanitize=undefined)
endif()

Only book_example targets receive the sanitizer flags. The book_demo and book_gap targets do not. The sanitizers run under CTest, so a sanitizer report fails the test.

Runtime cost

The instrumentation stays in the finished binary. ASan roughly doubles runtime and memory use in typical programs. UBSan adds a check before each instrumented operation. For this reason the sanitizer builds are a test configuration, not the shipping binary. The book keeps the instrumented binaries in the test config only. The dev preset builds every example, and CTest runs each one under the sanitizers. The same source, built without BOOK_SANITIZE, produces the ordinary binary.

Reading an ASan report

The example ch01_overflow reads past the end of a vector to trigger a report. The report begins with a line that names the error class:

==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000000fc
READ of size 4 at 0x6020000000fc thread T0
    #0 ... std::__1::__format::__create_format_arg ... format_arg_store.h:191
    ...
    #8 ... in main ch01_overflow.cpp:13

The first line names the error and the address. The second line describes the access, the size, and the thread. The stack trace follows. Each frame shows a call site. The final frame points into main at the source line of the offending read. The trace lets you walk from the top‑level call down to the exact statement that violated the rule.

Part of the Nix dev shell

The sanitizers are part of the Nix development shell. The file flake.nix provides the pinned toolchain, which includes LLVM 22.1.8. The shell sets CC=clang and CXX=clang++. The clang compiler ships the sanitizer runtimes, so the flags work without extra installation. Running nix develop and then the verify script builds every example and runs every test under the sanitizers.

The sanitizers are a test‑only configuration. They are not part of the shipping binary. The book enables them for the acceptance pipeline so that a memory error or an undefined operation in any example fails the build.

Try this

Write a small program that allocates an array with new[], reads one element past the end, and prints the value. Compile it with -fsanitize=address,undefined and run it. Confirm that ASan reports a heap‑buffer‑overflow with a stack trace that names your source line. Then write a program that adds two signed integers whose sum overflows, and confirm that UBSan reports the overflow while ASan stays silent.