aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-08-07 05:25:58 +0200
committerblasty <blasty@local>2026-08-07 05:25:58 +0200
commit494b465d6f06441abf7208238ec706bb3c4ed492 (patch)
tree7d3cd548461f47dcd7ed5a1e0cb68a2c5f3e230b
parentdecomp_map: stop sweeping every column three times over. It allocated three c... (diff)
downloadida-tui-494b465d6f06441abf7208238ec706bb3c4ed492.tar.gz
ida-tui-494b465d6f06441abf7208238ec706bb3c4ed492.tar.xz
ida-tui-494b465d6f06441abf7208238ec706bb3c4ed492.zip
bench: cover a rename and the listing's recovery from it
-rw-r--r--.auto/bench.py66
1 files changed, 66 insertions, 0 deletions
diff --git a/.auto/bench.py b/.auto/bench.py
index 4074838..b4f5793 100644
--- a/.auto/bench.py
+++ b/.auto/bench.py
@@ -322,6 +322,71 @@ async def phase_split(app, pilot, funcs):
note("split_mapped_lines", mapped)
+async def phase_rename(app, pilot, funcs):
+ """Rename a function and get the listing back — the commonest RE operation.
+
+ A rename invalidates cached names everywhere, and the question this measures
+ is what the *listing* then costs: the cursor has to land back on the same row
+ with the new name showing. Renames are undone afterwards so the .i64 the
+ bench stages from is never left edited.
+ """
+ lst = app.query_one(ListingView)
+ ed = app.program
+ done = 0
+ spent = 0.0
+ tag = f"_bench_{os.getpid()}"
+ for k, fn in enumerate(funcs[:6]):
+ app._open_function(fn.addr, fn.name)
+ if not await _wait(pilot, lambda fn=fn: app._cur is not None
+ and app._cur.ea == fn.addr and lst.total > 0
+ and lst._cursor_ea() == fn.addr, 120):
+ fail(f"rename:open:{fn.name}")
+ continue
+ row = lst.cursor
+ new = f"{tag}_{k}"
+ t0 = time.perf_counter()
+ try:
+ ed.client.call("rename",
+ batch={"func": [{"addr": hex(fn.addr), "name": new}]})
+ except Exception: # noqa: BLE001
+ fail(f"rename:call:{fn.name}")
+ continue
+ ed.bump_names()
+ # What the app does next: reopen the listing where it was and paint it.
+ lm = ed.listing(fn.addr)
+ idx = max(lm.ensure_ea(fn.addr), 0) if lm is not None else 0
+ lst.load(lm, new, cursor=idx, scroll_y=max(idx - 6, 0))
+ got = await _wait(pilot, lambda fn=fn: lst.total > 0
+ and lst._cursor_ea() == fn.addr, 120)
+ await pilot.pause(0)
+ _paint(lst)
+ spent += (time.perf_counter() - t0) * 1000
+ # The name lives on the `proc` banner row, a few rows above the code row
+ # ensure_ea lands on (banner rows aren't address-indexed), so look at the
+ # window rather than the single row.
+ shows_new = False
+ if lm is not None:
+ for k2 in range(max(idx - 4, 0), idx + 2):
+ h = lm.get(k2)
+ if h is not None and (new in (h.text or "")
+ or new == (h.name or "")):
+ shows_new = True
+ break
+ if not (got and idx == row and shows_new):
+ fail(f"rename:{fn.name}:row={idx}/{row} shows_new={shows_new}")
+ else:
+ done += 1
+ try: # put it back, whatever happened above
+ ed.client.call("rename",
+ batch={"func": [{"addr": hex(fn.addr),
+ "name": fn.name}]})
+ ed.bump_names()
+ except Exception: # noqa: BLE001
+ fail(f"rename:undo:{fn.name}")
+ TIMES.setdefault(PREFIX + "rename_ms", []).append(spent)
+ note("rename_ok", done)
+
+
async def phase_search(app, pilot, funcs, terms):
"""The real incremental-search path: search_begin, then one search_update per
typed character, exactly as the Input's on_changed drives it."""
@@ -500,6 +565,7 @@ async def run_target(binary, reps, nfuncs, pages, frames, terms, skip=0):
await phase_decomp(app, pilot, big)
await phase_graph(app, pilot, big)
if first:
+ await phase_rename(app, pilot, big)
await phase_split(app, pilot, big)
await phase_search(app, pilot, big, terms)
await phase_index(app, pilot, big)