Static analysis
Select, run, and triage C/C++ static analysis: clang-tidy, cppcheck, and scan-build. Grounded against LLVM/Clang 23.1.0 and cppcheck 2.21.0.
Contract
| Field | Bound contract |
|---|---|
| Trigger | The task runs or configures clang-tidy, cppcheck, or scan-build, interprets check categories, suppresses false positives, generates compile_commands.json, or wires static analysis into CI. |
| Authority | Reversible local: writes only .clang-tidy, suppression comments, source files (through -fix), and analysis reports; rollback is version control. No remote mutation. |
| Side effect | clang-tidy -fix edits source files in place; analyzers read the compilation database. |
| Done | The analyzer runs over the intended translation units and findings are triaged into fixed, suppressed with reason, or reported. |
Inputs
- A C/C++ project (required) and its build system.
- A compilation database (required for clang-tidy):
compile_commands.json. - Check policy (optional): which categories to enable and which warnings are errors.
Procedure
- Generate
compile_commands.json. Done when: the database covers the sources to analyze.
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
ln -s build/compile_commands.json .
# Make-based trees
bear -- make
# or: pip install compiledb && compiledb make
- Run clang-tidy. Done when: diagnostics cover the intended files.
clang-tidy src/foo.c -- -std=c23 -I include/
run-clang-tidy -p build/ -j"$(nproc)"
clang-tidy -checks='bugprone-*,modernize-*,performance-*' src/foo.cpp
clang-tidy -checks='modernize-use-nullptr' -fix src/foo.cpp
- Pick check categories by goal. Done when: the enabled set matches the stated goal.
| Goal | Categories |
|---|---|
| Find real bugs | bugprone-*, clang-analyzer-* |
| Modernize C++ | modernize-* |
| C++ Core Guidelines | cppcoreguidelines-* |
| Performance | performance-* |
| Security hardening | cert-*, hicpp-* |
| Readability and style | readability-* |
- Commit a
.clang-tidyat the project root so every run uses the same policy. Done when: the config file parses andclang-tidypicks it up.
Checks: >
bugprone-*,
modernize-*,
performance-*,
-modernize-use-trailing-return-type,
-bugprone-easily-swappable-parameters
WarningsAsErrors: 'bugprone-*,clang-analyzer-*'
HeaderFilterRegex: '^(src|include)/.*'
CheckOptions:
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: readability-identifier-naming.VariableCase
value: camelCase
- Suppress false positives with NOLINT comments; clang-tidy has no attribute-based suppression. Done when: each suppression names its check and carries a reason.
int result = riskyOp(); // NOLINT(bugprone-signed-char-misuse) - hardware register
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
constexpr int BUFFER_SIZE = 4096;
// NOLINTBEGIN(readability-*)
...legacy block...
// NOLINTEND(readability-*)
Exclude third-party trees with HeaderFilterRegex rather than NOLINT.
- Run cppcheck as a second, independent analyzer. Done when: cppcheck runs with an explicit enable set and exit code.
cppcheck --enable=warning,performance,portability \
--suppress=missingIncludeSystem \
--error-exitcode=1 \
--std=c23 \
src/
cppcheck --project=build/compile_commands.json
cppcheck --xml --xml-version=2 src/ 2> cppcheck-report.xml
--enable values: warning (undefined behavior, bad practice), performance, portability, information, or all.
- Run scan-build for path-sensitive analysis. It wraps a build and drives the Clang static analyzer, which tracks execution paths across functions: use-after-free, dead stores, null dereferences on complex paths. Done when: the HTML report is generated and triaged.
scan-build make
scan-build -o /tmp/scan-out cmake --build build/
scan-view /tmp/scan-out/*/
# Enable a specific checker; list all with clang -cc1 -analyzer-checker-help
scan-build -enable-checker security.insecureAPI.gets make
- Wire analysis into CI as a gate. Done when: the CI job fails on new warnings.
- name: Static analysis
run: |
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
run-clang-tidy -p build -j"$(nproc)" -warnings-as-errors '*'
- name: cppcheck
run: |
cppcheck --enable=warning,performance \
--suppress=missingIncludeSystem \
--error-exitcode=1 \
src/
For per-check details see references/clang-tidy-checks.md.
Failure and recovery
- clang-tidy finds no compilation database: generate
compile_commands.jsonfirst; without it flags and include paths are guesses. - Fix-its break the build: revert the file; apply
-fixper check, not across the whole suite at once. - Warning storm hides real bugs: enable one category at a time, fix or suppress, then widen.
- NOLINT on a third-party file: exclude the path with
HeaderFilterRegexinstead. - scan-build reports nothing: it only sees what the wrapped build compiles; force a full rebuild with
make -Bor a clean build directory. - cppcheck
missingIncludeSystemnoise: the flag suppresses it; deeper fixes need--includes-fileor-Iflags.
Output
A .clang-tidy or cppcheck configuration encoding the agreed check policy, a triaged findings list (fixed, suppressed with reason, or open), and a CI step that gates on new warnings.