Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .clang-tidy

This file was deleted.

1 change: 1 addition & 0 deletions .clang-tidy
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,23 @@ jobs:
docker tag nix:$NIX_VERSION $IMAGE_ID:master
docker push $IMAGE_ID:master

clang-tidy:
needs: basic-checks
runs-on: ubuntu-24.04
name: clang-tidy
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: ./.github/actions/install-nix-action
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
dogfood: ${{ github.event_name == 'workflow_dispatch' && inputs.dogfood || github.event_name != 'workflow_dispatch' }}
extra_nix_config: "sandbox = true"
- run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- name: Run clang-tidy
run: nix build .#hydraJobs.clangTidy.x86_64-linux -L

flake_regressions:
needs: tests
runs-on: ubuntu-24.04
Expand Down
1 change: 1 addition & 0 deletions doc/manual/source/SUMMARY.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
- [CLI guideline](development/cli-guideline.md)
- [JSON guideline](development/json-guideline.md)
- [C++ style guide](development/cxx.md)
- [Static Analysis](development/static-analysis.md)
- [Experimental Features](development/experimental-features.md)
- [Contributing](development/contributing.md)
- [Releases](release-notes/index.md)
Expand Down
85 changes: 85 additions & 0 deletions doc/manual/source/development/static-analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Static Analysis

Nix uses [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) for static analysis of C++ code.
This helps catch bugs, enforce coding standards, and maintain code quality.

## Running clang-tidy locally

To run clang-tidy on the entire codebase in the development shell:

```console
$ nix develop .#native-clangStdenv
$ configurePhase
$ meson compile -C build clang-tidy
```

This will analyze all C++ source files and report any warnings.

To automatically apply fixes for certain warnings:

```console
$ meson compile -C build clang-tidy-fix
```

> **Warning**: Review the changes before committing, as automatic fixes may not always be correct.

## CI integration

clang-tidy runs automatically on every pull request via GitHub Actions.
The CI job builds `.#hydraJobs.clangTidy.x86_64-linux` which:

1. Builds all components with debug mode (for faster compilation)
2. Runs clang-tidy on each component
3. Fails if any warnings are found (warnings are treated as errors)

## Configuration

The clang-tidy configuration is in `.clang-tidy` at the repository root (symlinked from `nix-meson-build-support/common/clang-tidy/.clang-tidy`).

### Suppressing warnings

If a warning is a false positive, you can suppress it in several ways:

1. **Inline suppression** (preferred for specific cases):
```cpp
// NOLINTBEGIN(bugprone-some-check)
... code ...
// NOLINTEND(bugprone-some-check)
```
Or for a single line:
```cpp
int x = something(); // NOLINT(bugprone-some-check)
```

2. **Configuration file** (for project-wide suppression):
Add the check to the disabled list in `.clang-tidy`:
```yaml
Checks:
- -bugprone-some-check # Reason for disabling
```

3. **Check options** (for configuring check behavior):
```yaml
CheckOptions:
bugprone-reserved-identifier.AllowedIdentifiers: '__some_identifier'
```

### Adding new checks

To enable additional checks:

1. Edit `nix-meson-build-support/common/clang-tidy/.clang-tidy`
2. Add the check to the `Checks` list
3. Run clang-tidy locally to see the impact
4. Fix any new warnings or disable specific sub-checks if needed

## Custom clang-tidy plugin

The Nix project includes infrastructure for custom clang-tidy checks in `src/clang-tidy-plugin/`.
These checks can enforce Nix-specific coding patterns that aren't covered by standard clang-tidy checks.

To add a new custom check:

1. Add the check implementation in `src/clang-tidy-plugin/`
2. Register it in `NixClangTidyChecks.cc`
3. Enable it in `.clang-tidy` with the `nix-` prefix
4 changes: 4 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@
"nix-perl-bindings" = {
supportsCross = false;
};

"nix-clang-tidy-plugin" = {
supportsCross = false;
};
}
(
pkgName:
Expand Down
3 changes: 3 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ endif
if get_option('kaitai-struct-checks')
subproject('kaitai-struct-checks')
endif

# Static Analysis
subdir('nix-meson-build-support/common/clang-tidy')
76 changes: 76 additions & 0 deletions nix-meson-build-support/common/clang-tidy/.clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
UseColor: true
Checks:
- -*
- bugprone-*
# Too many warnings
- -bugprone-assignment-in-if-condition
# Too many warnings
- -bugprone-narrowing-conversions
# Kind of nonsense
- -bugprone-easily-swappable-parameters
# Too many warnings for now
- -bugprone-implicit-widening-of-multiplication-result
# Exception handling patterns in Nix
- -bugprone-empty-catch
# Many warnings
- -bugprone-unchecked-optional-access
# Many warnings, questionable lint
- -bugprone-branch-clone
# Extremely noisy before clang 19: https://github.com/llvm/llvm-project/issues/93959
- -bugprone-multi-level-implicit-pointer-conversion
# We don't compile out our asserts
- -bugprone-assert-side-effect
# TODO: figure out if this warning is useful
- -bugprone-exception-escape
# We use pointers to aggregates intentionally; void * would look weird
- -bugprone-sizeof-expression
#
# Checks disabled to pass on current codebase (can be progressively enabled):
#
# 11 warnings - optional value conversions in various places
- -bugprone-optional-value-conversion
# 4 warnings - switches without default cases
- -bugprone-switch-missing-default-case
# 4 warnings - string_view::data() usage patterns
- -bugprone-suspicious-stringview-data-usage
# 4 warnings - .cc files included in other files (intentional pattern)
- -bugprone-suspicious-include
# 2 warnings - unused return values (AllowCastToVoid helps but some remain)
- -bugprone-unused-return-value
# 2 warnings - unused local RAII-style variables
- -bugprone-unused-local-non-trivial-variable
# 2 warnings - returning const& from parameter
- -bugprone-return-const-ref-from-parameter
# 1 warning - unsafe C functions (e.g., getenv)
- -bugprone-unsafe-functions
# 1 warning - signed char misuse
- -bugprone-signed-char-misuse
# 1 warning - calling parent virtual instead of override
- -bugprone-parent-virtual-call
# 1 warning - null termination issues
- -bugprone-not-null-terminated-result
# 1 warning - macro parentheses
- -bugprone-macro-parentheses
# 1 warning - increment/decrement in conditions
- -bugprone-inc-dec-in-conditions
#
# Non-bugprone checks (also disabled to pass on current codebase):
#
# 4 warnings - exceptions not derived from std::exception
# All thrown exceptions must derive from std::exception
# - hicpp-exception-baseclass
# 88 warnings - C-style casts should be explicit about intent
# - cppcoreguidelines-pro-type-cstyle-cast
# Capturing async lambdas are dangerous (no warnings, kept enabled)
- cppcoreguidelines-avoid-capturing-lambda-coroutines
# Custom nix checks (when added)
- nix-*

CheckOptions:
# __asan_default_options: ASAN runtime configuration function (see nix-meson-build-support/common/asan-options/)
# __wrap___assert_fail: Linker-wrapped assert handler for better stack traces (see nix-meson-build-support/common/assert-fail/)
# _SingleDerivedPathRaw, _DerivedPathRaw: Internal type aliases in derived-path.hh (leading underscore pattern)
# _SingleBuiltPathRaw, _BuiltPathRaw: Internal type aliases in built-path.hh (leading underscore pattern)
bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options;__wrap___assert_fail;_SingleDerivedPathRaw;_DerivedPathRaw;_SingleBuiltPathRaw;_BuiltPathRaw'
# Allow explicitly discarding return values with (void) cast
bugprone-unused-return-value.AllowCastToVoid: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""
Builds all generated files before running clang-tidy.

clang-tidy needs all source files and headers to exist before it can
analyze the code. This script queries Ninja for all custom command
targets that generate files needed for analysis and builds them.

Generated files include:
- .gen.hh: Embedded file headers (SQL schemas, Nix expressions, etc.)
- .gen.inc: Generated include files
- lexer-tab.cc, parser-tab.cc: Flex/Bison generated parsers
- Perl XS bindings, Kaitai parsers, and other generated sources

See: https://github.com/mesonbuild/meson/issues/12817
"""

import subprocess


def get_targets_of_rule(build_root: str, rule_name: str) -> list[str]:
return (
subprocess.check_output(
["ninja", "-C", build_root, "-t", "targets", "rule", rule_name]
)
.decode()
.strip()
.splitlines()
)


def ninja_build(build_root: str, targets: list[str]):
if targets:
subprocess.check_call(["ninja", "-C", build_root, "--", *targets])


def main():
import argparse

ap = argparse.ArgumentParser(description="Build required targets for clang-tidy")
ap.add_argument("build_root", help="Ninja build root", type=str)

args = ap.parse_args()

custom_commands = get_targets_of_rule(args.build_root, "CUSTOM_COMMAND")

targets = (
# Generated headers from embedded files
[t for t in custom_commands if t.endswith(".gen.hh")]
# Generated include files
+ [t for t in custom_commands if t.endswith(".gen.inc")]
# Flex/Bison generated parsers
+ [t for t in custom_commands if t.endswith("-tab.cc")]
# Kaitai Struct generated parsers
+ [t for t in custom_commands if t.endswith(".cpp") and "kaitai" in t]
# Perl XS generated bindings
+ [t for t in custom_commands if t.endswith(".cc") and "perl" in t.lower()]
)
ninja_build(args.build_root, targets)


if __name__ == "__main__":
main()
Loading
Loading