From 426e23d8be6f42fb18235e0e36f8793157f9bed0 Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Sun, 26 Apr 2026 17:37:01 -0700 Subject: [PATCH] Add clang-format, clang-tidy, ASan/UBSan CI lanes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Format and tidy enforce only on lines changed in PRs (diff-only via git-clang-format and clang-tidy-diff.py) — existing files keep their current style until edited. Mass reformat deferred; .git-blame-ignore-revs stub is in place for whenever that happens. Sanitizer lane builds with -fsanitize=address,undefined and runs the unit suite. continue-on-error: true initially so we can triage findings without blocking PRs. UB categories pervasive in the Hash9 C cascade (alignment, signed-integer-overflow, vptr) are suppressed pending file-by-file fixes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .clang-format | 49 ++++++++++++++++ .clang-tidy | 46 +++++++++++++++ .git-blame-ignore-revs | 11 ++++ .github/workflows/build-all.yml | 44 ++++++++++++++ .github/workflows/lint.yml | 100 ++++++++++++++++++++++++++++++++ 5 files changed, 250 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .git-blame-ignore-revs create mode 100644 .github/workflows/lint.yml diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4cb4e8f --- /dev/null +++ b/.clang-format @@ -0,0 +1,49 @@ +# Triangles code style. +# Conservative: do not reflow long lines, do not reorganize includes. +# This config is enforced *only on changed lines* via `git clang-format` in CI, +# so it shapes new/edited code without touching legacy files until they're touched. + +BasedOnStyle: LLVM +Language: Cpp +Standard: c++17 + +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ContinuationIndentWidth: 4 +AccessModifierOffset: -4 + +ColumnLimit: 0 # Don't reflow long lines — too disruptive for legacy code. +ReflowComments: false + +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false + +PointerAlignment: Left +DerivePointerAlignment: false +SpaceAfterCStyleCast: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeAssignmentOperators: true + +NamespaceIndentation: None +FixNamespaceComments: true + +# Includes: don't shuffle — header order in this codebase is load-bearing +# (e.g. main.cpp's mix of project + system headers carries platform meaning). +SortIncludes: false +IncludeBlocks: Preserve + +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 2 + +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignTrailingComments: true + +# Don't auto-add braces to single-statement bodies — too invasive. +InsertBraces: false diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..483902f --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,46 @@ +# Triangles clang-tidy config. +# +# Goal: catch real bugs in new/edited code without drowning in noise from +# legacy patterns. Enforced *diff-only* in CI (changed lines on PRs). +# +# Conservative starter set. Graduate checks to WarningsAsErrors only after +# the codebase is clean for that check. + +Checks: > + -*, + bugprone-*, + performance-*, + readability-misleading-indentation, + readability-redundant-control-flow, + readability-redundant-smartptr-get, + readability-redundant-string-cstr, + readability-redundant-string-init, + readability-string-compare, + modernize-use-nullptr, + modernize-use-override, + modernize-deprecated-headers, + cppcoreguidelines-init-variables, + cppcoreguidelines-pro-type-member-init, + -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + -bugprone-narrowing-conversions, + -bugprone-branch-clone, + -bugprone-signed-char-misuse, + -bugprone-reserved-identifier, + -bugprone-unchecked-optional-access, + -performance-no-int-to-ptr, + -performance-avoid-endl + +# Warn-only initially. Once a check is clean repo-wide we can promote it here. +WarningsAsErrors: '' + +# Run on project sources; skip vendored/generated code. +HeaderFilterRegex: '^.*src/(?!json/nlohmann_json|leveldb|lz4|tor/tor-src).*\.h$' + +FormatStyle: file + +CheckOptions: + - key: readability-identifier-naming.IgnoreMainLikeFunctions + value: '1' + - key: cppcoreguidelines-init-variables.IncludeStyle + value: 'google' diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..dcde191 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,11 @@ +# Revisions listed here are skipped by `git blame` when --ignore-revs-file +# is configured. GitHub honors this file automatically. +# +# Add the SHA of any large mechanical reformat / rename / mass-style commit +# below, with a one-line comment. +# +# Example: +# abc1234567890abcdef # repo-wide clang-format (no behavior change) +# +# To enable locally: +# git config blame.ignoreRevsFile .git-blame-ignore-revs diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index ae24811..39639c8 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -37,6 +37,50 @@ jobs: - name: Run unit tests run: cd build && ctest --output-on-failure || true + test-linux-sanitizers: + # ASan + UBSan build of the daemon + unit tests. Allowed to fail until + # findings are triaged — see .github/workflows/lint.yml comment block. + # Once the test suite is clean under sanitizers, drop continue-on-error. + runs-on: ubuntu-22.04 + continue-on-error: true + env: + # ASan: leak detection off by default (BDB and OpenSSL produce noise on shutdown). + # Re-enable once we've quieted the legitimate suspects. + ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1:strict_string_checks=1:detect_stack_use_after_return=1" + # UBSan: print full stack traces on first error and exit non-zero. + UBSAN_OPTIONS: "halt_on_error=1:abort_on_error=1:print_stacktrace=1" + # Suppress UB categories that are pervasive in the Hash9 C cascade + # and BDB until they're fixed file-by-file. + SAN_FLAGS: "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr" + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential cmake ninja-build \ + libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ + libevent-dev libminiupnpc-dev zlib1g-dev + + - name: Configure with sanitizers + run: | + cmake -B build-san -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_FLAGS="$SAN_FLAGS" \ + -DCMAKE_CXX_FLAGS="$SAN_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="$SAN_FLAGS" \ + -DBUILD_QT=OFF \ + -DBUILD_DAEMON=ON \ + -DBUILD_TESTS=ON \ + -DUSE_UPNP=OFF + + - name: Build + run: cmake --build build-san -j$(nproc) + + - name: Run unit tests under sanitizers + run: cd build-san && ctest --output-on-failure + build-windows-qt: runs-on: windows-latest defaults: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..9646b8f --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,100 @@ +name: Lint + +on: + pull_request: + branches: [master] + workflow_dispatch: + +# Diff-only enforcement: clang-format and clang-tidy run only on lines changed +# in the PR. Existing files keep their current style until they're edited. +# See .clang-format and .clang-tidy for the rule sets. + +jobs: + clang-format-diff: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + # Need merge-base with target branch to compute the diff. + fetch-depth: 0 + + - name: Install clang-format + run: | + sudo apt-get update + sudo apt-get install -y clang-format-15 + sudo ln -sf /usr/bin/clang-format-15 /usr/local/bin/clang-format + + - name: Check format on changed lines + run: | + BASE_SHA=$(git merge-base origin/${{ github.base_ref }} HEAD) + echo "Comparing against merge-base: $BASE_SHA" + + # git-clang-format prints a diff if any changed line violates style. + # --diff exits non-zero when reformatting would change something. + OUTPUT=$(git clang-format --diff "$BASE_SHA" -- '*.cpp' '*.h' '*.hpp' '*.cc' || true) + + if [ -z "$OUTPUT" ] || [ "$OUTPUT" = "no modified files to format" ] || [ "$OUTPUT" = "clang-format did not modify any files" ]; then + echo "clang-format: clean" + exit 0 + fi + + echo "::error::clang-format wants to change the following on lines you touched." + echo "Run \`git clang-format $BASE_SHA\` locally and commit the result." + echo "$OUTPUT" + exit 1 + + clang-tidy-diff: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install dependencies + clang-tidy + run: | + sudo apt-get update + sudo apt-get install -y build-essential cmake ninja-build clang-tidy-15 \ + libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ + libevent-dev libminiupnpc-dev zlib1g-dev + sudo ln -sf /usr/bin/clang-tidy-15 /usr/local/bin/clang-tidy + + - name: Configure (export compile_commands.json) + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DBUILD_QT=OFF \ + -DBUILD_DAEMON=ON \ + -DBUILD_TESTS=ON \ + -DUSE_UPNP=OFF + + - name: Generate build artifacts that headers depend on + # build.h, qt UI headers, etc. — clang-tidy needs them to parse sources. + run: cmake --build build --target generate_build_info + + - name: Run clang-tidy on changed lines + run: | + BASE_SHA=$(git merge-base origin/${{ github.base_ref }} HEAD) + echo "Comparing against merge-base: $BASE_SHA" + + # clang-tidy-diff.py ships with clang-tidy; runs tidy only on changed lines. + DIFF_SCRIPT=$(dpkg -L clang-tidy-15 | grep clang-tidy-diff.py | head -1) + if [ -z "$DIFF_SCRIPT" ]; then + DIFF_SCRIPT=/usr/share/clang/clang-tidy-diff.py + fi + echo "Using: $DIFF_SCRIPT" + + # -p1 strips the leading "a/"/"b/" from git diff paths. + # -path=build points clang-tidy at compile_commands.json. + # -iregex restricts to project sources (not vendored). + git diff -U0 "$BASE_SHA" -- 'src/*.cpp' 'src/*.h' \ + ':(exclude)src/json/nlohmann_json.hpp' \ + ':(exclude)src/leveldb/*' \ + ':(exclude)src/lz4/*' \ + ':(exclude)src/tor/tor-src/*' \ + | python3 "$DIFF_SCRIPT" -p1 -path build \ + -iregex '.*\.(cpp|cc|h|hpp)$' \ + -j$(nproc) || EXIT=$? + + # Warn-only initially. Flip this to `exit ${EXIT:-0}` once we're clean. + exit 0