A second IpcHandler with an already-used target does not error -- it silently shadows the first, and for the polkit target that means the agent's authentication requests stop reaching the prompt: every password dialog on the desktop, gone without a message. That duplicate nearly shipped once, because the handlers live scattered through a long shell.qml. The contract pairs every IpcHandler with its quoted target across the shell's QML (harnesses excluded -- each is its own root), fails on any name declared twice, and refuses to pass on an empty scan so a declaration-format change cannot quietly blind it.
58 lines
2.3 KiB
Bash
Executable File
58 lines
2.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# One IpcHandler per target name.
|
|
#
|
|
# `qs ipc call <target> ...` resolves by name, and the handlers are spread
|
|
# across a shell.qml several hundred lines long -- a second IpcHandler with an
|
|
# already-used target is exactly the mistake a reader makes there, and it does
|
|
# not error: one handler silently shadows or conflicts with the other. For the
|
|
# polkit target that is not cosmetic -- the agent delivers authentication
|
|
# requests through it, so a shadowed `begin` breaks every password prompt on
|
|
# the desktop. This nearly shipped once; now it cannot.
|
|
#
|
|
# Harness files are excluded: each is its own root, never loaded beside the
|
|
# shell, so sharing a target with it is not a conflict.
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
shell_dir="$repo_dir/config/dot/quickshell"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
|
|
# target name <TAB> file, one line per IpcHandler declaration. The awk pairs
|
|
# each `IpcHandler {` with the next quoted `target:` line in the same file,
|
|
# which is how every handler in this codebase is written.
|
|
declarations="$(find "$shell_dir" -name '*.qml' -not -name '*-harness.qml' -exec awk '
|
|
/IpcHandler *{/ { pending = 1 }
|
|
pending && /target: "/ {
|
|
match($0, /target: "[^"]+"/)
|
|
printf "%s\t%s\n", substr($0, RSTART + 9, RLENGTH - 10), FILENAME
|
|
pending = 0
|
|
}
|
|
' {} +)"
|
|
|
|
# The scan finding almost nothing means the declaration format changed, not
|
|
# that the handlers went away -- fail loudly rather than pass on an empty read.
|
|
total="$(grep -c . <<<"$declarations" || true)"
|
|
(( total >= 5 )) \
|
|
|| note "only $total IpcHandler targets were found; the scan no longer matches how handlers are declared"
|
|
|
|
while read -r count target; do
|
|
[[ -n "$target" ]] || continue
|
|
if (( count > 1 )); then
|
|
files="$(awk -F'\t' -v t="$target" '$1 == t { print $2 }' <<<"$declarations" \
|
|
| sed "s|$repo_dir/||" | paste -sd ', ')"
|
|
note "the \"$target\" IPC target is declared $count times ($files); one silently shadows the other"
|
|
fi
|
|
done < <(cut -f1 <<<"$declarations" | sort | uniq -c)
|
|
|
|
if (( ${#findings[@]} > 0 )); then
|
|
printf 'ipc targets contract: %d finding(s)\n' "${#findings[@]}" >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'ipc targets contract: PASS (%d targets, each declared once)\n' "$total"
|