Compare commits
1 Commits
issue-152-
...
c0a9b58e0c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0a9b58e0c |
4
TODO.md
4
TODO.md
@@ -44,6 +44,10 @@ undefined identifiers, which is how
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-08-11: `script/verify-build` now walks `dist/` NUL-delimited and asserts
|
||||||
|
`dist/` is a real directory, so a path with a trailing space or a newline can
|
||||||
|
no longer carry a debug marker past the unlisted-bundle check
|
||||||
|
([#223](https://git.eeqj.de/sneak/AutistMask/issues/223)).
|
||||||
- 2026-08-11: A dust threshold of `0` now means "hide nothing" instead of
|
- 2026-08-11: A dust threshold of `0` now means "hide nothing" instead of
|
||||||
falling back to the 100,000 gwei default, and every address comparison in
|
falling back to the 100,000 gwei default, and every address comparison in
|
||||||
`src/shared/transactions.js` goes through one case-normalising helper so a
|
`src/shared/transactions.js` goes through one case-normalising helper so a
|
||||||
|
|||||||
@@ -22,6 +22,18 @@ set -eu
|
|||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
|
# Absolute path to this script, resolved before anything cd's anywhere.
|
||||||
|
# check_unlisted_bundles re-invokes it through xargs, and $0 on its own may be
|
||||||
|
# relative to a directory we are about to leave.
|
||||||
|
SELF="$(cd "$(dirname "$0")" && pwd -P)/$(basename "$0")"
|
||||||
|
|
||||||
|
# Internal re-entry flag; see scan_dist_paths.
|
||||||
|
SCAN_FLAG="--scan-dist-paths"
|
||||||
|
|
||||||
|
# A literal newline, for the is_listed guard.
|
||||||
|
NEWLINE='
|
||||||
|
'
|
||||||
|
|
||||||
MANIFEST="dist/constants-bundles.txt"
|
MANIFEST="dist/constants-bundles.txt"
|
||||||
MARKER_ON="autistmask-build-debug=on"
|
MARKER_ON="autistmask-build-debug=on"
|
||||||
MARKER_OFF="autistmask-build-debug=off"
|
MARKER_OFF="autistmask-build-debug=off"
|
||||||
@@ -29,11 +41,20 @@ MARKER_OFF="autistmask-build-debug=off"
|
|||||||
# Set by read_marker.
|
# Set by read_marker.
|
||||||
MARKER=""
|
MARKER=""
|
||||||
|
|
||||||
|
# Temporary file holding the NUL-delimited dist/ listing, removed by the EXIT
|
||||||
|
# trap because fail() exits from wherever it is called.
|
||||||
|
LISTING=""
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
echo "verify-build: FAIL: $*" >&2
|
echo "verify-build: FAIL: $*" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
[ -z "$LISTING" ] || rm -f "$LISTING"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
# Is the literal $1 present in the file $2? Match (grep exit 0) and no-match
|
# Is the literal $1 present in the file $2? Match (grep exit 0) and no-match
|
||||||
# (exit 1) are answers about the emitted output. Anything else (exit 2: the
|
# (exit 1) are answers about the emitted output. Anything else (exit 2: the
|
||||||
# file could not be read) is not an answer at all, and must not be reported as
|
# file could not be read) is not an answer at all, and must not be reported as
|
||||||
@@ -58,7 +79,17 @@ has_marker() {
|
|||||||
# manifest could not be read and is not an answer at all. Without this, an
|
# manifest could not be read and is not an answer at all. Without this, an
|
||||||
# unreadable manifest reads as "this file is not listed" and every emitted
|
# unreadable manifest reads as "this file is not listed" and every emitted
|
||||||
# bundle gets reported as an unlisted one.
|
# bundle gets reported as an unlisted one.
|
||||||
|
#
|
||||||
|
# A path containing a newline is answered without asking grep, because grep
|
||||||
|
# would read the pattern as two patterns and report a match on either. That is
|
||||||
|
# how such a path escaped this check even once the walk stopped splitting it:
|
||||||
|
# the half before the newline matched a listed line and the file was skipped.
|
||||||
|
# The manifest is line-delimited, so it cannot name such a path at all, and
|
||||||
|
# "not listed" is the only true answer.
|
||||||
is_listed() {
|
is_listed() {
|
||||||
|
case "$1" in
|
||||||
|
*"$NEWLINE"*) return 1 ;;
|
||||||
|
esac
|
||||||
_il_status=0
|
_il_status=0
|
||||||
grep -q -x -F -e "$1" -- "$MANIFEST" || _il_status=$?
|
grep -q -x -F -e "$1" -- "$MANIFEST" || _il_status=$?
|
||||||
case "$_il_status" in
|
case "$_il_status" in
|
||||||
@@ -117,35 +148,66 @@ read_marker() {
|
|||||||
# an endsWith(".js") test; repeating that literal here would mean a bundle
|
# an endsWith(".js") test; repeating that literal here would mean a bundle
|
||||||
# emitted under some other extension escaped the manifest AND this check at
|
# emitted under some other extension escaped the manifest AND this check at
|
||||||
# once, which is the correlated blind spot the two-source design exists to
|
# once, which is the correlated blind spot the two-source design exists to
|
||||||
# avoid. Every file under dist/ is searched, so build.js's filter is the only
|
# avoid. Every regular file and every symlink under dist/ is searched — that
|
||||||
# place the assumption lives and this check is what catches it being wrong.
|
# is the whole of what a build emits — so build.js's filter is the only place
|
||||||
|
# the assumption lives and this check is what catches it being wrong.
|
||||||
#
|
#
|
||||||
# That claim only holds if the walk is exhaustive, so two things are enforced
|
# That claim only holds if the walk is exhaustive and every name survives it
|
||||||
# here rather than assumed:
|
# intact, so four things are enforced here rather than assumed:
|
||||||
#
|
#
|
||||||
|
# - the walk is NUL-delimited and the paths reach the check as arguments, so
|
||||||
|
# no name can be reshaped on the way in. Read line by line, a name with a
|
||||||
|
# trailing space lost it to read's field splitting and the remnant then
|
||||||
|
# matched a manifest line, and a name containing a newline arrived as a
|
||||||
|
# listed path plus an empty one. Both left a marker-carrying, unlisted file
|
||||||
|
# unchecked while the script still reported success. Delivering such a name
|
||||||
|
# intact is only half of it; is_listed also has to keep it out of grep's
|
||||||
|
# pattern, for the same reason.
|
||||||
# - find's exit status is checked. A subtree it cannot descend is reported on
|
# - find's exit status is checked. A subtree it cannot descend is reported on
|
||||||
# stderr and then simply missing from the listing, so an unchecked status
|
# stderr and then simply missing from the listing, so an unchecked status
|
||||||
# turns "could not look" into "nothing was there" — the same conflation
|
# turns "could not look" into "nothing was there" — the same conflation
|
||||||
# has_marker exists to prevent. The status cannot be read off a pipeline
|
# has_marker exists to prevent. The status cannot be read off a pipeline,
|
||||||
# ending in sort, so the sort is a separate step.
|
# so the listing lands in a file that xargs then reads back.
|
||||||
# - symlinks are walked too (-type l), not skipped. A marker-carrying bundle
|
# - symlinks are walked too (-type l), not skipped. A marker-carrying bundle
|
||||||
# reachable under an unlisted path in dist/ is a stale manifest whether the
|
# reachable under an unlisted path in dist/ is a stale manifest whether the
|
||||||
# path is a link or a file, and grep reads through the link. A link that
|
# path is a link or a file, and grep reads through the link. A link that
|
||||||
# cannot be read through — dangling, or pointing at a directory — fails
|
# cannot be read through — dangling, or pointing at a directory — fails
|
||||||
# hard via has_marker's exit-2 path, which is the fail-closed answer: the
|
# hard via has_marker's exit-2 path, which is the fail-closed answer: the
|
||||||
# build emits neither, so their DEBUG state is unproven, not fine.
|
# build emits neither, so their DEBUG state is unproven, not fine.
|
||||||
|
# - dist/ itself must be a directory and not a symlink, which main asserts
|
||||||
|
# before anything reads through it. find does not follow a symlink named on
|
||||||
|
# its own command line, so a linked dist/ collapses this walk to one entry
|
||||||
|
# and cross-checks nothing.
|
||||||
|
#
|
||||||
|
# Types other than regular files and symlinks are left out on purpose: a build
|
||||||
|
# emits none of them, and grep on a fifo would hang rather than fail.
|
||||||
check_unlisted_bundles() {
|
check_unlisted_bundles() {
|
||||||
|
LISTING="$(mktemp "${TMPDIR:-/tmp}/verify-build-dist.XXXXXX")" ||
|
||||||
|
fail "could not create a temporary file for the dist/ listing, so the
|
||||||
|
tree was never walked. Refusing to report success."
|
||||||
|
|
||||||
_find_status=0
|
_find_status=0
|
||||||
_listing="$(find dist \( -type f -o -type l \) -print)" || _find_status=$?
|
find dist \( -type f -o -type l \) -print0 >"$LISTING" || _find_status=$?
|
||||||
[ "$_find_status" -eq 0 ] ||
|
[ "$_find_status" -eq 0 ] ||
|
||||||
fail "find exited $_find_status enumerating dist/, so part of the tree
|
fail "find exited $_find_status enumerating dist/, so part of the tree
|
||||||
was never walked and nothing was established about the files in it. Any
|
was never walked and nothing was established about the files in it. Any
|
||||||
unlisted bundle there went unchecked. That is a permissions or I/O fault on
|
unlisted bundle there went unchecked. That is a permissions or I/O fault on
|
||||||
the artifact, not a stale manifest. Refusing to report success."
|
the artifact, not a stale manifest. Refusing to report success."
|
||||||
_listing="$(printf '%s\n' "$_listing" | sort)"
|
|
||||||
|
|
||||||
while read -r _file; do
|
_scan_status=0
|
||||||
[ -n "$_file" ] || continue
|
xargs -0 "$SELF" "$SCAN_FLAG" <"$LISTING" || _scan_status=$?
|
||||||
|
[ "$_scan_status" -eq 0 ] ||
|
||||||
|
fail "the unlisted-bundle scan exited $_scan_status: either a path
|
||||||
|
under dist/ failed the check reported above, or the scan could not be run
|
||||||
|
at all. Refusing to report success."
|
||||||
|
}
|
||||||
|
|
||||||
|
# The per-path half of check_unlisted_bundles. It runs in a re-invocation of
|
||||||
|
# this script, so it uses the same is_listed and has_marker as the rest of the
|
||||||
|
# file rather than a second copy of them that could drift. Paths arrive as
|
||||||
|
# arguments and are never split, joined or trimmed.
|
||||||
|
scan_dist_paths() {
|
||||||
|
for _file in "$@"; do
|
||||||
if is_listed "$_file"; then
|
if is_listed "$_file"; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -154,9 +216,7 @@ check_unlisted_bundles() {
|
|||||||
fail "$_file carries a debug marker but is absent from $MANIFEST,
|
fail "$_file carries a debug marker but is absent from $MANIFEST,
|
||||||
so the manifest no longer describes the emitted bundles."
|
so the manifest no longer describes the emitted bundles."
|
||||||
fi
|
fi
|
||||||
done <<EOF
|
done
|
||||||
$_listing
|
|
||||||
EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# The requested mode, read from our own environment using build.js's exact
|
# The requested mode, read from our own environment using build.js's exact
|
||||||
@@ -173,9 +233,32 @@ expected_marker() {
|
|||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
|
# Internal re-entry from check_unlisted_bundles' xargs. Not part of the
|
||||||
|
# command-line interface: nothing else invokes it, and it is a distinct
|
||||||
|
# entry point rather than a mode flag threaded through the checks below.
|
||||||
|
if [ "${1-}" = "$SCAN_FLAG" ]; then
|
||||||
|
shift
|
||||||
|
scan_dist_paths "$@"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
expected="$(expected_marker)"
|
expected="$(expected_marker)"
|
||||||
echo "Verifying emitted bundles (expecting $expected)..."
|
echo "Verifying emitted bundles (expecting $expected)..."
|
||||||
|
|
||||||
|
# Asserted here rather than left to grep. A symlinked dist/ used to fail
|
||||||
|
# only because GNU grep exits 2 on a directory, so check_unlisted_bundles'
|
||||||
|
# single entry hit has_marker's I/O path by luck; under a grep that exits 1
|
||||||
|
# instead, the whole cross-check would have collapsed into a pass.
|
||||||
|
if [ -h dist ]; then
|
||||||
|
fail "dist is a symlink, not a directory. find does not follow a
|
||||||
|
symlink named on its own command line, so the unlisted-bundle cross-check
|
||||||
|
would see one entry instead of the emitted tree and establish nothing about
|
||||||
|
it. Refusing to report success."
|
||||||
|
fi
|
||||||
|
[ -d dist ] ||
|
||||||
|
fail "dist is not a directory, so there is no emitted tree to verify.
|
||||||
|
build.js writes it; run make build first."
|
||||||
|
|
||||||
[ -f "$MANIFEST" ] ||
|
[ -f "$MANIFEST" ] ||
|
||||||
fail "$MANIFEST is missing. build.js writes it at the end of a
|
fail "$MANIFEST is missing. build.js writes it at the end of a
|
||||||
successful build; run make build first."
|
successful build; run make build first."
|
||||||
|
|||||||
Reference in New Issue
Block a user