Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
73e4ef4
Create clang-tidy report in CI
filipe-cuim Dec 19, 2025
74786b2
Fix llvm-namespace-comment
filipe-cuim Dec 19, 2025
07b3780
Fix bugprone-too-small-loop-variable
filipe-cuim Dec 19, 2025
9933cc3
Fix clang-analyzer-deadcode.DeadStores
filipe-cuim Dec 19, 2025
3321a9a
Fix clang-analyzer-core.NonNullParamChecker
filipe-cuim Dec 19, 2025
ae14739
Fix bugprone-implicit-widening-of-multiplication-result
filipe-cuim Dec 19, 2025
c2b52bb
Fix bugprone-reserved-identifier
filipe-cuim Dec 19, 2025
8597487
Fix bugprone-branch-clone
filipe-cuim Dec 29, 2025
9a44142
Fix bugprone-narrowing-conversions
filipe-cuim Dec 29, 2025
a651908
Fix llvm-qualified-auto
filipe-cuim Dec 29, 2025
4041057
Fix google-readability-casting
filipe-cuim Dec 29, 2025
f7717b5
Fix llvm-else-after-return
filipe-cuim Dec 29, 2025
ce90d0a
Update clang-tidy script with arguments
filipe-cuim Dec 30, 2025
4dc46b4
Update requirements.txt
filipe-cuim Dec 30, 2025
dc85b04
Address comments
filipe-cuim Jan 20, 2026
a26b24a
Fix clang-analyzer-core.NullDereference
filipe-cuim Jan 23, 2026
bcdb8e1
Fix clang-analyzer-security.insecureAPI.strcpy
filipe-cuim Jan 23, 2026
b6d7e14
Fix clang-analyzer-core.CallAndMessage
filipe-cuim Jan 23, 2026
440d113
Fix clang-diagnostic-error related to bspIo
filipe-cuim Jan 26, 2026
69a4f71
Suppress bugprone-unhandled-self-assignment
filipe-cuim Jan 27, 2026
b0aa497
Suppress bugprone-exception-escape
filipe-cuim Jan 27, 2026
c435fee
Suppress bugprone-unhandled-self-assignment
filipe-cuim Jan 27, 2026
d45d7f7
Fix cert-err58-cpp
filipe-cuim Jan 27, 2026
13b23ee
Suppress cert-dcl50-cpp
filipe-cuim Jan 27, 2026
7817bb3
Suppress cert-dcl37-c
filipe-cuim Jan 27, 2026
c053b38
Suppress cert-dcl51-cpp
filipe-cuim Jan 27, 2026
8684f64
Fix cert-err33-c
filipe-cuim Jan 27, 2026
8bd882b
Fix cert-dcl51-cpp
filipe-cuim Jan 27, 2026
3cfec67
Suppress cert-msc30-c and others related
filipe-cuim Jan 27, 2026
e9a4ea5
Fix .clang-tidy config error
filipe-cuim Jan 27, 2026
0e6c20b
Suppress clang-analyzer-core.DivideZero
filipe-cuim Jan 28, 2026
b8f1625
Ignore clang-diagnostic-error for now at script level
filipe-cuim Jan 28, 2026
96927fc
Disable cert-dcl37-c and cert-dcl51-cpp globally
filipe-cuim Feb 24, 2026
32d6b0f
Upload clang-tidy report only if findings are found
filipe-cuim Feb 24, 2026
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
230 changes: 230 additions & 0 deletions .ci/clang-tidy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
"""
Clang-tidy Checker
==================
This script runs the llvm helper tool run-clang-tidy on all compilation units specified by compile_commands.json.
It filters out files from excluded directories (by default: 3rdparty), exports the findings to a specified YAML file,
and generates a detailed report with the number of findings per diagnostic which is printed to stdout.

The script exits with:
- Exit code 0: No findings detected
- Exit code 1: Findings were detected or an error occurred
"""

import argparse
import subprocess
import sys
import yaml
from pathlib import Path
from collections import Counter


def parse_arguments():
"""
Parse command line arguments.

Returns:
argparse.Namespace: Parsed command line arguments
"""
parser = argparse.ArgumentParser(
description="Run clang-tidy on a CMake build directory and analyze findings.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --build_directory build/tests/posix/Debug --output_file ct-findings.yaml
%(prog)s --build_directory build/tests/posix/Debug --output_file ct-findings.yaml --exclude "test|mock"
%(prog)s --build_directory build/tests/posix/Debug --output_file ct-findings.yaml --quiet
%(prog)s --build_directory build/tests/posix/Debug --output_file ct-findings.yaml --verbose
""",
)

parser.add_argument(
"--build_directory",
type=Path,
help="Path to the build directory containing compile_commands.json generated by CMake",
required=True,
)

parser.add_argument(
"--output_file",
type=str,
help="Path to the output YAML file where clang-tidy findings will be stored",
required=True,
)

parser.add_argument(
"--exclude",
type=str,
default="3rdparty",
help="Regular expression pattern to match files to exclude (default: '3rdparty')",
)

parser.add_argument(
"--ignore-checks",
type=str,
default="",
help="Comma-separated list of diagnostic names to ignore (e.g., 'clang-diagnostic-error,clang-diagnostic-warning')",
)

output_group = parser.add_mutually_exclusive_group()
output_group.add_argument(
"--quiet",
action="store_true",
help="Suppress run-clang-tidy progress output (default)",
)

output_group.add_argument(
"--verbose",
action="store_true",
help="Show run-clang-tidy progress output",
)

args = parser.parse_args()

# Validate that build directory exists
if not args.build_directory.exists():
parser.error(f"Build directory does not exist: {args.build_directory}")

# Validate that compile_commands.json exists in build directory
compile_commands = args.build_directory / "compile_commands.json"
if not compile_commands.exists():
parser.error(
f"compile_commands.json not found in build directory: {args.build_directory}"
)

return args


def count_findings(file_name: Path, ignored_checks: list = []) -> int:
"""
Reads the YAML file generated by clang-tidy and counts the number of findings per diagnostic name.

Args:
file_name: Path to the YAML file containing clang-tidy findings
ignored_checks: List of diagnostic names to ignore

Returns:
Total number of findings (excluding ignored checks)
"""

with open(file_name, "r") as f:
data = yaml.safe_load(f)

# Count findings per diagnostic name
counter = Counter()
ignored_counter = Counter()

for diagnostic in data.get("Diagnostics", []):
diagnostic_name = diagnostic.get("DiagnosticName")
if diagnostic_name:
if diagnostic_name in ignored_checks:
ignored_counter[diagnostic_name] += 1
else:
counter[diagnostic_name] += 1

# Sort by count (descending) and then by name
sorted_findings = sorted(counter.items(), key=lambda x: (-x[1], x[0]))

# Print the report
total_findings = sum(counter.values())
total_ignored = sum(ignored_counter.values())
print("Clang-Tidy Findings Report")
print("=" * 60)
print(f"\nTotal unique checks: {len(sorted_findings)}")
print(f"Total findings: {total_findings}")
if total_ignored > 0:
print(f"Total ignored findings: {total_ignored}")

if sorted_findings:
print("\nFindings per check:")
print("-" * 60)

for check_name, count in sorted_findings:
print(f"{check_name}: {count}")
else:
print("\n✓ No findings detected!")

if total_ignored > 0:
print("\nIgnored findings:")
print("-" * 60)
for check_name, count in sorted(
ignored_counter.items(), key=lambda x: (-x[1], x[0])
):
print(f"{check_name}: {count}")

return total_findings


def run_clang_tidy(
build_dir: Path, output_file: Path, exclude_pattern: str, quiet: bool
) -> int:
"""
Run clang-tidy on the build directory.

Args:
build_dir: Path to the build directory
output_file: Path to the output YAML file
exclude_pattern: Regular expression pattern to exclude files
quiet: Whether to suppress run-clang-tidy output

Returns:
Return code from run-clang-tidy
"""
# Convert the exclusion pattern to a negative lookahead pattern
# This makes run-clang-tidy check files that DO NOT match the exclude pattern
negated_pattern = f"^(?!.*({exclude_pattern})).*"

cmd = [
"run-clang-tidy-17.py",
"-p",
str(build_dir),
"-export-fixes",
str(output_file),
negated_pattern,
]

if quiet:
cmd.insert(3, "-quiet")

try:
result = subprocess.run(cmd, check=False)
return result.returncode
except FileNotFoundError:
print(
"Error: run-clang-tidy not found. Please ensure LLVM tools are installed.",
file=sys.stderr,
)
sys.exit(1)
except Exception as e:
print(f"Error running clang-tidy: {e}", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
args = parse_arguments()

print(f"Running clang-tidy on build directory: {args.build_directory}")
print(f"Output file: {args.output_file}")
print(f"Exclude pattern: {args.exclude}")
print("-" * 60)

# Run clang-tidy
run_clang_tidy(
args.build_directory,
args.output_file,
args.exclude,
not args.verbose, # quiet mode is default unless --verbose is specified
)

# Parse ignored checks
ignored_checks = [
check.strip() for check in args.ignore_checks.split(",") if check.strip()
]

# Count and report findings
number_of_findings = count_findings(args.output_file, ignored_checks)

# Exit with appropriate code
if number_of_findings != 0:
sys.exit(1)
else:
sys.exit(0)
Loading