diff options
Diffstat (limited to '.auto/check_edit.py')
| -rw-r--r-- | .auto/check_edit.py | 126 |
1 files changed, 0 insertions, 126 deletions
diff --git a/.auto/check_edit.py b/.auto/check_edit.py deleted file mode 100644 index 2472c84..0000000 --- a/.auto/check_edit.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 -"""Correctness gate for the listing after an item edit (run by .auto/checks.sh). - -An item edit (`c`/`d`/`u`/`p`) changes structure, but only locally: every head -in front of it keeps its address and its row number. So `Program.bump_items(ea)` -keeps the walk up to there instead of discarding the model — worth 257x on a big -binary (4.9s to make one byte into data, against 19ms). - -Keeping *anything* across a structural edit is the risky half of that, and it -fails silently: the pane shows rows that are no longer what the database says. -So this drives real edits and compares the kept model against one built from -scratch, row for row — narrow reads (what painting does) and wide ones (what -building the search body does). - -The staged database is a throwaway copy and is never saved, so the edits here do -not need undoing and can be as destructive as they like. - - ~/ida-venv/bin/python .auto/check_edit.py [targets/echo] -""" -from __future__ import annotations - -import os -import shutil -import sys - -HERE = os.path.dirname(os.path.abspath(__file__)) -ROOT = os.path.dirname(HERE) -sys.path.insert(0, ROOT) -sys.path.insert(0, HERE) - -from bench import stage # noqa: E402 -from idatui.domain import Program # noqa: E402 -from idatui.worker_client import WorkerClient # noqa: E402 - - -def snapshot(model, total: int, wide: bool): - if wide: - rows = [] - for base in range(0, total, 4096): - rows.extend(model.window(base, min(4096, total - base))) - else: - rows = [model.get(i) for i in range(total)] - return [(h.ea, h.kind, h.size, h.text, h.name) if h else None for h in rows] - - -def main() -> int: - target = sys.argv[1] if len(sys.argv) > 1 else "targets/echo" - d, path = stage(os.path.join(ROOT, target)) - fails: list[str] = [] - client = WorkerClient(path) - try: - prog = Program(client) - idx = prog.functions() - idx.load_all() - funcs = sorted(idx.all_loaded(), key=lambda f: f.addr) - if len(funcs) < 8: - print(f"{target}: too few functions to check") - return 1 - seg = funcs[len(funcs) // 2].addr - model = prog.listing(seg) - model.load_all() - total = len(model) - - # Edits at a spread of positions: near the end (where truncation keeps - # nearly everything), the middle, and early on (where it must give up). - spots = [funcs[int(len(funcs) * f)].addr for f in (0.9, 0.5, 0.05)] - for k, ea in enumerate(spots): - # Undefine, at three sizes. This is the edit that can coalesce - # BACKWARDS into the undefined run in front of it, which is the - # reason truncate_from drops two pages rather than one. - size = (1, 4, 16)[k % 3] - kind = f"undefine {size}B" - try: - prog.undefine(ea, size) - except Exception as e: # noqa: BLE001 - fails.append(f"{kind} at {ea:#x} failed: {e}") - continue - prog.bump_items(ea) - - kept = prog.listing(seg) - kept_total = None - got_wide = None - if kept is not None: - kept.load_all() - kept_total = len(kept) - got_wide = snapshot(kept, kept_total, wide=True) - got_narrow = snapshot(kept, kept_total, wide=False) - - # ... against a model that knows nothing about what came before. - prog._listings.clear() - fresh = prog.listing(seg) - fresh.load_all() - want = snapshot(fresh, len(fresh), wide=False) - - if kept_total != len(fresh): - fails.append(f"{kind} at {ea:#x}: kept model has {kept_total} " - f"rows, a rebuild has {len(fresh)}") - elif got_narrow != want or got_wide != want: - which = "narrow" if got_narrow != want else "wide" - bad = next((i for i, (a, b) in - enumerate(zip(got_narrow if which == "narrow" - else got_wide, want)) if a != b), None) - fails.append( - f"{kind} at {ea:#x}: {which} read differs from a rebuild at " - f"row {bad}: {(got_narrow if which == 'narrow' else got_wide)[bad]}" - f" vs {want[bad]}") - - model = prog.listing(seg) - model.load_all() - total = len(model) - - print(f"item edits: {len(spots)} edits checked against a rebuild " - f"({total} rows), {len(fails)} problems") - for f in fails: - print(" FAIL", f) - return 1 if fails else 0 - finally: - try: - client.close() - except Exception: # noqa: BLE001 - pass - shutil.rmtree(d, ignore_errors=True) - - -if __name__ == "__main__": - sys.exit(main()) |
