update
This commit is contained in:
Executable
+350
@@ -0,0 +1,350 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
: "${PRE15_DUT:?PRE15_DUT is required}"
|
||||
: "${PRE15_ROOT:?PRE15_ROOT is required}"
|
||||
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
|
||||
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
|
||||
. "$PRE15_LIB_DIR/runner.sh"
|
||||
|
||||
baseline=643b83c9cea6a99398fa3dd053f2e8f65e108ca0
|
||||
artifacts=$PRE15_RUN_DIR/artifacts
|
||||
action=${1:-callgraph}
|
||||
shift || :
|
||||
|
||||
case "$action" in
|
||||
callgraph|nm-allowlist) ;;
|
||||
*) pre15_fail_usage "unknown B31 action: $action" ;;
|
||||
esac
|
||||
|
||||
for tool in git python3 sha256sum; do
|
||||
command -v "$tool" >/dev/null 2>&1 || \
|
||||
pre15_infra_blocked "missing B31 host tool: $tool"
|
||||
done
|
||||
|
||||
pre15_record_fixture b31-data "$PRE15_DUT/src/data.c"
|
||||
pre15_record_fixture b31-dir "$PRE15_DUT/src/dir.c"
|
||||
pre15_record_fixture b31-inode "$PRE15_DUT/src/inode.c"
|
||||
pre15_record_fixture b31-internal "$PRE15_DUT/src/internal.h"
|
||||
pre15_record_fixture b31-case \
|
||||
"$PRE15_DUT/tests/pre15/cases/B31-visibility.sh"
|
||||
mkdir -p "$artifacts"
|
||||
|
||||
if ! git -C "$PRE15_ROOT" diff --quiet "$baseline" -- \
|
||||
repo-pre-15/src/data.c repo-pre-15/src/dir.c \
|
||||
repo-pre-15/src/inode.c repo-pre-15/src/internal.h; then
|
||||
pre15_target_reached
|
||||
pre15_dut_fail 'B31 production files differ from the B30 source baseline'
|
||||
fi
|
||||
|
||||
if test "$action" = callgraph; then
|
||||
test "$#" -eq 0 || pre15_fail_usage 'B31 callgraph takes no arguments'
|
||||
for source in zdata.c super.c namei.c erofs_fs.h; do
|
||||
pre15_record_fixture "b31-$source" "$PRE15_DUT/src/$source"
|
||||
done
|
||||
pre15_record_fixture b31-linux-data "$PRE15_ROOT/src-linux/data.c"
|
||||
pre15_record_fixture b31-linux-internal "$PRE15_ROOT/src-linux/internal.h"
|
||||
pre15_record_fixture b31-linux-fileio "$PRE15_ROOT/src-linux/fileio.c"
|
||||
pre15_record_fixture b31-linux-fscache "$PRE15_ROOT/src-linux/fscache.c"
|
||||
pre15_record_fixture b31-linux-inode "$PRE15_ROOT/src-linux/inode.c"
|
||||
pre15_record_fixture b31-linux-xattr "$PRE15_ROOT/src-linux/xattr.c"
|
||||
pre15_record_fixture b31-linux-zmap "$PRE15_ROOT/src-linux/zmap.c"
|
||||
pre15_record_fixture b31-linux-zdata "$PRE15_ROOT/src-linux/zdata.c"
|
||||
|
||||
pre15_target_reached
|
||||
if ! python3 -B - "$PRE15_ROOT" "$PRE15_DUT" \
|
||||
"$artifacts/B31-callgraph.json" <<'PY'
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
root = Path(sys.argv[1])
|
||||
dut = Path(sys.argv[2])
|
||||
report_path = Path(sys.argv[3])
|
||||
src = dut / "src"
|
||||
|
||||
symbols = {
|
||||
"erofs_map_dev": "data.c",
|
||||
"erofs_map_blocks": "data.c",
|
||||
"erofs_dirent_namelen": "dir.c",
|
||||
"erofs_iloc": "inode.c",
|
||||
}
|
||||
expected_occurrences = {
|
||||
"erofs_map_dev": {"data.c": 2},
|
||||
"erofs_map_blocks": {"data.c": 3, "super.c": 1, "zdata.c": 1},
|
||||
"erofs_dirent_namelen": {"dir.c": 3, "namei.c": 1},
|
||||
"erofs_iloc": {"inode.c": 3},
|
||||
}
|
||||
expected_cross_tu = {
|
||||
"erofs_map_dev": [],
|
||||
"erofs_map_blocks": ["super.c", "zdata.c"],
|
||||
"erofs_dirent_namelen": ["namei.c"],
|
||||
"erofs_iloc": [],
|
||||
}
|
||||
introduced_by = {
|
||||
("erofs_map_blocks", "super.c"): "55c609db",
|
||||
("erofs_map_blocks", "zdata.c"): "55c609db",
|
||||
("erofs_dirent_namelen", "namei.c"): "c7508502",
|
||||
}
|
||||
|
||||
|
||||
def strip_noncode(source: str) -> str:
|
||||
output = list(source)
|
||||
state = "code"
|
||||
index = 0
|
||||
while index < len(source):
|
||||
char = source[index]
|
||||
following = source[index + 1] if index + 1 < len(source) else ""
|
||||
if state == "code":
|
||||
if char == "/" and following == "*":
|
||||
output[index] = output[index + 1] = " "
|
||||
state = "block"
|
||||
index += 2
|
||||
continue
|
||||
if char == "/" and following == "/":
|
||||
output[index] = output[index + 1] = " "
|
||||
state = "line"
|
||||
index += 2
|
||||
continue
|
||||
if char == '"':
|
||||
output[index] = " "
|
||||
state = "string"
|
||||
elif char == "'":
|
||||
output[index] = " "
|
||||
state = "character"
|
||||
elif state == "block":
|
||||
if char == "*" and following == "/":
|
||||
output[index] = output[index + 1] = " "
|
||||
state = "code"
|
||||
index += 2
|
||||
continue
|
||||
if char != "\n":
|
||||
output[index] = " "
|
||||
elif state == "line":
|
||||
if char == "\n":
|
||||
state = "code"
|
||||
else:
|
||||
output[index] = " "
|
||||
else:
|
||||
if char == "\\" and following:
|
||||
output[index] = output[index + 1] = " "
|
||||
index += 2
|
||||
continue
|
||||
if (state == "string" and char == '"') or (
|
||||
state == "character" and char == "'"
|
||||
):
|
||||
state = "code"
|
||||
if char != "\n":
|
||||
output[index] = " "
|
||||
index += 1
|
||||
if state == "block":
|
||||
raise SystemExit("unterminated block comment")
|
||||
return "".join(output)
|
||||
|
||||
|
||||
sources = {
|
||||
path.name: strip_noncode(path.read_text(encoding="utf-8"))
|
||||
for path in sorted(src.glob("*.c"))
|
||||
}
|
||||
locations: dict[str, list[dict[str, object]]] = {}
|
||||
cross_tu: dict[str, list[str]] = {}
|
||||
history: dict[str, str] = {}
|
||||
|
||||
for symbol, owner in symbols.items():
|
||||
pattern = re.compile(r"\b" + re.escape(symbol) + r"\s*\(")
|
||||
sites = []
|
||||
counts = Counter()
|
||||
for filename, source in sources.items():
|
||||
for match in pattern.finditer(source):
|
||||
line = source.count("\n", 0, match.start()) + 1
|
||||
sites.append({"file": filename, "line": line})
|
||||
counts[filename] += 1
|
||||
actual_counts = dict(sorted(counts.items()))
|
||||
if actual_counts != expected_occurrences[symbol]:
|
||||
raise SystemExit(
|
||||
f"{symbol} call-token inventory changed: {actual_counts!r}"
|
||||
)
|
||||
consumers = sorted(filename for filename in counts if filename != owner)
|
||||
if consumers != expected_cross_tu[symbol]:
|
||||
raise SystemExit(f"{symbol} cross-TU consumers changed: {consumers!r}")
|
||||
locations[symbol] = sites
|
||||
cross_tu[symbol] = consumers
|
||||
|
||||
for filename in consumers:
|
||||
for site in sites:
|
||||
if site["file"] != filename:
|
||||
continue
|
||||
blamed = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(root),
|
||||
"blame",
|
||||
"--line-porcelain",
|
||||
f"-L{site['line']},{site['line']}",
|
||||
"--",
|
||||
f"repo-pre-15/src/{filename}",
|
||||
],
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout.splitlines()[0].split()[0]
|
||||
expected = introduced_by[(symbol, filename)]
|
||||
if not blamed.startswith(expected):
|
||||
raise SystemExit(
|
||||
f"{symbol} {filename} introducer changed: {blamed}"
|
||||
)
|
||||
history[f"{symbol}:{filename}"] = blamed
|
||||
|
||||
internal = (src / "internal.h").read_text(encoding="utf-8")
|
||||
for symbol in symbols:
|
||||
prototype_count = len(
|
||||
re.findall(r"\b" + re.escape(symbol) + r"\s*\(", internal)
|
||||
)
|
||||
if prototype_count != 1:
|
||||
raise SystemExit(f"{symbol} prototype count changed: {prototype_count}")
|
||||
|
||||
all_source = "\n".join(
|
||||
path.read_text(encoding="utf-8") for path in sorted(src.glob("*.[ch]"))
|
||||
)
|
||||
mtime_helper_invocations = internal.count(
|
||||
"EROFS_FEATURE_FUNCS(mtime, compat, COMPAT_MTIME)"
|
||||
)
|
||||
mtime_explicit_references = len(re.findall(r"\berofs_sb_has_mtime\b", all_source))
|
||||
if mtime_helper_invocations != 1 or mtime_explicit_references != 0:
|
||||
raise SystemExit(
|
||||
"mtime helper inventory changed: "
|
||||
f"generator={mtime_helper_invocations} explicit={mtime_explicit_references}"
|
||||
)
|
||||
|
||||
erofs_fs = (src / "erofs_fs.h").read_text(encoding="utf-8")
|
||||
inode = (src / "inode.c").read_text(encoding="utf-8")
|
||||
super_source = (src / "super.c").read_text(encoding="utf-8")
|
||||
timestamp_anchors = {
|
||||
"ondisk_constant": "#define EROFS_FEATURE_COMPAT_MTIME" in erofs_fs,
|
||||
"compact_checked_add": "__builtin_add_overflow(sbi->epoch," in inode,
|
||||
"extended_timestamp": "(int64_t)le64toh(die->i_mtime)" in inode,
|
||||
"epoch_decode": "sbi->epoch = (int64_t)le64toh(dsb->epoch);" in super_source,
|
||||
"fixed_nsec_decode": "sbi->fixed_nsec = le32toh(dsb->fixed_nsec);" in super_source,
|
||||
}
|
||||
if not all(timestamp_anchors.values()):
|
||||
raise SystemExit(f"timestamp path changed: {timestamp_anchors!r}")
|
||||
|
||||
linux_src = root / "src-linux"
|
||||
linux_text = {
|
||||
path.name: strip_noncode(path.read_text(encoding="utf-8"))
|
||||
for path in (
|
||||
linux_src / "data.c",
|
||||
linux_src / "fileio.c",
|
||||
linux_src / "fscache.c",
|
||||
linux_src / "inode.c",
|
||||
linux_src / "xattr.c",
|
||||
linux_src / "zmap.c",
|
||||
linux_src / "zdata.c",
|
||||
linux_src / "internal.h",
|
||||
)
|
||||
}
|
||||
linux_internal = linux_text["internal.h"]
|
||||
if not re.search(r"\bint\s+erofs_map_dev\s*\(", linux_internal):
|
||||
raise SystemExit("Linux erofs_map_dev external prototype is absent")
|
||||
if not re.search(r"\bint\s+erofs_map_blocks\s*\(", linux_internal):
|
||||
raise SystemExit("Linux erofs_map_blocks external prototype is absent")
|
||||
if not re.search(
|
||||
r"static\s+inline\s+erofs_off_t\s+erofs_iloc\s*\(", linux_internal
|
||||
):
|
||||
raise SystemExit("Linux erofs_iloc is no longer static inline")
|
||||
if re.search(r"\berofs_dirent_namelen\s*\(", "\n".join(linux_text.values())):
|
||||
raise SystemExit("unexpected Linux erofs_dirent_namelen symbol")
|
||||
|
||||
linux_consumers = {}
|
||||
for symbol in ("erofs_map_dev", "erofs_map_blocks", "erofs_iloc"):
|
||||
pattern = re.compile(r"\b" + re.escape(symbol) + r"\s*\(")
|
||||
linux_consumers[symbol] = sorted(
|
||||
filename for filename, source in linux_text.items() if pattern.search(source)
|
||||
)
|
||||
|
||||
report = {
|
||||
"decision": "STOP-NO-SOURCE",
|
||||
"freebsd_call_tokens": locations,
|
||||
"freebsd_cross_tu_consumers": cross_tu,
|
||||
"introduced_by": history,
|
||||
"linux_linkage": {
|
||||
"erofs_map_dev": "external",
|
||||
"erofs_map_blocks": "external",
|
||||
"erofs_dirent_namelen": "absent",
|
||||
"erofs_iloc": "static inline",
|
||||
},
|
||||
"linux_consumer_files_in_probe": linux_consumers,
|
||||
"mtime_helper": {
|
||||
"generator_invocations": mtime_helper_invocations,
|
||||
"explicit_references": mtime_explicit_references,
|
||||
},
|
||||
"timestamp_anchors": timestamp_anchors,
|
||||
}
|
||||
report_path.write_text(
|
||||
json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii"
|
||||
)
|
||||
PY
|
||||
then
|
||||
pre15_dut_fail 'B31 cross-TU visibility audit failed'
|
||||
fi
|
||||
printf '%s\n' \
|
||||
'B31 callgraph audit reached mandatory STOP' \
|
||||
'erofs_map_blocks: cross-TU consumers super.c,zdata.c introduced by B07c' \
|
||||
'erofs_dirent_namelen: cross-TU consumer namei.c introduced by B10' \
|
||||
'erofs_map_dev and erofs_iloc remain same-TU only; atomic B31 source edit is blocked' \
|
||||
'mtime helper remains generated with zero explicit references; ondisk constant and timestamp paths remain'
|
||||
pre15_gate_stop \
|
||||
'B31 blocked: B07c/B10 introduced cross-TU consumers for listed symbols'
|
||||
fi
|
||||
|
||||
test "$#" -eq 2 || \
|
||||
pre15_fail_usage 'B31 nm-allowlist requires zstdio0 and zstdio1 modules'
|
||||
for tool in awk diff nm; do
|
||||
command -v "$tool" >/dev/null 2>&1 || \
|
||||
pre15_infra_blocked "missing B31 nm tool: $tool"
|
||||
done
|
||||
|
||||
module0=$1
|
||||
module1=$2
|
||||
pre15_record_fixture b31-zstdio0-module "$module0"
|
||||
pre15_record_fixture b31-zstdio1-module "$module1"
|
||||
pre15_target_reached
|
||||
|
||||
for config in 0 1; do
|
||||
eval module=\$module$config
|
||||
nm -g --defined-only "$module" | awk '{ print $NF }' | LC_ALL=C sort \
|
||||
>"$artifacts/B31-zstdio$config-global-names.txt"
|
||||
nm -u "$module" | awk '{ print $NF }' | LC_ALL=C sort \
|
||||
>"$artifacts/B31-zstdio$config-undefined-names.txt"
|
||||
for symbol in erofs_map_dev erofs_map_blocks erofs_dirent_namelen erofs_iloc; do
|
||||
if ! awk -v symbol="$symbol" '$0 == symbol { found = 1 } END { exit !found }' \
|
||||
"$artifacts/B31-zstdio$config-global-names.txt"; then
|
||||
pre15_dut_fail "B31 zstdio$config lost required global $symbol"
|
||||
fi
|
||||
done
|
||||
if awk '$0 == "erofs_sb_has_mtime" { found = 1 } END { exit !found }' \
|
||||
"$artifacts/B31-zstdio$config-global-names.txt" || \
|
||||
awk '$0 == "erofs_sb_has_mtime" { found = 1 } END { exit !found }' \
|
||||
"$artifacts/B31-zstdio$config-undefined-names.txt"; then
|
||||
pre15_dut_fail "B31 zstdio$config unexpectedly emits erofs_sb_has_mtime"
|
||||
fi
|
||||
done
|
||||
|
||||
if ! diff -u "$artifacts/B31-zstdio0-global-names.txt" \
|
||||
"$artifacts/B31-zstdio1-global-names.txt" \
|
||||
>"$artifacts/B31-config-global.diff"; then
|
||||
pre15_dut_fail 'B31 defined globals differ by ZSTDIO configuration'
|
||||
fi
|
||||
|
||||
printf '%s\n' \
|
||||
'B31 nm allowlist PASS' \
|
||||
'global delta from B30 source baseline: zero' \
|
||||
'four listed symbols remain global in both configurations after mandatory STOP' \
|
||||
'erofs_sb_has_mtime is not emitted or undefined in either configuration'
|
||||
Reference in New Issue
Block a user