105 lines
4.1 KiB
Bash
105 lines
4.1 KiB
Bash
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
|
|
# ucode captures a closure's upvalues when the closure is CREATED. A local
|
|
# declared later in the same scope was never captured, so the name resolves as a
|
|
# global and evaluates to null — "left-hand side is not a function" at call time.
|
|
#
|
|
# Neither `ucode -c` nor `ucode -S -c` catches this, and a unit test only catches
|
|
# it if it actually calls the offending path. Both halves are needed:
|
|
# 1. a static scan for declaration-after-use across every module
|
|
# 2. a runtime invocation of the push handlers, which are pure closures over
|
|
# probes declared elsewhere in the same scope
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
|
|
|
fail() {
|
|
printf 'FAIL: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
# ── 1. static scan ──────────────────────────────────────────────────────────
|
|
# Comments and string literals are stripped first so that prose mentioning a
|
|
# function name is not mistaken for a call.
|
|
scan_forward_refs() {
|
|
local file="$1" stripped
|
|
stripped="$(mktemp)"
|
|
sed -E 's://.*::; s:"[^"]*":"":g' "$file" > "$stripped"
|
|
|
|
grep -nE '^[[:space:]]+function [a-z_0-9]+\(' "$stripped" \
|
|
| sed -E 's/^([0-9]+):[[:space:]]*function ([a-z_0-9]+).*/\1 \2/' \
|
|
| while read -r decl name; do
|
|
local first
|
|
first="$(grep -nE "(^|[^a-zA-Z0-9_.])$name([^a-zA-Z0-9_]|$)" "$stripped" \
|
|
| grep -vE "function $name" | head -1 | cut -d: -f1)"
|
|
if [ -n "$first" ] && [ "$first" -lt "$decl" ]; then
|
|
printf '%s used at line %s but declared at line %s\n' "$name" "$first" "$decl"
|
|
fi
|
|
done
|
|
|
|
rm -f "$stripped"
|
|
}
|
|
|
|
found=0
|
|
while IFS= read -r uc; do
|
|
hits="$(scan_forward_refs "$uc")"
|
|
if [ -n "$hits" ]; then
|
|
printf 'FAIL: forward reference in %s:\n%s\n' "${uc#"$ROOT_DIR/"}" "$hits" >&2
|
|
found=1
|
|
fi
|
|
done < <(find "$TACHYON_LIB" -name '*.uc' | sort)
|
|
[ "$found" -eq 0 ] || fail "nested functions are used before their declaration (ucode does not hoist them)"
|
|
|
|
# The scan is only worth trusting if it fires on a known-bad file.
|
|
canary="$(mktemp "${TMPDIR:-/tmp}/tachyon_fwd_canary_XXXXXX")"
|
|
trap 'rm -f "$canary"' EXIT
|
|
cat > "$canary" <<'EOF'
|
|
function outer() {
|
|
let self = {};
|
|
self.go = function() { return later(); };
|
|
function later() { return 1; }
|
|
return self;
|
|
}
|
|
EOF
|
|
[ -n "$(scan_forward_refs "$canary")" ] \
|
|
|| { rm -f "$canary"; fail "the forward-reference scan does not detect a known-bad file"; }
|
|
|
|
# ── 2. runtime invocation ───────────────────────────────────────────────────
|
|
# Calling the handlers is what actually proves the bindings resolve. The probes
|
|
# they drive touch nft and /proc, which do not exist in the test container, so
|
|
# run_probe's isolation absorbs the failures — a null binding would still raise
|
|
# "left-hand side is not a function" before any probe body runs.
|
|
driver="$(mktemp "${TMPDIR:-/tmp}/tachyon_fwd_XXXXXX")"
|
|
cat > "$driver" <<'EOF'
|
|
let events = require("core.events");
|
|
let ec = require("service.event_controller");
|
|
|
|
let b = events.bus();
|
|
let seen = [];
|
|
for (let t in ["singbox.stopped", "firewall.reloaded", "honeypot.hit"])
|
|
b.on(t, function(ev) { push(seen, ev.type); }, { name: "collect" });
|
|
|
|
let c = ec.controller(b, {});
|
|
c.handle_ubus_service_stop("sing-box", "test");
|
|
c.handle_ubus_firewall_reload();
|
|
c.handle_honeypot_line("10.0.0.1");
|
|
print(join(",", seen) + "\n");
|
|
EOF
|
|
|
|
out="$(ucode -L "$TACHYON_LIB" "$driver" 2>&1)" || {
|
|
rm -f "$driver"
|
|
fail "invoking the push handlers raised an error:
|
|
$out"
|
|
}
|
|
rm -f "$driver"
|
|
|
|
printf '%s\n' "$out" | grep -q 'singbox.stopped' \
|
|
|| fail "handle_ubus_service_stop published nothing (got: $out)"
|
|
printf '%s\n' "$out" | grep -q 'firewall.reloaded' \
|
|
|| fail "handle_ubus_firewall_reload published nothing (got: $out)"
|
|
printf '%s\n' "$out" | grep -q 'honeypot.hit' \
|
|
|| fail "handle_honeypot_line published nothing (got: $out)"
|
|
|
|
printf 'forward reference checks passed\n'
|