Find guards wired onto a helper that can never return non-zero
This commit is contained in:
Executable
+171
@@ -0,0 +1,171 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
# PURPOSE
|
||||||
|
# Find guards that cannot fire. A script that writes
|
||||||
|
#
|
||||||
|
# do_the_work || STEP_OK=false
|
||||||
|
#
|
||||||
|
# is only telling the truth if do_the_work can actually return non-zero. When it cannot, the
|
||||||
|
# flag stays true no matter what happened, and the run reports a step it never completed.
|
||||||
|
#
|
||||||
|
# This is the most expensive bug shape in this repo — a summary that says ✅ is the thing the
|
||||||
|
# operator trusts instead of reading the log. adapter.sh already carries the scar: "every
|
||||||
|
# caller that wrote `platform_push_setup_state || X=false` was testing a constant."
|
||||||
|
#
|
||||||
|
# Written 2026-08-24. Its first run found partnership_offboard.sh Step 3 reporting a stack
|
||||||
|
# cleanup that could not fail and, on a second path, one that had not run at all.
|
||||||
|
#
|
||||||
|
# OPERATIONAL MODEL
|
||||||
|
# For every `cmd || VAR=false` in the tree, the command left of the || is resolved and
|
||||||
|
# classified:
|
||||||
|
#
|
||||||
|
# CANFAIL a shell function with an explicit non-zero exit path — the guard is real
|
||||||
|
# EXTERNAL chown, docker, ssh, rsync and friends — can fail, nothing to check
|
||||||
|
# IDIOM [[ … ]] && X=true || X=false — a conditional, not a guard
|
||||||
|
# NEVERFAILS a function with no non-zero path — the guard is decorative
|
||||||
|
# UNKNOWN could not be resolved; reported rather than assumed either way
|
||||||
|
#
|
||||||
|
# A function "can fail" if it contains `return` or `exit` with a non-zero literal or any
|
||||||
|
# variable. The variable case matters: cleanup_partner_containers ends `return "$_rc"`, and a
|
||||||
|
# pattern that only looked for digits reported a working guard as broken.
|
||||||
|
#
|
||||||
|
# UNKNOWN is a first-class verdict, not a failure. Deciding what a bare `done` returns means
|
||||||
|
# evaluating the last command of the last loop, and a checker that guesses at that would
|
||||||
|
# produce confident wrong answers in both directions.
|
||||||
|
#
|
||||||
|
# DESIGN PRINCIPLES
|
||||||
|
# Resolve the command, never word-split it.
|
||||||
|
# An early build stripped a leading "do" to handle `for x in …; do cmd || F=false; done`
|
||||||
|
# and turned do_final_sync into a phantom _final_sync that resolved to nothing. The
|
||||||
|
# inline-loop case is matched on "; do " with required trailing space; nothing else is
|
||||||
|
# trimmed from a command name.
|
||||||
|
#
|
||||||
|
# Comments are excluded before anything else.
|
||||||
|
# Two of the matches in this repo are prose describing the bug, in adapter.sh and
|
||||||
|
# rsync_stop.sh. A checker that reports the documentation of a fixed bug as the bug
|
||||||
|
# teaches the operator to skim its output.
|
||||||
|
#
|
||||||
|
# Every verdict names where it looked.
|
||||||
|
# A NEVERFAILS line carries the file and line of the function it judged, because the
|
||||||
|
# first question is always "which definition did it find" — a name can exist twice.
|
||||||
|
#
|
||||||
|
# OPERATIONAL SAFEGUARDS
|
||||||
|
# Read-only. Greps and reads; writes nothing, executes nothing it finds.
|
||||||
|
# Exits 1 when any NEVERFAILS is reported, 0 otherwise — safe to gate a commit on.
|
||||||
|
# UNKNOWN never fails the run. It is a prompt to look, not a defect claim.
|
||||||
|
#
|
||||||
|
# RUNTIME MODES
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
#
|
||||||
|
# audit_guards.sh
|
||||||
|
# Classify every guard in the repo. Exits 1 if any guard cannot fire.
|
||||||
|
#
|
||||||
|
# audit_guards.sh --all
|
||||||
|
# Show every verdict, including the guards that are sound.
|
||||||
|
#
|
||||||
|
# audit_guards.sh --unknown
|
||||||
|
# Show only the guards that could not be resolved.
|
||||||
|
#
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
SHOW_ALL=false
|
||||||
|
ONLY_UNKNOWN=false
|
||||||
|
for a in "$@"; do
|
||||||
|
case "$a" in
|
||||||
|
--all) SHOW_ALL=true ;;
|
||||||
|
--unknown) ONLY_UNKNOWN=true ;;
|
||||||
|
*) echo "unknown argument: $a" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$REPO_ROOT" || { echo "cannot reach repo root: $REPO_ROOT" >&2; exit 2; }
|
||||||
|
|
||||||
|
# Locate a function definition and print "file:line" followed by its body.
|
||||||
|
_fn_body() {
|
||||||
|
local fn="$1" loc f ln
|
||||||
|
loc=$(grep -rnE "^[[:space:]]*(function[[:space:]]+)?${fn}[[:space:]]*\(\)" \
|
||||||
|
--include='*.sh' . 2>/dev/null | grep -v '^./.git' | head -1)
|
||||||
|
[[ -z "$loc" ]] && return 1
|
||||||
|
f=${loc%%:*}
|
||||||
|
ln=$(echo "$loc" | cut -d: -f2)
|
||||||
|
echo "${f#./}:$ln"
|
||||||
|
awk -v s="$ln" 'NR>=s { print; if (NR > s && /^\}/) exit }' "$f"
|
||||||
|
}
|
||||||
|
|
||||||
|
dead=0 sound=0 unknown=0 other=0
|
||||||
|
|
||||||
|
while IFS= read -r hit; do
|
||||||
|
file=${hit%%:*}
|
||||||
|
lno=$(echo "$hit" | cut -d: -f2)
|
||||||
|
code=$(echo "$hit" | cut -d: -f3-)
|
||||||
|
|
||||||
|
# Everything left of the ||, with an inline loop header removed. The trailing space after
|
||||||
|
# "do" is required — without it this eats the prefix of do_final_sync.
|
||||||
|
cmd=$(echo "$code" | sed 's/[[:space:]]*||.*//' \
|
||||||
|
| sed 's/^[[:space:]]*//' \
|
||||||
|
| sed 's/^.*;[[:space:]]*do[[:space:]]\{1,\}//')
|
||||||
|
head=$(echo "$cmd" | awk '{print $1}')
|
||||||
|
|
||||||
|
verdict=""; detail=""
|
||||||
|
case "$head" in
|
||||||
|
'[['|'[' | test )
|
||||||
|
verdict=IDIOM; detail="conditional, not a guard" ;;
|
||||||
|
chown|chmod|find|rm|mv|cp|docker|ssh|rsync|systemctl|timeout|curl|git|mkdir|ln|tar )
|
||||||
|
verdict=EXTERNAL; detail="external command, can fail" ;;
|
||||||
|
-*|2\>*|1\>*|\>* )
|
||||||
|
# A guard written across several lines: the match landed on a continuation of an
|
||||||
|
# external command (find … -exec …, a redirect) rather than on its head. Reading
|
||||||
|
# back to the head would mean parsing line continuations; the classification is
|
||||||
|
# the same either way, so it is recorded as what it is.
|
||||||
|
verdict=EXTERNAL; detail="continuation of a multi-line external command" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ -z "$verdict" ]]; then
|
||||||
|
if body=$(_fn_body "$head"); then
|
||||||
|
where=$(echo "$body" | head -1)
|
||||||
|
body=$(echo "$body" | tail -n +2)
|
||||||
|
# Non-zero literal, or any variable — quoted or bare.
|
||||||
|
if echo "$body" | grep -qE '^[[:space:]]*(return|exit)[[:space:]]+("?\$|[1-9])'; then
|
||||||
|
verdict=CANFAIL; detail="$where"
|
||||||
|
else
|
||||||
|
verdict=NEVERFAILS
|
||||||
|
detail="$where — no non-zero exit path"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
verdict=UNKNOWN; detail="could not resolve '$head'"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$verdict" in
|
||||||
|
NEVERFAILS) dead=$((dead+1)) ;;
|
||||||
|
CANFAIL) sound=$((sound+1)) ;;
|
||||||
|
UNKNOWN) unknown=$((unknown+1)) ;;
|
||||||
|
*) other=$((other+1)) ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if $ONLY_UNKNOWN; then
|
||||||
|
show=false
|
||||||
|
[[ "$verdict" == UNKNOWN ]] && show=true
|
||||||
|
elif $SHOW_ALL || [[ "$verdict" == NEVERFAILS ]]; then
|
||||||
|
show=true
|
||||||
|
else
|
||||||
|
show=false
|
||||||
|
fi
|
||||||
|
if $show; then
|
||||||
|
printf '%-11s %s:%s\n %s\n %s\n' \
|
||||||
|
"$verdict" "$file" "$lno" "$(echo "$cmd" | cut -c1-76)" "$detail"
|
||||||
|
fi
|
||||||
|
|
||||||
|
done < <(grep -rnE '\|\|[[:space:]]*[A-Za-z_][A-Za-z0-9_]*=false' --include='*.sh' . 2>/dev/null \
|
||||||
|
| grep -v '^./.git' \
|
||||||
|
| grep -vE ':[0-9]+:[[:space:]]*#' \
|
||||||
|
| sed 's|^\./||')
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "──────────────────────────────────────────────────────────────"
|
||||||
|
printf 'sound %s external/idiom %s unresolved %s CANNOT FIRE %s\n' \
|
||||||
|
"$sound" "$other" "$unknown" "$dead"
|
||||||
|
[[ $dead -gt 0 ]] && exit 1
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user