#!/bin/sh # script/lint-audit: audit a captured golangci-lint run for output that # cannot describe this tree. Called by script/lint on every run; usable # on its own against any saved lint output. # # script/lint-audit # # Exits 0 when every finding cites a file in this tree, 1 when any does # not. It NEVER certifies that a lint run passed - it has no idea # whether the run found issues, and does not look. It only rejects # output that is impossible for this tree, which is a different and much # weaker claim. Do not use it as a gate; use script/lint. # # Why this exists (issue #99): golangci-lint caches analysis results, # and a cache shared between two checkouts of this repo can serve one # checkout's stored findings for another, file paths included. The # failure is symmetric and only one direction is loud - a clean tree # failed by a dirty sibling gets investigated, while a dirty tree passed # by a clean sibling is silent. This turns the silent direction into a # hard error, which is why it runs on clean output too. # # The primary fix is that script/lint now keys its cache on the worktree # path so the collision cannot happen. This is the backstop, because a # backstop that only runs when we already believe things are fine is # worth more than one more assumption. set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" # Where script/lint bind-mounts the tree inside the pinned image. A # containerized run that prints absolute paths (`--path-mode abs`) # prints them under this, so they are this tree's files under another # name. Note the consequence, and why the cache key rather than this # check is the real fix: two containerized runs of different checkouts # both call themselves /src, so contamination between two container # runs is not distinguishable by path alone. CONTAINER_ROOT="/src" usage() { echo "usage: $(basename "$0") " >&2 exit 2 } # Every path cited by a finding that is not a file in this tree. # # The linter runs with the tree root as its working directory, so a # legitimate finding cites either a relative path that resolves inside # the tree or an absolute path under the root. A path that escapes # (absolute and elsewhere, or with a `..` component) or that names a # file which is not here describes something this run did not analyse. foreign_paths() { capture="$1" awk -F: '$1 ~ /\.go$/ && $2 ~ /^[0-9]+$/ { print $1 }' "$capture" | sort -u | while IFS= read -r path; do case "$path" in "$ROOT"/*) path="${path#"$ROOT"/}" ;; "$CONTAINER_ROOT"/*) path="${path#"$CONTAINER_ROOT"/}" ;; /*) printf '%s\n' "$path" continue ;; ../* | */../*) printf '%s\n' "$path" continue ;; esac if [ ! -e "$ROOT/$path" ]; then printf '%s\n' "$path" fi done } main() { [ "$#" -eq 1 ] || usage capture="$1" if [ ! -f "$capture" ]; then echo "lint-audit: no such capture file: $capture" >&2 exit 2 fi foreign="$(foreign_paths "$capture")" if [ -z "$foreign" ]; then exit 0 fi cat >&2 <&2 cat >&2 <