aboutsummaryrefslogtreecommitdiffstats
path: root/experiments/opfmt_tools.py
diff options
context:
space:
mode:
authorblasty <peter@haxx.in>2026-08-21 12:14:46 +0200
committerblasty <peter@haxx.in>2026-08-21 12:15:15 +0200
commit02d02417800184fb76cd0245cdaa94c437aa4081 (patch)
tree7589e6e2e8426bb7370fc56eee52d1559c9d7e57 /experiments/opfmt_tools.py
parentadopt ruff: pinned formatter + import sorting, opt-in pre-commit hook (diff)
downloadida-tui-02d02417800184fb76cd0245cdaa94c437aa4081.tar.gz
ida-tui-02d02417800184fb76cd0245cdaa94c437aa4081.tar.xz
ida-tui-02d02417800184fb76cd0245cdaa94c437aa4081.zip
reformat: ruff format + import sort, mechanically (see ruff.toml)
No behavior. Listed in .git-blame-ignore-revs (next commit).
Diffstat (limited to 'experiments/opfmt_tools.py')
-rw-r--r--experiments/opfmt_tools.py241
1 files changed, 175 insertions, 66 deletions
diff --git a/experiments/opfmt_tools.py b/experiments/opfmt_tools.py
index 535688c..7654677 100644
--- a/experiments/opfmt_tools.py
+++ b/experiments/opfmt_tools.py
@@ -8,6 +8,7 @@ does to the database, what Hex-Rays will and won't render.
python3 experiments/opfmt_tools.py # 29 checks, ~40s
"""
+
import importlib.util
import os
import shutil
@@ -30,15 +31,21 @@ if os.path.exists(seed):
shutil.copy(seed, tmp + ".i64")
import idapro # noqa: E402
+
idapro.enable_console_messages(False)
assert idapro.open_database(tmp, run_auto_analysis=True) == 0
import ida_auto # noqa: E402
+
ida_auto.auto_wait()
-import ida_typeinf, idaapi, ida_bytes, ida_lines # noqa: E402,F401
+import ida_bytes
+import ida_lines
+import ida_typeinf # noqa: E402,F401
+import idaapi
spec = importlib.util.spec_from_file_location(
- "_patch", os.path.join(REPO, "server", "patch_server.py"))
+ "_patch", os.path.join(REPO, "server", "patch_server.py")
+)
patch = importlib.util.module_from_spec(spec)
spec.loader.exec_module(patch)
@@ -101,14 +108,25 @@ while e < ea + 0x400:
if target:
break
e = ida_bytes.next_head(e, ea + 0x800)
-print("target:", hex(target[0]), "n=", target[1], "value", hex(target[2]),
- "|", line(target[0]))
+print(
+ "target:",
+ hex(target[0]),
+ "n=",
+ target[1],
+ "value",
+ hex(target[2]),
+ "|",
+ line(target[0]),
+)
tea, tn, tv = target
r = op_format(addr=hex(tea), mode="show")
print(" show:", r)
-check("show reports the operand and its choices",
- r.get("n") == tn and "hex" in r.get("choices", []), str(r))
+check(
+ "show reports the operand and its choices",
+ r.get("n") == tn and "hex" in r.get("choices", []),
+ str(r),
+)
check("show doesn't change anything", r.get("applied") is False, str(r))
seen = []
@@ -116,15 +134,25 @@ for i in range(8):
r = op_format(addr=hex(tea), mode="cycle")
seen.append((r.get("format"), r.get("text")))
print(f" cycle -> {r.get('prev')} -> {r.get('format')}: {r.get('text')}")
-check("cycling returns to where it started",
- seen[0][0] == seen[len(r.get("choices", []))][0]
- if len(seen) > len(r.get("choices", [])) else True, str(seen))
-check("decimal renders differently from hex",
- any(s[1] != seen[0][1] for s in seen), str(seen))
+check(
+ "cycling returns to where it started",
+ seen[0][0] == seen[len(r.get("choices", []))][0]
+ if len(seen) > len(r.get("choices", []))
+ else True,
+ str(seen),
+)
+check(
+ "decimal renders differently from hex",
+ any(s[1] != seen[0][1] for s in seen),
+ str(seen),
+)
r = op_format(addr=hex(tea), mode="dec")
-check("explicit dec sticks", r.get("format") == "dec" and str(tv) in r.get("text", ""),
- str(r))
+check(
+ "explicit dec sticks",
+ r.get("format") == "dec" and str(tv) in r.get("text", ""),
+ str(r),
+)
r = op_format(addr=hex(tea), mode="back")
print(" back ->", r.get("format"), r.get("text"))
check("back steps the ring the other way", r.get("format") == "hex", str(r))
@@ -142,29 +170,55 @@ while e < ea + 0x800:
if off_target:
break
e = ida_bytes.next_head(e, ea + 0x800)
-print("target:", off_target and hex(off_target[0]), "|",
- off_target and line(off_target[0]))
+print(
+ "target:",
+ off_target and hex(off_target[0]),
+ "|",
+ off_target and line(off_target[0]),
+)
if off_target:
import ida_name # noqa: E402
+
oe, on = off_target
v, _w = NS["_idatui_op_value"](oe, on)
named = bool(ida_name.get_ea_name(v))
r = op_format(addr=hex(oe), n=on, mode="show")
- print(" ", hex(oe), "n=", on, "|", r.get("text"), r.get("choices"),
- "target named:", named)
- check("an unnamed target is not a cycle stop (it would invent a name)",
- ("offset" in r.get("choices", [])) == named, str(r))
+ print(
+ " ",
+ hex(oe),
+ "n=",
+ on,
+ "|",
+ r.get("text"),
+ r.get("choices"),
+ "target named:",
+ named,
+ )
+ check(
+ "an unnamed target is not a cycle stop (it would invent a name)",
+ ("offset" in r.get("choices", [])) == named,
+ str(r),
+ )
r = op_format(addr=hex(oe), n=on, mode="offset")
print(" offset ->", r.get("text"))
- check("but asking explicitly makes the reference",
- r.get("format") == "offset" and "offset" in r.get("text", ""), str(r))
+ check(
+ "but asking explicitly makes the reference",
+ r.get("format") == "offset" and "offset" in r.get("text", ""),
+ str(r),
+ )
r = op_format(addr=hex(oe), n=on, mode="show")
- check("and from then on the ring includes it",
- "offset" in r.get("choices", []), str(r))
+ check(
+ "and from then on the ring includes it",
+ "offset" in r.get("choices", []),
+ str(r),
+ )
r = op_format(addr=hex(oe), n=on, mode="hex")
print(" hex ->", r.get("text"))
- check("and back to a number", r.get("format") == "hex"
- and "offset" not in r.get("text", ""), str(r))
+ check(
+ "and back to a number",
+ r.get("format") == "hex" and "offset" not in r.get("text", ""),
+ str(r),
+ )
op_format(addr=hex(oe), n=on, mode="default")
else:
print(" (no literal-that-is-an-address in this function)")
@@ -175,22 +229,32 @@ spans = NS["_idatui_op_spans"](tea, txt)
print(" text:", repr(txt), "spans:", spans)
if len(spans) >= 2:
r = op_format(addr=hex(tea), col=spans[0][0], mode="show")
- check("a column inside operand 0 picks operand 0 (or the first literal)",
- r.get("n") in (spans[0][2], NS["_idatui_op_candidates"](tea)[0]), str(r))
+ check(
+ "a column inside operand 0 picks operand 0 (or the first literal)",
+ r.get("n") in (spans[0][2], NS["_idatui_op_candidates"](tea)[0]),
+ str(r),
+ )
r = op_format(addr=hex(tea), col=spans[-1][0], mode="show")
- check("a column inside the last operand picks it", r.get("n") == spans[-1][2],
- str(r))
+ check(
+ "a column inside the last operand picks it", r.get("n") == spans[-1][2], str(r)
+ )
print("\n=== listing: an unmapped value refuses to become an offset ===")
r = op_format(addr=hex(tea), n=tn, mode="offset")
print(" ", r.get("error") or r)
-check("offset on a non-address is refused, not invented",
- bool(r.get("error")) or ida_bytes.is_mapped(tv), str(r))
+check(
+ "offset on a non-address is refused, not invented",
+ bool(r.get("error")) or ida_bytes.is_mapped(tv),
+ str(r),
+)
print("\n=== listing: char is only offered when it renders as one ===")
r = op_format(addr=hex(tea), n=tn, mode="show")
-check("0x%x isn't offered as a char" % tv,
- ("char" in r["choices"]) == NS["_idatui_printable"](tv), str(r))
+check(
+ "0x%x isn't offered as a char" % tv,
+ ("char" in r["choices"]) == NS["_idatui_printable"](tv),
+ str(r),
+)
print("\n=== listing: a stack variable can be cycled AND put back ===")
stk = None
@@ -208,23 +272,32 @@ if stk:
orig = line(se)
r = op_format(addr=hex(se), n=sn, mode="cycle")
print(" cycle ->", r.get("format"), r.get("text"), "|", r.get("warn"))
- check("leaving a stack variable says so, and how to undo it",
- "stack" in (r.get("warn") or ""), str(r))
- ring = [op_format(addr=hex(se), n=sn, mode="cycle")
- for _ in range(len(r["choices"]))]
+ check(
+ "leaving a stack variable says so, and how to undo it",
+ "stack" in (r.get("warn") or ""),
+ str(r),
+ )
+ ring = [
+ op_format(addr=hex(se), n=sn, mode="cycle") for _ in range(len(r["choices"]))
+ ]
print(" ring:", [(x["format"], x["text"]) for x in ring])
- check("the ring is the same at every step (a lap comes home)",
- [x["format"] for x in ring] == r["choices"][1:] + r["choices"][:1],
- f"{[x['format'] for x in ring]} vs {r['choices']}")
+ check(
+ "the ring is the same at every step (a lap comes home)",
+ [x["format"] for x in ring] == r["choices"][1:] + r["choices"][:1],
+ f"{[x['format'] for x in ring]} vs {r['choices']}",
+ )
r = op_format(addr=hex(se), n=sn, mode="stack")
- check("'stack' puts the frame variable back",
- r.get("format") == "stack" and r.get("text") == orig,
- f"{r.get('text')!r} want {orig!r}")
+ check(
+ "'stack' puts the frame variable back",
+ r.get("format") == "stack" and r.get("text") == orig,
+ f"{r.get('text')!r} want {orig!r}",
+ )
else:
print(" (no stack-variable operand found)")
print("\n=== data item ===")
import ida_segment # noqa: E402
+
seg = ida_segment.get_segm_by_name(".data")
if seg:
de = seg.start_ea
@@ -257,8 +330,11 @@ while e < seg.end_ea and ue is None:
if ue is not None:
r = op_format(addr=hex(ue), mode="cycle")
print(" ", hex(ue), "->", r.get("error"))
- check("undefined bytes are refused with the fix, not a silent no-op",
- "define" in (r.get("error") or ""), str(r))
+ check(
+ "undefined bytes are refused with the fix, not a silent no-op",
+ "define" in (r.get("error") or ""),
+ str(r),
+ )
else:
print(" (no undefined bytes)")
@@ -268,6 +344,7 @@ print(" show without a line:", r.get("error"))
check("no line is an error, not a guess", bool(r.get("error")), str(r))
import ida_hexrays # noqa: E402
+
cf = ida_hexrays.decompile(parse_address("main"))
sv = cf.get_pseudocode()
pcline = None
@@ -276,8 +353,13 @@ for i in range(len(sv)):
if nums and nums[0]["value"] > 9:
pcline = (i, nums[0])
break
-print(" line", pcline[0], repr(ida_lines.tag_remove(sv[pcline[0]].line).strip()),
- "num:", pcline[1])
+print(
+ " line",
+ pcline[0],
+ repr(ida_lines.tag_remove(sv[pcline[0]].line).strip()),
+ "num:",
+ pcline[1],
+)
i, num = pcline
r = pc_num_format(addr="main", line=i, mode="show")
check("show finds the literal", r.get("ea") == hex(num["ea"]), str(r))
@@ -293,8 +375,11 @@ print(" bin ->", r.get("error"))
check("binary is refused with a reason", bool(r.get("error")), str(r))
r = pc_num_format(addr="main", line=i, mode="default")
print(" default ->", r.get("text"))
-check("default restores Hex-Rays' own choice",
- r.get("text") == ida_lines.tag_remove(sv[i].line).strip(), str(r))
+check(
+ "default restores Hex-Rays' own choice",
+ r.get("text") == ida_lines.tag_remove(sv[i].line).strip(),
+ str(r),
+)
r = pc_num_format(addr="main", line=i, mode="show")
check("and the format reads back as default", r.get("format") == "default", str(r))
print("\n=== pseudocode: the ring visits every stop ===")
@@ -303,10 +388,16 @@ for _ in range(len(r.get("choices", [])) * 2):
rr = pc_num_format(addr="main", line=i, mode="cycle")
ring.append((rr.get("format"), rr.get("text")))
print(" ", [x[0] for x in ring])
-check("every stop in the ring is reached",
- set(x[0] for x in ring) == set(r["choices"]), f"{ring} vs {r['choices']}")
-check("the ring's renderings are distinct",
- len({x[1] for x in ring}) >= len(r["choices"]) - 1, str(ring))
+check(
+ "every stop in the ring is reached",
+ set(x[0] for x in ring) == set(r["choices"]),
+ f"{ring} vs {r['choices']}",
+)
+check(
+ "the ring's renderings are distinct",
+ len({x[1] for x in ring}) >= len(r["choices"]) - 1,
+ str(ring),
+)
pc_num_format(addr="main", line=i, mode="default")
print("\n=== pseudocode: col picks the literal ===")
@@ -327,9 +418,12 @@ if multi:
col = len(NS["_idatui_compact"](plain[:x]).rstrip()) if x else 0
r = pc_num_format(addr="main", line=k, col=col, mode="show")
print(" col", col, "->", r.get("ea"), r.get("value"))
- check("a column selects the number under it",
- r.get("value") == hex(nums[1]["value"])
- or r.get("value") == hex(nums[0]["value"]), str(r))
+ check(
+ "a column selects the number under it",
+ r.get("value") == hex(nums[1]["value"])
+ or r.get("value") == hex(nums[0]["value"]),
+ str(r),
+ )
else:
print(" (no line with two literals)")
@@ -342,8 +436,11 @@ if row and row.get("ops"):
t = row["text"]
for lo, hi, n in row["ops"]:
print(f" op{n}: {t[lo:hi]!r}")
- check("the extents index the row's own text",
- all(t[lo:hi].strip() for lo, hi, n in row["ops"]), str(row["ops"]))
+ check(
+ "the extents index the row's own text",
+ all(t[lo:hi].strip() for lo, hi, n in row["ops"]),
+ str(row["ops"]),
+ )
# and they agree with what op_format picks for a column inside them
ok = True
for lo, hi, n in row["ops"]:
@@ -361,8 +458,11 @@ if regop:
lo, hi, n = regop
r = op_format(addr=hex(tea), col=(lo + hi) // 2, mode="cycle")
print(" ", repr(row["text"][lo:hi]), "->", r.get("error"))
- check("it names the operand and the one that CAN change",
- bool(r.get("error")) and "operand" in r["error"], str(r))
+ check(
+ "it names the operand and the one that CAN change",
+ bool(r.get("error")) and "operand" in r["error"],
+ str(r),
+ )
else:
print(" (this instruction has no register-only operand)")
@@ -376,15 +476,24 @@ for rec in pn["nums"]:
two = next((v for v in multi2.values() if len(v) >= 2), None)
if two:
import ida_hexrays as _hx
+
cf2 = _hx.decompile(parse_address("main"))
- disp = NS["_idatui_compact"](ida_lines.tag_remove(cf2.get_pseudocode()[two[0]["line"]].line))
+ disp = NS["_idatui_compact"](
+ ida_lines.tag_remove(cf2.get_pseudocode()[two[0]["line"]].line)
+ )
print(" line:", repr(disp.strip()))
for rec in two:
- print(f" x{rec['x0']}..{rec['x1']} = {disp[rec['x0']:rec['x1']]!r} value {rec['value']}")
- check("spans land on the literals in the DISPLAYED text",
- all(disp[r0["x0"]:r0["x1"]].strip() for r0 in two), str(two))
- check("distinct literals get distinct spans",
- two[0]["x0"] != two[1]["x0"], str(two))
+ print(
+ f" x{rec['x0']}..{rec['x1']} = {disp[rec['x0'] : rec['x1']]!r} value {rec['value']}"
+ )
+ check(
+ "spans land on the literals in the DISPLAYED text",
+ all(disp[r0["x0"] : r0["x1"]].strip() for r0 in two),
+ str(two),
+ )
+ check(
+ "distinct literals get distinct spans", two[0]["x0"] != two[1]["x0"], str(two)
+ )
print(f"\n{OK} passed, {FAIL} failed")
idapro.close_database(save=False)