Merge pull request #2499 from ankitbuilds99/fix/lint-mem-gate-warning-suffix

fix(ci): parse clang-tidy analyzer warning suffix
This commit is contained in:
Martin Vogel authored and GitHub committed 2026-11-03 19:53:15 +01:00
commit 1fc7300cae
3 files changed
+117 -1

No files matched your search

+9 -1
View File
@@ -47,7 +47,15 @@ WHITELIST = Path(os.environ.get(
MIN_WHY = 120
MIN_TRIED = 40
FINDING_RE = re.compile(r"^(?P<file>[^:]+):(?P<line>\d+):\d+:\s+(?:error|warning):\s+(?P<msg>.*?)\s*\[(?P<check>[\w.-]+)\]\s*$")
# With WarningsAsErrors enabled, clang-tidy appends the escalation reason to
# analyzer checks, for example:
# [clang-analyzer-core.NullDereference,-warnings-as-errors]
# Keep the check name separate from that suffix so the gate still matches the
# same whitelist key instead of silently dropping the finding.
FINDING_RE = re.compile(
r"^(?P<file>[^:]+):(?P<line>\d+):\d+:\s+(?:error|warning):\s+"
r"(?P<msg>.*?)\s*\[(?P<check>[\w.-]+)(?:,-warnings-as-errors)?\]\s*$"
)
def strip_noise(text):
+3
View File
@@ -295,6 +295,9 @@ bash "$ROOT/tests/test_activation_diagnostic_contract.sh"
echo "=== Step 0e3: security gate fail-closed contract ==="
bash "$ROOT/tests/test_security_gate_fail_closed.sh"
echo "=== Step 0e4: memory-analyzer gate warning-suffix contract (#2442) ==="
bash "$ROOT/tests/test_lint_mem_gate.sh"
echo "=== Step 0f: tree-sitter runtime Makefile dependencies ==="
bash "$ROOT/tests/test_makefile_ts_runtime_dependencies.sh"
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Regression contract for clang-tidy's `,-warnings-as-errors` diagnostic suffix
# (#2442). With WarningsAsErrors enabled, clang-tidy prints an analyzer check as
# [clang-analyzer-<check>,-warnings-as-errors]; a gate that only accepts
# [<check>] drops every such line and reports a clean gate. Both directions are
# pinned, and every assertion is reported, so a regression names what broke:
# 1. a suffixed finding WITH an argued whitelist entry is counted under its
# plain check name and suppressed (exit 0, "1 argued false positive");
# 2. a suffixed finding WITHOUT an entry fails the gate (exit 1) and is
# reported under its plain check name. This is the negative assertion: a
# parser that drops the suffixed line reports clean (exit 0) here.
# Reverting the suffix group in FINDING_RE (scripts/lint-mem-gate.py) turns
# both red.
#
# The gate runs from inside the fixture directory with RELATIVE paths. On the
# Windows leg python3 is a native MSYS2 build: MSYS converts path ARGUMENTS for
# it, but never the diagnostic text on stdin, so an absolute /c/... path in a
# finding would not resolve there, and a C:/... one would not parse (the file
# field ends at the first colon).
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
GATE="$ROOT/scripts/lint-mem-gate.py"
FIX="$(mktemp -d "${TMPDIR:-/tmp}/lint-mem-gate.XXXXXX")"
trap 'rm -rf "$FIX"' EXIT
failures=0
fail() {
echo "FAIL: $*" >&2
failures=$((failures + 1))
}
show() {
sed 's/^/ | /' "$1" >&2
}
# A subshell, so the test's own shell never sits inside $FIX (Windows cannot
# remove a directory that is a process's working directory).
run_gate() {
(cd "$FIX" && LINT_MEM_WHITELIST=whitelist.txt python3 "$GATE" "$@")
}
cat > "$FIX/sample.c" <<'EOF'
int sample(int *value) {
return *value;
}
int unlisted(int *value) {
return *value;
}
EOF
# A native Windows python3 writes CRLF; strip it before the hash is reused.
HASH="$(run_gate --hash sample.c sample | tr -d '\r' | awk '/^segment-sha256:/ {print $2}')"
if [[ ! "$HASH" =~ ^[0-9a-f]{64}$ ]]; then
echo "FAIL: lint-mem-gate.py --hash sample.c sample printed no segment hash (got '$HASH')" >&2
exit 1
fi
cat > "$FIX/whitelist.txt" <<EOF
## sample.c :: sample :: clang-analyzer-core.NullDereference
segment-sha256: $HASH
why: |
The test deliberately models a diagnostic that is suppressed by the fixture whitelist.
This text is long enough to exercise the gate's normal argument validation path.
tried: |
The fixture is intentionally minimal and only verifies diagnostic parsing.
EOF
SUFFIX='[clang-analyzer-core.NullDereference,-warnings-as-errors]'
# 1. Whitelisted: counted under the plain check name, then suppressed.
rc=0
printf '%s\n' "sample.c:2:12: warning: possible null dereference $SUFFIX" \
| run_gate > /dev/null 2> "$FIX/whitelisted.err" || rc=$?
if [ "$rc" -ne 0 ]; then
fail "a whitelisted suffixed finding left the gate at exit $rc, expected 0"
show "$FIX/whitelisted.err"
fi
if ! grep -q 'memory gate clean (1 argued false positive' "$FIX/whitelisted.err"; then
fail "a whitelisted suffixed finding was not counted as 1 argued false positive"
show "$FIX/whitelisted.err"
fi
# 2. Not whitelisted: the gate must fail and name the finding.
rc=0
printf '%s\n' "sample.c:6:12: warning: possible null dereference $SUFFIX" \
| run_gate > /dev/null 2> "$FIX/unlisted.err" || rc=$?
if [ "$rc" -ne 1 ]; then
fail "an unwhitelisted suffixed finding left the gate at exit $rc, expected 1" \
"(exit 0 = the suffixed line was parsed as no finding, #2442)"
show "$FIX/unlisted.err"
fi
if ! grep -qF 'sample.c:6: possible null dereference [clang-analyzer-core.NullDereference]' "$FIX/unlisted.err" ||
! grep -qF 'no whitelist entry for unlisted()' "$FIX/unlisted.err" ||
! grep -qF '1 finding(s) unaccounted for' "$FIX/unlisted.err"; then
fail "the gate did not report the unwhitelisted suffixed finding under its plain check name"
show "$FIX/unlisted.err"
fi
if [ "$failures" -ne 0 ]; then
echo "lint-mem-gate warning suffix contract: $failures assertion(s) failed" >&2
exit 1
fi
echo 'lint-mem-gate warning suffix contract passed (whitelisted + unwhitelisted suffixed findings)'