Find C and C++ Bugs During a Build with scan-build-20
scan-build-20 wraps your existing build command and hands back a static-analysis report without touching a single line of your project. You will finish with a repeatable run, a report directory you can inspect, and an exit-status check that tells a broken build apart from a potential bug. The examples use scan-build-20 from Debian package clang-tools-20, version 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes for a first run. You need a working compiler toolchain, the project's normal build command, and enough disk space for reports. The scan is normally unprivileged: do not use sudo just because the build is being analysed, fix file ownership or access to the source tree instead. This guide does not install packages, edit compiler settings or change the project.
1. Confirm the installed command
Check the executable and package version before relying on examples. These are read-only commands:
$ command -v scan-build-20
/usr/bin/scan-build-20
$ dpkg-query -W -f='${Package} ${Version}\n' clang-tools-20
clang-tools-20 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139
$ scan-build-20 --help | sed -n '1,24p'
USAGE: scan-build [options] <build command> [build options]
Do not use --version as a discovery step: the installed script treats it as an unrecognised option, so the package query and help output are the useful local checks. The manpage is labelled Clang 20 and dated August 2024, but the installed package is a later 20.1.8 build. When the two descriptions differ, test the exact option on this installation and prefer the installed help for newly added switches.
Checkpoint
You know which scan-build-20 will run, and the ordinary build succeeds without the wrapper.
2. Run the analyser around the normal build
Put scan-build options first, then the build command and its normal options. This example sends reports to a dedicated directory and passes -j2 to make:
$ scan-build-20 -o /tmp/my-scan-reports make -j2
scan-build: Using '/usr/lib/llvm-20/bin/clang' for static analysis
scan-build: Analysis run complete.
scan-build: No bugs found.
The output location is a parent directory: scan-build creates a new run subdirectory beneath it rather than overwriting the previous run. Without -o, reports go to a temporary directory, usually below /tmp on Linux. Use a project-specific path if results need to survive a reboot or a cleanup job.
- Everything after make belongs to make. For another build system, replace only that part, for example
scan-build-20 -o /tmp/my-scan-reports ninja -C buildifninja -C buildis already your working build command. - Parallel builds are supported, but the manpage warns that distributed builds are not.
- An empty run directory may get removed when the installed command finds no findings. That is not a failed analysis. Add
--keep-emptyonly if it appears in your local help and you need automation to see an empty directory; it is not part of the scan-build-20(1) interface described here.
3. Create a report you can inspect
For an HTML report, use the default output or make it explicit with -o. When a bug is found, the run directory contains an index.html and individual report pages:
$ find /tmp/my-scan-reports -maxdepth 2 -type f -print
/tmp/my-scan-reports/2026-09-26-213901-1234567-1/index.html
/tmp/my-scan-reports/2026-09-26-213901-1234567-1/report-abcdef.html
The timestamp and random-looking suffix vary. Open index.html in a browser, or pass the run directory to scan-view if it is installed. The report describes a possible path through the source; it is not proof the defect is reachable in production. Read the trace, then reproduce the relevant input or state before changing code.
If another tool consumes property-list reports, use -plist instead. To keep both the browser-friendly pages and the plist data, use -plist-html:
$ scan-build-20 -plist-html -o /tmp/my-scan-reports make -j2
scan-build: Analysis results (plist and HTML files) deposited in '/tmp/my-scan-reports/2026-09-26-213901-1234567-1'
Warning
Report files can contain source excerpts and paths. Treat them as project data, and do not upload them to a third party until your review allows it.
4. Make the exit status useful in automation
By default, scan-build returns the status from the build command. A compiler error makes the wrapper fail, but a clean build with a reported potential bug can still return zero. If a pipeline must stop on findings, add --status-bugs:
$ scan-build-20 --status-bugs -o /tmp/my-scan-reports make -j2
$ status=$?
$ printf 'scan-build status: %s\n' "$status"
scan-build status: 1
Status 1 here means scan-build found potential bugs, not that every report is confirmed. With --status-bugs, a finding takes precedence over the build status when choosing the wrapper's result, so preserve the command output in CI and investigate both the build and the report results.
Checkpoint
Decide whether your job should fail on analysis findings. Do not add --status-bugs to a deployment gate until the team has agreed how reports get triaged.
5. Narrow or extend the checker set deliberately
A default group of checkers runs unless you change it. List the checkers on this machine before picking one:
$ scan-build-20 --help-checkers | sed -n '1,18p'
core.BitwiseShift
core.CallAndMessage
core.DivideZero
core.NonNullParamChecker
core.NullDereference
core.StackAddressEscape
Enable a named checker with --enable-checker, or disable one with --disable-checker. For example, this adds a check for insecure uses of gets to an otherwise normal build:
$ scan-build-20 --enable-checker security.insecureAPI.gets \
-o /tmp/my-scan-reports make -j2
Checker names and defaults depend on the installed analyser build and operating system. Copy names from --help-checkers rather than trusting a blog post or a different LLVM release. Enabling more checks can increase runtime and report volume; disabling a checker can hide a class of defect, so record that choice in the build configuration or CI log.
6. Diagnose the common distractions
If no source files are analysed, first confirm the build actually recompiles them. An up-to-date build may do nothing, leaving scan-build with no compiler invocation to intercept. Clean only a disposable build directory, and check the project's documented rebuild command before removing generated files; a clean build is a state-changing operation, so do not run it in a tree with uncommitted generated output you may need.
Use -v when you need to see the compiler commands and analyser activity:
$ scan-build-20 -v -o /tmp/my-scan-reports make -j1 2>scan-build.log
$ sed -n '1,40p' scan-build.log
- A serial build (-j1) is easier to read while diagnosing an interposition problem.
- Generated makefiles that hard-code a compiler need configuring through scan-build too, following the project's normal backup and regeneration process. Do not casually replace
CCorCXXin a shared build environment. - A failures directory in the report may include preprocessed source and crash details worth keeping for a bug report, or use
-no-failure-reportswhen those files are not suitable for the workspace. That option removes diagnostic context, so use it only when a privacy or storage boundary requires it.
Done means
- The installed package and executable were checked before the run.
- scan-build-20 wrapped the project's own build command successfully.
- Reports were saved in a known run directory, in HTML or plist form.
- The automation decision about --status-bugs is explicit.
- Checker names came from this installation's help output.
- Potential findings were reviewed as traces, not accepted blindly as confirmed defects.
- No package, source file, persistent compiler setting or service was changed.