Static analysis#
LIBRA makes static analysis nearly zero-configuration: enable
LIBRA_ANALYSIS and the targets appear automatically
for every tool found on PATH. This page explains the design
decisions behind that behaviour and documents tool-specific quirks.
For the target reference, see Target reference.
How LIBRA configures analysis#
LIBRA detects the languages enabled for your CMake project and sets the source files passed to each analysis tool accordingly. This allows tools that only support C or C++ to coexist without causing errors on incompatible source files.
An individual target is created per auto-registered source file, giving you per-file warnings and errors — the same granularity as compilation.
Analysis Philosophy#
Analysis is a separate workflow from the usual debug/test cycle and should
run in its own preset and build directory. This is not merely a conceptual
preference — it is a correctness requirement for the fix targets (see
Header files below). Running fix
targets against a stale build that does not reflect current source can
produce incorrect patches.
It is (highly) unlikely that a given project will want to require that ALL
static analysis checks pass for MR merge, etc. Many of the checks overlap with
each other (esp. clang-tidy), so it is up to each project to choose which
checks to use.
Compilation database#
All supported analysis tools use a compilation database
(compile_commands.json) by default. This is the most reliable source of
truth for compiler flags, include paths, and defines, since it reflects exactly
what was passed to the compiler for each translation unit. When not using a
compilation database, LIBRA walks only the INTERFACE_INCLUDE_DIRECTORIES and
INTERFACE_COMPILE_DEFINITIONS of the main target — it does not recurse into
PRIVATE dependencies, as doing so would violate CMake’s visibility
contract. For projects with multiple layers of PRIVATE deps, this path is
unreliable; LIBRA_USE_COMPDB=YES (the default) is strongly preferred.
Note
For best results, set the compiler to clang when using clang-based
tools such as clang-tidy. If the compiler is not clang, the
compilation database may contain flags that clang does not understand,
causing analysis to fail even if the project builds cleanly. Using
LIBRA_USE_COMPDB =YES is strongly recommended if
GCC for builds and clang for analysis.
Header files#
Header files are always required to be syntactically self-contained and valid in
isolation — they must not rely on a specific include order or on definitions
provided by a prior #include. This is enforced directly by LIBRA’s analysis
machinery.
Headers that are included by at least one .c/.cpp translation unit are
covered automatically by a compdb: tools analyze them in the context of the
including TU, which has a full compilation database entry and therefore correct
flags. Headers that are part of the public API but are not included by any
.c/.cpp — for example, in header-only libraries or partially header-only
components — would otherwise be invisible to the compilation database. LIBRA
handles these by generating a lightweight stub translation unit for each such
header at configure time in the build directory:
// Auto-generated by libra -- DO NOT EDIT
#include <myproject/config/profiling_config.hpp>
Note
.c stubs are generated for .h files and .cpp stubs are generated for .hpp files. Under LIBRA conventions (see Project layout) .h files are always C code and .hpp files are always C++ code.
Each stub is compiled as part of an object library that links privately against the analysis target, giving it a real compilation database entry with the correct include paths and defines. The stub is then registered as an analysis target in the same way as any other source file. If a header fails analysis when checked via its stub, that is a real bug in the header — not a tooling artifact. The fix is to make the header self-contained, not to suppress the error.
Stubs are only generated for headers with no real .c/.cpp coverage. This is
a correctness requirement for the fix analysis variant (e.g.,
fix-clang-tidy): if a header gains .c/.cpp coverage after stubs were
generated, both the stub TU and the real TU will analyze it and generate
identical patches. The second patch application fails because the first has
already modified the file, invalidating the expected context.
This also enables fix analysis to be done in parallel.
To avoid this, always reconfigure and rebuild before running any fix-*
target. The separate analysis preset makes this natural: cmake --preset
analyze && cmake --build --preset analyze before cmake --build
--preset analyze --target fix-clang-tidy ensures stubs reflect the
current source tree.
The sequence that produces a conflict:
foo.hhas no.ccoverage → stub generated → stub compiled → compdb entry exists.User adds
#include <foo.h>to some.c.User runs
fix-clang-tidywithout reconfiguring.Both the stub TU and the real TU analyze
foo.hand generate identical patches.Second patch application fails with a conflict error.
foo.hpphas no.cppcoverage → stub generated → stub compiled → compdb entry exists.User adds
#include <foo.hpp>to some.cpp.User runs
fix-clang-tidywithout reconfiguring.Both the stub TU and the real TU analyze
foo.hppand generate identical patches.Second patch application fails with a conflict error.
Note
The GNU extension versions of -std are passed when not using a
compilation database, to match LIBRA behavior in standard
autodetection.