aboutsummaryrefslogtreecommitdiffstats
path: root/.auto/check_edit.py
diff options
context:
space:
mode:
Diffstat (limited to '.auto/check_edit.py')
-rw-r--r--.auto/check_edit.py126
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())