Clang-Tidy
clang-tidy is a static analysis tool that detects common programming errors, enforces coding standards, and suggests modern C++ improvements.
It runs as part of CI on pull requests targeting main. CI checks eligible changed C/C++ files with --warnings-as-errors='*', so findings fail the clang-tidy job. The workflow currently describes this job as non-required.
This guide explains how to run clang-tidy locally so you can fix issues before pushing.
What clang-tidy checks
FoundationDB configures 55 named checks in the .clang-tidy file at the repository root. The
active set depends on the clang-tidy version and can be inspected with clang-tidy --list-checks.
The intent is to enable more as we go forward. Here are some example rules:
36 Bugprone rules – catch potential runtime errors, including unsafe self-assignment, forwarding constructors that hide copy or move constructors, narrow accumulation initializers, mismatched argument comments, obvious infinite loops, chained comparisons, swapped arguments, integer division in floating-point calculations, missed base-class copy construction, repeated macro argument evaluation, near-miss virtual overrides, dangling returned references, incorrect erase/remove calls, and incorrect POSIX error checks
1 C++ Core Guidelines rule – catch unsafe captures in coroutine lambdas (
cppcoreguidelines-avoid-capturing-lambda-coroutines)2 Misc rules – catch redundant expressions and RAII objects held across coroutine suspension points
4 Modernize rules – encourage modern C++ practices (e.g.,
modernize-use-auto,modernize-use-override)5 Performance rules – avoid unnecessary copies, hidden range-loop conversions, repeated vector growth in simple loops, pointless moves, and move constructors that copy movable members (
performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-vector-operation,performance-move-const-arg,performance-move-constructor-init)7 Readability rules – improve code clarity (e.g.,
readability-container-contains,readability-container-size-empty)
misc-coroutine-hostile-raii checks Flow’s blocking MutexHolder and
ThreadSpinLockHolder guards as well as std::lock_guard and
std::scoped_lock. These guards must leave scope before a coroutine suspension
point; asynchronous locks designed to span suspension are not included.
bugprone-unhandled-self-assignment retains its default restriction to types
with suspicious fields. Reference-counted assignments that acquire the incoming
reference before releasing the old one use documented, check-specific
NOLINTNEXTLINE annotations where the checker cannot recognize their safety.
Basic examples of clang-tidy style and performance improvement changes:
// Use empty() instead of size() == 0
-if (str.size() == 0) {
+if (str.empty()) {
// Use contains() instead of count() > 0
-if (classPath.count(path) > 0) {
+if (classPath.contains(path)) {
// Avoid unnecessary copies in range-for loops
-for (auto state : states)
+for (const auto& state : states)
// Remove unnecessary const on return types
-const Key keyServersKey(const KeyRef& k) {
+Key keyServersKey(const KeyRef& k) {
clang-tidy vs Clang-Format
Both run in CI but serve different purposes:
Feature |
|
|
|---|---|---|
Focus |
Whitespace, indentation, braces |
Bugs, performance, modern practices |
Speed |
Very fast (syntax-only) |
Slower (requires compilation database) |
Action |
Modifies files in-place ( |
Reports violations (read-only) |
Example |
|
|
Running clang-tidy locally
Step 1: Generate the compilation database
clang-tidy needs a compile_commands.json file to understand include paths and macros. Add -DCMAKE_EXPORT_COMPILE_COMMANDS=ON when you configure your build:
cmake -B ~/build_output -S . -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
Then symlink it to your source root:
ln -sf ~/build_output/compile_commands.json .
Note
A full build is recommended so that generated headers exist and include paths resolve correctly. Without a build, clang-tidy may report false errors on files that depend on generated code. If your compile_commands.json was generated on a different machine (e.g., Okteto), fix the paths with:
sed -i '' 's|/root/src/foundationdb/|/Users/YOU/checkouts/foundationdb/|g' compile_commands.json
Step 2: Locate the helper script
clang-tidy-diff.py runs clang-tidy only on the lines changed in your diff, avoiding noise from existing code you did not touch.
On macOS (with Homebrew LLVM):
export TIDY_DIFF=$(find $(brew --prefix llvm)/share/clang -name "clang-tidy-diff.py")
On Linux:
export TIDY_DIFF=$(find /usr/lib/llvm-*/share/clang -name "clang-tidy-diff.py" | head -n 1)
To make this permanent, add to your ~/.bashrc or ~/.zshrc:
alias fdb-tidy='python3 $(find $(brew --prefix llvm 2>/dev/null || echo "/usr/lib/llvm-*") -name "clang-tidy-diff.py" | head -n 1) -p 1 -path .'
Step 3: Run against your changes
Check all changes between your branch and main:
git diff -U0 origin/main...HEAD | python3 "$TIDY_DIFF" -p 1 -path .
# Or with the alias:
git diff -U0 origin/main...HEAD | fdb-tidy
Check a specific commit:
git show -U0 <commit_hash> | python3 "$TIDY_DIFF" -p 1 -path .
Important
Use -U0 (no context lines). Without it, clang-tidy may lint surrounding lines that you did not change, reporting pre-existing issues.
Understanding the flags
The clang-tidy-diff.py script has different flag conventions from clang-tidy itself:
-p 1– strip one directory prefix from git diff paths (a/src/main.cppbecomessrc/main.cpp)-path .– directory containingcompile_commands.json-quiet– suppress per-file warning count summaries-j N– run N clang-tidy instances in parallel
Warning
In the standard clang-tidy command, -p points to the build directory. In clang-tidy-diff.py, -path points to the build directory and -p is for path stripping.
Running clang-tidy during builds (CMake integration)
FoundationDB’s CMake build supports running clang-tidy automatically during C/C++ compilation:
cmake -S . -B build -G Ninja -DUSE_CLANG_TIDY=ON
ninja -C build fdbserver
This runs clang-tidy on every file as it compiles. It is slower than using clang-tidy-diff.py on your diff but catches issues in all compiled code.
Optional CMake variables:
CLANG_TIDY– path to theclang-tidyexecutable (auto-detected by default)CLANG_TIDY_EXTRA_ARGS– additional space-separated arguments passed toclang-tidy
Known limitations
Build-integrated clang-tidy skips bundled external-library targets, including crc32, libb64, md5, libeio, and libcoroutine.
cppcoreguidelines-slicing is not enabled: Standalone<T> inherits T and
owns its bytes in an Arena, while passing a borrowed T view by value is
an intentional FoundationDB pattern. The check cannot distinguish those borrows
from unintended slicing; the borrow must still not outlive its owner.
readability-simplify-boolean-expr is not enabled because it diagnoses ordinary
uses of FoundationDB’s ASSERT macro after macro expansion.
Quick reference
git diff -U0 origin/main...HEAD | python3 "$TIDY_DIFF" -p 1 -path .
GCC-built compile_commands.json with clang-tidy. If your compile_commands.json was generated with GCC, it may contain GCC-specific flags that clang-tidy (which uses the clang frontend) does not recognize. Add -extra-arg=-Wno-unknown-warning-option to suppress these errors:
git diff -U0 origin/main...HEAD | python3 "$TIDY_DIFF" -p 1 -path . -extra-arg=-Wno-unknown-warning-option
Quick reference