########## 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: .. code-block:: cpp // 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 :doc:`clang-format` ================================= Both run in CI but serve different purposes: .. list-table:: :header-rows: 1 :widths: 15 40 45 * - Feature - ``clang-format`` (Code Style) - ``clang-tidy`` (Code Quality) * - **Focus** - Whitespace, indentation, braces - Bugs, performance, modern practices * - **Speed** - Very fast (syntax-only) - Slower (requires compilation database) * - **Action** - Modifies files in-place (``-i``) - Reports violations (read-only) * - **Example** - ``if(x){...}`` to ``if (x) { ... }`` - ``vec.size() == 0`` to ``vec.empty()`` 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: .. code-block:: shell cmake -B ~/build_output -S . -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON Then symlink it to your source root: .. code-block:: shell 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: .. code-block:: shell 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): .. code-block:: shell export TIDY_DIFF=$(find $(brew --prefix llvm)/share/clang -name "clang-tidy-diff.py") On Linux: .. code-block:: shell 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``: .. code-block:: shell 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``: .. code-block:: shell 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: .. code-block:: shell git show -U0 | 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.cpp`` becomes ``src/main.cpp``) * ``-path .`` -- directory containing ``compile_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: .. code-block:: shell 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 the ``clang-tidy`` executable (auto-detected by default) * ``CLANG_TIDY_EXTRA_ARGS`` -- additional space-separated arguments passed to ``clang-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`` 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 =============== .. code-block:: shell 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: .. code-block:: shell git diff -U0 origin/main...HEAD | python3 "$TIDY_DIFF" -p 1 -path . -extra-arg=-Wno-unknown-warning-option Quick reference