libc++ has four hardening modes: none, fast, extensive and debug. The LLVM documentation intends fast mode for production, with low constant time overhead, extensive for cases you have benchmarked, and debug for testing only. Fast mode turns out-of-bounds element access in standard containers into an immediate trap.
Choosing the mode
You set it at compile time:
clang++ -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST app.cpp
The other values are _LIBCPP_HARDENING_MODE_NONE, _LIBCPP_HARDENING_MODE_EXTENSIVE and _LIBCPP_HARDENING_MODE_DEBUG. Pass only one. Library vendors choose the default, and the vendor default is none, meaning no checks.
What each mode adds
- None. Adds nothing and has no overhead.
- Fast. Adds valid element access and valid input range checks. Intended for production, with low constant time cost.
- Extensive. Adds to fast checks such as non-null pointers, non-overlapping ranges, valid deallocation and argument domain. Benchmark before production.
- Debug. Adds to extensive the semantic requirement and internal checks. For testing and development only.
Each mode is currently a superset of the one before it, though the library may change which assertions each mode enables.
What failure looks like
Fast and extensive modes trap at once with no message. Debug mode terminates and prints the error message. That difference matters for support: a production trap needs a crash report pipeline, and a debug build is where you find out which assertion fired.
Compatibility notes
The documentation says choosing a hardening mode does not affect the ABI. Some checks, such as bounds checking on dereferencing a span iterator, depend on ABI options that the vendor sets when building the library, and those can change class layout. Because the compiled library comes from your vendor, your macro mainly affects header based code.
A rollout path
- Build tests with debug mode and fix every assertion.
- Switch CI to extensive and measure runtime.
- Ship fast mode in production builds of network facing components.
- Revisit extensive after benchmarking real workloads.
Where it helps and where it does not
It adds checks to standard library calls, so it catches a bad index into a vector or a mismatched input range. It does not see raw pointer arithmetic in your own code, or lifetime bugs such as a dangling reference. The OpenSSF guide also lists the fast mode macro among its core flags.
Testing the hardening itself
Write a small test that indexes one past the end of a vector in a throwaway build, and confirm the program traps in fast mode instead of reading garbage. If it does not trap, your build is not using the mode you think it is. The ABI tag the documentation describes encodes the mode in its first character, which can help you confirm what was compiled, though the documentation says the tag format is not stable across releases.
Also decide what trapping means for your service. A fast mode trap ends the process with no message. That is the right behavior for an exposed parser, as long as your supervisor restarts it and your crash reporter captures the stack. If you have no crash pipeline yet, build that first, otherwise you will trade a hidden memory bug for an unexplained outage.
Quick check
Is hardening mode none the vendor default?
Yes, according to the libc++ documentation, so you have to opt in.
Sources
Last reviewed: 19 October 2026.