Check C and C++ Files Properly with clang-check-20
clang-check-20 will quietly parse a file with no compiler flags at all and still call it a clean pass. This guide gets you checking syntax, reading the AST and treating the file-changing --fixit option as the risky step it is. The examples use clang-check-20 from Ubuntu LLVM 20.1.8, supplied by 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.
- Time: about fifteen minutes.
- Needs: a shell, the installed package, and source files you are allowed to read.
- Stays read-only except for step 6, where
--fixitedits source and this guide treats it as a backed-up, deliberate step.
1. Confirm the installed tool
Check the executable and version first. These are ordinary commands, no elevated privileges needed:
$ command -v clang-check-20
/usr/bin/clang-check-20
$ clang-check-20 --version
Ubuntu LLVM version 20.1.8
Optimized build.
The version matters when you compare diagnostics or AST output with another machine. Keep the major version aligned with the project's Clang toolchain where possible.
Checkpoint
If the command is missing, stop here. Do not substitute an unverified binary from another directory and assume it has the same behaviour.
2. Check one file without changing it
Pass one source path as the input. This basic invocation builds the translation unit and reports diagnostics:
$ clang-check-20 path/to/source.cpp
Processing path/to/source.cpp.
A clean run can produce only the processing line, or no useful standard output depending on the file and tool invocation. Check the status immediately if you need a scriptable result:
$ clang-check-20 path/to/source.cpp
$ status=$?
$ printf 'clang-check status: %s\n' "$status"
clang-check status: 0
Status 0 is not proof the file was parsed with your project's settings. If clang-check cannot find a compilation database, it warns that it is running without flags and may still return 0. That fallback is the main source of misleading results.
3. Point it at the compilation database
Build projects commonly record each translation unit's directory, compiler and flags in compile_commands.json. With CMake, generate it by configuring the build with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON. Then pass the directory containing that file with -p:
$ cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
$ clang-check-20 -p build src/example.cpp
Processing src/example.cpp.
The CMake command reconfigures the build but does not compile it. It is normally unprivileged; use the permissions already required by your project and do not run the whole build as root merely to make clang-check work.
- Omit
-pand clang-check searches parent directories of the first input file forcompile_commands.json. - An absolute input path must point inside the CMake source tree.
- A relative input path must be beneath the current source-tree directory. A path that exists is not automatically a path the database can match.
Checkpoint
Verify the database is present before debugging a diagnostic:
$ test -s build/compile_commands.json && printf '%s\n' 'compile database found'
compile database found
$ clang-check-20 -p build src/example.cpp
Processing src/example.cpp.
4. Inspect the AST or tokens
Use an output mode that answers one question at a time.
--ast-printpretty-prints declarations after parsing.--ast-dumpemits Clang's debug representation, more verbose but useful when a tool or refactoring needs to distinguish declarations and expressions.--syntax-tree-dumpdumps the syntax tree.--tokens-dumpshows preprocessed and spelled tokens.
$ clang-check-20 -p build --ast-print src/example.cpp
int answer() {
return 42;
}
These modes write large outputs for real projects, so redirect them to a reviewable file rather than flooding a terminal:
$ clang-check-20 -p build --tokens-dump src/example.cpp > /tmp/example.tokens
$ sed -n '1,24p' /tmp/example.tokens
expanded tokens:
int answer ( ) { return 42 ; }
The temporary file is disposable and does not alter the project. If the output looks unexpectedly small or headers are missing, go back to step 3 and check the database and selected command before changing source code.
5. Add one diagnostic flag without rewriting the build
Use --extra-arg to append one compiler argument, or --extra-arg-before to prepend one. This suits a controlled experiment, such as checking a conditional branch:
$ clang-check-20 -p build --extra-arg=-DCLANG_CHECK_EXAMPLE=1 --ast-print src/example.cpp
int answer() {
return 42;
}
These options affect this invocation only. They do not update compile_commands.json or the build system. Keep the added flag obvious and record it in a script if the check must be repeated.
6. Treat fix-it mode as a file change
Warning
--fixit applies fix-it advice to the input source. --fix-what-you-can also applies available fixes when other errors remain. Before either option, save or commit your work and inspect the exact files selected by the command. These options can change source files and are not undoable by clang-check itself.
$ git diff -- path/to/source.cpp
$ clang-check-20 -p build --fixit path/to/source.cpp
$ git diff -- path/to/source.cpp
Review the diff before keeping it.
Recovery
Use your normal version-control restore or editor undo workflow; do not use a broad reset if unrelated work is present. If the fixes are not wanted, restore only the reviewed file from your known-good copy or commit, following your project's recovery process.
Common failure traps
- No compilation database warning. The command is falling back to default parsing. Fix the database path or accept that the result is only a limited experiment.
- Wrong command selected. A valid database can still select the wrong command when the source path does not match its recorded path. Run from the source tree and use the path form recorded by the build.
- Multiple files hide failures. A failure in one file can be lost in a long stream. Start with one file, then expand the command deliberately.
--analyzeis a different question. It runs the static analysis engine, not syntax checking, AST printing or token dumping; use it only when you specifically need that analysis mode.
Done means
- Version confirmed:
clang-check-20 --versionreports the expected toolchain. - Database matched: the selected source path matches an entry in
compile_commands.json. - Clean status: a normal check returns the status you expect without a missing-database warning.
- Question answered: AST or token output answers the debugging question you started with.
- Fix-it reviewed: any fix-it diff was checked, and unwanted changes recovered using your existing version-control workflow.