From 4f6ee6f08eb310bc2f9f61049ee9567ab4e06dde Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 30 Sep 2026 13:32:00 +0200 Subject: [PATCH] ci(cppcheck): analyse one pinned configuration instead of up to twelve The advisory static-analysis job has taken 40-50 minutes on nearly every run. The cause is cppcheck's configuration enumeration: given no -D, it analyses each file once per combination of the #ifdef branches it finds (platform, DEBUG, endianness, fuzzing), up to 12, and most files hit that cap ("Too many #ifdef configurations"). CPPCHECK_DEFS pins the gcc / x86-64 / Linux debug configuration CI compiles. Measured with cppcheck 2.13.0 (the version the runner installs): format.c 449 s -> 57 s, eval.c 568 s -> 78 s, pivot.c 325 s -> 42 s, and the whole tree at -j 4 in 632 s, exit 0. Findings are unchanged: the single-file cross-TU warning in eval.c still reports with the pin. Unlike the CI half of #649, this keeps what made the job worth running: the whole tree (no changed-files-only pass, no skipped translation units) and --error-exitcode=1, so a finding still fails the advisory job. What the pin gives up is analysis of the Windows/macOS/WASM #ifdef branches, which were only ever sampled within the 12-configuration cap anyway. The job also gets a 30-minute timeout so a regression in analysis time cannot hold a runner for hours. --- .github/workflows/ci.yml | 7 +++++-- Makefile | 12 +++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47bdf2f1..426878bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,12 +118,15 @@ jobs: # check sets currently surface only false positives (GNU computed-goto # label addresses, caller-guaranteed non-null params, defensive # redundant-null-check heuristics), so this reports without gating. - # cppcheck runs whole-tree here (fast); the heavier clang-tidy pass + # cppcheck runs whole-tree here over one pinned configuration (see + # CPPCHECK_DEFS in the Makefile); the heavier clang-tidy pass # (`make tidy`) runs locally and in the nightly workflow. Flip to a - # blocking gate once the baseline is annotated clean. + # blocking gate once the baseline is annotated clean. The timeout keeps a + # regression in analysis time from holding a runner for hours. static-analysis: runs-on: ubuntu-latest continue-on-error: true + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Install cppcheck diff --git a/Makefile b/Makefile index 0373ab53..ac513dbf 100644 --- a/Makefile +++ b/Makefile @@ -420,6 +420,16 @@ tidy: clang-tidy --quiet $(FILES) -- $(TIDY_FLAGS) # cppcheck is a second-opinion linter — advisory only, never a gate. +# +# CPPCHECK_DEFS pins ONE preprocessor configuration: the gcc / x86-64 / Linux +# debug build CI compiles. Given no -D, cppcheck enumerates the #ifdef +# combinations it finds (platform, DEBUG, endianness, fuzzing) and analyses +# each file up to 12 times — most files hit that cap, which is what made +# the whole-tree pass take ~50 minutes. Undefined macros stay undefined, so +# the Windows/macOS/WASM branches are not analysed here; every Linux file is. +CPPCHECK_DEFS = -D__linux__ -D__GNUC__ -D__x86_64__ -D__SIZEOF_INT128__=16 \ + -D__ORDER_LITTLE_ENDIAN__=1234 -D__BYTE_ORDER__=1234 -D__GLIBC__ -DDEBUG + cppcheck: @command -v cppcheck >/dev/null || { echo "cppcheck: not found"; exit 1; } cppcheck --enable=warning,portability --inline-suppr --error-exitcode=1 \ @@ -427,7 +437,7 @@ cppcheck: --suppress=missingIncludeSystem \ --suppress=assignBoolToPointer \ --suppress=nullPointerRedundantCheck \ - --std=c17 -q $(INCLUDES) src/ + --std=c17 -q $(CPPCHECK_DEFS) $(INCLUDES) src/ # assignBoolToPointer: cppcheck misparses the GNU computed-goto label # address `&&label` (a void*) as a logical-AND yielding a bool. # nullPointerRedundantCheck: a heuristic that fires on the codebase's