SOTA C & C++ (2026)
Expert-level rules for producing and auditing production C and C++. C and C++
are memory-unsafe by default: the compiler will not stop you from reading
freed memory, overrunning a buffer, or invoking undefined behavior (UB) that
the optimizer then weaponizes. These rules exist to claw back the safety the
language doesn't give you — through RAII, the type system, sanitizers, hardened
build flags, and disciplined review. Baseline: C++23 (ISO/IEC 14882:2024) and
C17/C23; flag where a control needs a newer toolchain. C++26 is feature-complete
(DIS ballot 2026) — contracts, reflection, erroneous behavior for uninitialized
reads, and a standardized hardened standard library; the last is usable today
via libc++/libstdc++ hardening flags (rules/02). Every rule states the
why; every rules file ends with an audit checklist of grep/clang-tidy/
sanitizer patterns.
Purpose
Two consumers, one source of truth:
- BUILD mode — generating new C/C++: follow the rules as defaults, not suggestions. Prefer C++ with RAII over raw C idioms unless the target is C. Deviate only with a comment justifying it.
- AUDIT mode — reviewing existing C/C++: hunt violations using the audit checklists, classify by severity, report in the finding format below. Memory- safety and UB findings are presumed exploitable until proven otherwise.
BUILD mode
- Before writing, read the rules files relevant to the task (see index). A
parser handling untrusted bytes needs
02,03,04; a threaded service needs05. - Apply the top-10 non-negotiables (below) unconditionally.
- New projects: CMake (≥3.20) with
-Wall -Wextra -Wpedantic -Werror, the OpenSSF hardening flags (rules/04), a debug build wired to ASan+UBSan, clang-tidy + clang-format configs, and CI running all of it from day one (rules/06). - Prefer the standard library and RAII types over hand-rolled
allocation/ownership. Every
new/malloc/fopen/mutex.lock()should be owned by a destructor (unique_ptr, container,lock_guard), not a manual matching call you can forget on an early return or exception. - Treat warnings as errors. A clean
-Wall -Wextrabuild is the floor, not the goal — also run a static analyzer and the sanitizers (rules/06). - When you must use a sharp tool (raw pointer arithmetic,
reinterpret_cast,unsafeC interop, manual lifetime), leave a// NOTE(sota):comment explaining the invariant you're upholding so auditors don't flag it blind.
AUDIT mode
Work through each relevant rules file's audit checklist against the target.
Run the listed grep/clang-tidy/sanitizer commands; confirm each hit manually
(greps are recall-oriented). Where feasible, build with -fsanitize=address, undefined and run the test suite — a sanitizer abort is ground truth.
Severity conventions
| Severity | Meaning | Examples |
|---|---|---|
| CRITICAL | Exploitable memory corruption or guaranteed UB on reachable input | Heap/stack buffer overflow on attacker data, use-after-free, double-free, OOB write, format-string with user-controlled fmt, system() with interpolated input, data race on a pointer |
| HIGH | Likely corruption, crash, or security weakness | Unchecked malloc/new size from input, integer overflow feeding an allocation or index, missing bounds check, strcpy/sprintf/gets, TOCTOU on a path, missing RAII so a leak/UB occurs on the exception path |
| MEDIUM | Correctness/maintainability hazard, latent bug | Raw owning pointers, manual new/delete pairs, C-style casts, narrowing conversions, memcpy where a typed copy fits, missing override/= delete, signed/unsigned comparison |
| LOW | Idiom/perf debt, works but wrong shape | Pass-by-value of large objects, needless copies instead of std::move, using namespace std in headers, macros where constexpr/inline fits |
| INFO | Style/doc/hygiene | clang-format drift, naming, missing [[nodiscard]], include hygiene |
Finding format
[SEVERITY] file.cpp:LINE — short title
Rule: rules/NN-name.md § section
Evidence: the offending line(s), verbatim
Impact: one sentence — what corrupts/leaks/races, under what input
Fix: concrete replacement code or action
Effort: trivial | small | medium | large
Group findings by severity, CRITICAL first. End with: counts per severity, the three highest-leverage fixes, and which checklists/sanitizers were run.
Rules index
| File | Read this when... |
|---|---|
rules/01-idioms.md |
Writing/reviewing any C++: RAII and the rule of zero/five, ownership with unique_ptr/shared_ptr, value semantics and move, const/constexpr, references vs pointers, casts, enum class, error handling (exceptions vs std::expected vs error codes), in-band sentinels (absence encoded as -1/0/"") incl. EOF in a char — broken only where char is unsigned, C-vs-C++ idiom choices |
rules/02-memory-safety.md |
Anything touching pointers, buffers, lifetimes, or allocation: bounds, use-after-free/return, dangling references and views (string_view/span), iterator invalidation, ownership discipline, sanitizers (ASan/MSan), _FORTIFY_SOURCE/_GLIBCXX_ASSERTIONS |
rules/03-undefined-behavior.md |
Reasoning about UB and the optimizer: integer overflow, strict aliasing, uninitialized reads, null/misaligned access, signed shifts, data races as UB, unsigned arithmetic, UBSan, why "it worked in debug" proves nothing |
rules/04-security.md |
Any input crossing a trust boundary: CERT C/C++ + MISRA, banned functions (gets/strcpy/sprintf/system), integer-overflow-to-allocation, format strings, path traversal/TOCTOU, command injection, deserialization/parsers, CSPRNG, the OpenSSF hardening flag set |
rules/05-concurrency.md |
Anything with threads, atomics, or shared state: the C++ memory model, data races, std::atomic and memory orders, mutex/lock_guard/scoped_lock, deadlock ordering, condition variables, std::jthread/stop tokens, TSan |
rules/06-build-tooling-ci.md |
Setting up or auditing builds/CI: CMake hygiene, warning flags, clang-tidy/clang-format, static analysis (clang-analyzer, cppcheck, Coverity), sanitizer CI matrix, fuzzing (libFuzzer/OSS-Fuzz), dependencies and supply chain (vcpkg/Conan, pinning, SBOM). Test strategy lives in sota-testing; this file owns C/C++ build/test mechanics. |
rules/07-performance.md |
Latency/throughput/memory work: profiling (perf, VTune, Callgrind), allocation reduction and custom allocators, move/copy elision, cache locality and data-oriented layout, <algorithm> over hand loops, LTO/PGO, micro-benchmarking pitfalls |
Top-10 non-negotiables
- Every resource is owned by a destructor (RAII). No
new/deleteormalloc/freepairs you have to match by hand; no bare owning pointers. Useunique_ptr, containers,lock_guard/scoped_lock, RAII wrappers. A leak or UB on the exception/early-return path is the default failure mode of manual cleanup. (rules/01,rules/02) - No buffer touches memory it doesn't own. Bounds-check every index/
length derived from input; use
std::span/std::string/containers and.at()or explicit checks, never raw pointer + length you assume. Overflow on attacker input is CRITICAL. (rules/02) - No use-after-free / dangling. A pointer, reference, iterator,
string_view, orspanmust not outlive its storage. Never return a reference/view to a local or to a temporary. (rules/02) - Undefined behavior is a bug even if it "works". Signed integer
overflow, strict-aliasing violations, uninitialized reads, OOB, data races
are UB the optimizer may exploit. Build with UBSan; treat any UBSan
diagnostic as CRITICAL/HIGH. (
rules/03) - Integers feeding an allocation, index, or
memcpysize are overflow-checked and the right signedness. Validate ranges before use; prefer unsigned for sizes, check for wrap. Overflow-to-undersize-alloc is a classic RCE primitive. (rules/03,rules/04) - Banned functions are banned. No
gets,strcpy/strcat/sprintf(use bounded forms orstd::string/std::format), nosystem()with interpolated input (useposix_spawn/exec*with an argv array). (rules/04) - Build hardened, by default.
-Wall -Wextra -Werrorplus the OpenSSF set (-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -fstack-protector-strong -fstack-clash-protection -fcf-protection -Wl,-z,relro,-z,now). Missing hardening on a network-facing binary is a HIGH finding. (rules/04,rules/06) - Shared mutable state is synchronized; data races are CRITICAL. Guard
with a
mutex/scoped_lockor usestd::atomicwith a justified memory order. A-fsanitize=threadfailure is not flaky noise. (rules/05) - Sanitizers and a static analyzer gate CI. A debug/test job runs
ASan+UBSan (and TSan for threaded code); clang-tidy + cppcheck run on every
PR. Untrusted-input parsers get a fuzz target. (
rules/06) - Prefer the type system to convention.
enum classover macros,constexpr/inlineover#define,gsl::span/std::spanover pointer+ length,[[nodiscard]]on must-check returns,expliciton single-arg constructors,override/final. Make misuse fail to compile. (rules/01)