Home / Alt manpages / clang-query-20(1)

  • clang-query-20(1)
  • User command
  • linux

Find C++ AST Matches with clang-query 20

You will finish with a small, repeatable clang-query 20 check that finds C++ declarations from a compilation database. The examples use the installed Ubuntu LLVM build, which reports version 20.1.8, from package clang-tools-20 version 20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139.

Allow about fifteen minutes. You need clang-query 20, CMake, a C++ source tree and permission to read its build directory. This workflow only reads source and compiler commands. It does not edit code, rebuild the project or change system configuration. The examples use a temporary project so that you can copy them without risking an existing checkout.

1. Confirm the installed command

Start with ordinary, read-only checks. They do not need elevated privileges:

$ command -v clang-query-20
/usr/bin/clang-query-20
$ clang-query-20 --version
Ubuntu LLVM version 20.1.8
  Optimized build.

The command name is versioned here. Use clang-query-20 in the rest of this guide, even if another clang-query binary is also installed. The option contract is available locally:

$ clang-query-20 --help | sed -n '1,35p'
USAGE: clang-query-20 [options] <source0> [... <sourceN>]

... -p <string>   - Build path
... -c <command> - Specify command to run

Checkpoint: if the version command fails, stop here and install or select the matching LLVM tools package through your normal package-management process. Do not diagnose an AST query until the executable you are invoking is clear.

2. Create a tiny source tree

For a clean smoke test, make a temporary directory and a source file:

$ WORK_DIR="$(mktemp -d)"
$ mkdir "$WORK_DIR/src" "$WORK_DIR/build"
$ cat > "$WORK_DIR/src/example.cpp" <<'EOF'
int add(int left, int right) {
  return left + right;
}

int main() {
  return add(2, 3);
}
EOF

This creates state only under /tmp. When you have finished, remove that one temporary directory with rm -rf -- "$WORK_DIR". Check the variable before running that command: deletion is irreversible. If you use an existing checkout instead, skip this step and keep its source and build paths unchanged.

3. Generate the compile command database

clang-query needs the compiler arguments for each source file. CMake writes them to compile_commands.json when you enable export:

$ cat > "$WORK_DIR/CMakeLists.txt" <<'EOF'
cmake_minimum_required(VERSION 3.16)
project(query_example LANGUAGES CXX)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_executable(query_example src/example.cpp)
EOF
$ cmake -S "$WORK_DIR" -B "$WORK_DIR/build"
$ test -s "$WORK_DIR/build/compile_commands.json"
$ printf 'compile database: %s\n' "$WORK_DIR/build/compile_commands.json"
compile database: /tmp/.../build/compile_commands.json

The shortened path in the last line is illustrative. The test command is the useful checkpoint: it succeeds only when the database exists and is non-empty. No sudo is needed for this temporary project.

4. Run one matcher against the source

Use -p for the build directory, then pass the source path. The -c option runs one clang-query command and exits:

$ clang-query-20 -p "$WORK_DIR/build" -c 'match functionDecl()' "$WORK_DIR/src/example.cpp"
Match #1:
.../src/example.cpp:1:1: note: "root" binds here
    1 | int add(int left, int right) {

Match #2:
.../src/example.cpp:5:1: note: "root" binds here
    5 | int main() {
2 matches.

The directory prefix, source excerpts and marker lengths vary. The stable result is two function declarations, add and main. clang-query is matching the parsed abstract syntax tree, not searching the file as text.

To make the result more specific, match only named functions and bind the declaration as f:

$ clang-query-20 -p "$WORK_DIR/build" -c 'match functionDecl(isDefinition(), hasName("add")).bind("f")' "$WORK_DIR/src/example.cpp"
Match #1:
  ... functionDecl ... add ...

Keep the whole query in single quotes so the shell passes parentheses and double quotes to clang-query unchanged. The matcher syntax belongs to clang-query, while the shell still processes quoting before clang-query sees it.

5. Use the interactive mode when a query needs iteration

Omit -c to enter the interactive prompt. This is useful when you want to try several matchers against the same input:

$ clang-query-20 -p "$WORK_DIR/build" "$WORK_DIR/src/example.cpp"
match functionDecl(hasName("main"))
Match #1:
.../src/example.cpp:5:1: note: "root" binds here
    5 | int main() {
quit

In a terminal you can enter those commands at the interactive prompt. The exact prompt and AST formatting can vary between builds. Type help at the prompt if you need the available clang-query commands. For a repeatable script or a saved review note, prefer -c or a command file.

6. Put repeatable commands in a file

The -f option reads commands from a file. Create one with a single matcher:

$ cat > "$WORK_DIR/queries.txt" <<'EOF'
match functionDecl(isDefinition(), hasName("main"))
EOF
$ clang-query-20 -p "$WORK_DIR/build" -f "$WORK_DIR/queries.txt" "$WORK_DIR/src/example.cpp"
Match #1:
  ... main ...

Use --preload when you want to read commands from a file and then remain in interactive mode. That option is different from -f, which is the better fit for a command that should finish without input.

7. Diagnose empty or failed results

An empty match is not automatically a broken tool. First check the matcher against the source you actually supplied. A name matcher is exact, so hasName("Main") will not match the lower-case main above.

Next check the compilation database and path relationship. With -p, clang-query reads compile_commands.json from that build path. Without -p, it searches parent directories of the first input file. That search is convenient but easy to misunderstand when several build trees exist.

Relative source paths have another constraint: the current directory must be inside the CMake source tree, and the path must correspond to a suffix in the database. An absolute path must point into that CMake source tree. If a path is rejected, use the exact source path recorded in compile_commands.json or rerun from the intended source tree. Do not fix a path problem by adding unrelated compiler flags.

If the project needs a flag that is not in its compile commands, add it explicitly. --extra-arg appends a compiler argument; --extra-arg-before prepends one:

$ clang-query-20 -p "$WORK_DIR/build" \
    --extra-arg=-Wno-unused-command-line-argument \
    -c 'match functionDecl()' "$WORK_DIR/src/example.cpp"

Use these only for a known compatibility need. They alter how the input is parsed for this query, so record them with the query rather than treating a changed result as comparable to the default run.

Done means

  • clang-query-20 --version reports the installed LLVM 20 build.
  • The selected build path contains a usable compile_commands.json.
  • A command run with -c finds the expected declarations.
  • You know when to use -f, --preload and interactive mode.
  • Empty results are checked against the source path, database and exact matcher.
  • Any temporary directory is removed only after checking its path.