#!/usr/bin/env python3 """Structural checks on the shim.cfg.* templates. Lane BP found two defects in `shim.cfg.cbpin` while copying it for a determinism probe, and its own first draft reproduced one of them -- which is what a trap looks like. Both are checked here: * A **wrapped header comment landing on a live setting.** `cbpin`'s first line ran on past the comment and left `aiseed.values=...\\u0060.` as a real setting ahead of the real one at the bottom, so the file contradicted its own "filled in per run" note. * A **key set twice.** The last one silently wins, which is exactly how a stale value hides in a config nobody re-reads. There is deliberately **no global check that a template names every registered hook**. `hooks=trace` means "trace everything except what is turned off", and most templates rely on that on purpose -- an exhaustiveness rule applied to all of them would be a rule about a style nobody agreed to. It matters only where a lane wants a *minimal* hook set, and no file states that intent, so a template opts in by carrying a line reading exactly `# exhaustive` in its header. For those, an unnamed hook is a detour the config did not ask for, and on a determinism probe an extra detour is precisely what rule 19 says can move the bytes. Exits non-zero with the offending file and what is wrong with it. """ import pathlib import re import sys ROOT = pathlib.Path(__file__).resolve().parent.parent SRC = ROOT / "src" / "shim" # A hook declares itself with `static constexpr const char* name = "...";` NAME_RE = re.compile(r'static\s+constexpr\s+const\s+char\*\s+name\s*=\s*"([^"]+)"') HOOK_RE = re.compile(r"^hook\.([^=]+)=") # Not real hooks: the doc-comment's example in trace/hook.h, and the self-test hooks, # which exist to make the harness fail on purpose and are never part of a measurement. EXEMPT = {"Game::Foo"} EXEMPT_PREFIXES = ("Shim::SelfTest::",) # Settings whose values legitimately end in a period or a path separator. PROSE_SAFE = ("path", "out", "dir", "file") def registered_hooks(): names = set() for path in SRC.rglob("*"): if path.suffix in (".h", ".cpp"): for m in NAME_RE.finditer(path.read_text(encoding="utf-8", errors="replace")): names.add(m.group(1)) return {n for n in names if n not in EXEMPT and not n.startswith(EXEMPT_PREFIXES)} def check(cfg, hooks): problems = [] lines = cfg.read_text(encoding="utf-8", errors="replace").splitlines() settings = [l for l in lines if l.strip() and not l.lstrip().startswith("#")] for l in settings: key = l.split("=", 1)[0].strip() if "=" not in l: problems.append(f"line is neither a comment nor a setting: {l!r}") elif l.rstrip().endswith(("`", "`.")) or ( l.rstrip().endswith(".") and not any(p in key.lower() for p in PROSE_SAFE) ): problems.append(f"setting looks like a wrapped comment: {l!r}") seen = {} for l in settings: key = l.split("=", 1)[0].strip() if key in seen: problems.append(f"{key!r} set twice ({seen[key]!r} then {l!r})") seen[key] = l if any(l.strip() == "# exhaustive" for l in lines): if not any(l.strip() == "hooks=trace" for l in settings): problems.append("declares `# exhaustive` but does not set `hooks=trace`, so it does nothing") named = {m.group(1) for m in (HOOK_RE.match(l) for l in settings) if m} missing = sorted(hooks - named) if missing: problems.append( f"declares `# exhaustive` but does not name {len(missing)} registered hook(s), " "which therefore default ON: " + ", ".join(missing) ) return problems def main(): hooks = registered_hooks() if not hooks: print("check_shim_configs: found no registered hooks -- the parser is wrong, not the configs") return 1 failures = [] exhaustive = 0 for cfg in sorted(SRC.glob("shim.cfg.*")): if any(l.strip() == "# exhaustive" for l in cfg.read_text(errors="replace").splitlines()): exhaustive += 1 for p in check(cfg, hooks): failures.append(f"{cfg.name}: {p}") if failures: print("check_shim_configs: FAILED") for f in failures: print(" " + f) return 1 print(f"check_shim_configs: OK ({len(hooks)} registered hooks, {exhaustive} template(s) declared exhaustive)") return 0 if __name__ == "__main__": sys.exit(main())