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

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

Find C++ AST Matches with clang-query 20

clang-query 20 searches the parsed syntax tree of your C++ code, not the text of the file, so a grep-shaped guess about matches will mislead you. This guide builds a small, repeatable check that finds 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.

  • Time: about fifteen minutes.
  • Needs: clang-query 20, CMake, a C++ source tree and permission to read its build directory.
  • Read-only: 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 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 throughout, 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 interactive mode when a query needs iteration

Omit -c to enter the interactive prompt, 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 for 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 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.

  • 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.
  • 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, convenient but easy to misunderstand when several build trees exist.
  • Check the source path constraint. A relative source path needs the current directory 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

  • Version confirmed: clang-query-20 --version reports the installed LLVM 20 build.
  • Database ready: the selected build path contains a usable compile_commands.json.
  • Match found: a command run with -c finds the expected declarations.
  • Modes understood: you know when to use -f, --preload and interactive mode.
  • Empty results checked: against the source path, database and exact matcher.
  • Cleanup done: any temporary directory is removed only after checking its path.