diff options
| author | blasty <blasty@local> | 2026-07-10 15:29:21 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-10 15:29:21 +0200 |
| commit | 9583334d0d413d8f4962b5d8fa1eccdbc196dd33 (patch) | |
| tree | 13ed9424c875dcf98d1323f573d19e35c139ef1b /tests | |
| parent | docs: RPC protocol reference (docs/RPC.md) (diff) | |
| download | ida-tui-9583334d0d413d8f4962b5d8fa1eccdbc196dd33.tar.gz ida-tui-9583334d0d413d8f4962b5d8fa1eccdbc196dd33.tar.xz ida-tui-9583334d0d413d8f4962b5d8fa1eccdbc196dd33.zip | |
rpc: more verbs — structured reads, modal select, in-view search, save
Introspection (offloaded to a thread so a big fetch can't freeze the
render): pseudocode (full body), disassembly (bounded), xrefs_to/from
(structured), resolve (name->ea). These let an agent reason about a
function and the call graph without scraping screen().
Semantic: select (choose the highlighted/nth item in the open xrefs or
symbol-palette list and activate it — the natural follow-up to xrefs/
symbols), search (/ or ? incremental in the active code view), save
(Ctrl+S). rpc_smoke exercises all of them end-to-end (18 green).
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/rpc_smoke.py | 56 |
1 files changed, 56 insertions, 0 deletions
diff --git a/tests/rpc_smoke.py b/tests/rpc_smoke.py index d456552..01418b8 100644 --- a/tests/rpc_smoke.py +++ b/tests/rpc_smoke.py @@ -144,6 +144,62 @@ async def run(db): else: check("found the function name to rename", False, repr(line0_text[:60])) + # --- structured introspection ------------------------------------- # + resp = await c.call("resolve", name=target["name"]) + check("resolve maps a name to its ea", + resp.get("result", {}).get("ea") == target["ea"], str(resp.get("result"))) + + resp = await c.call("pseudocode", target=target["name"]) + pc = resp.get("result", {}) + check("pseudocode returns the full body", + isinstance(pc.get("code"), str) and len(pc["code"]) > 0 and not pc["failed"], + f"failed={pc.get('failed')} len={len(pc.get('code') or '')}") + + resp = await c.call("disassembly", target=target["name"], max=50) + da = resp.get("result", {}) + check("disassembly returns lines with addresses", + isinstance(da.get("lines"), list) and len(da["lines"]) > 0 + and all("ea" in ln and "text" in ln for ln in da["lines"]), + f"total={da.get('total')} n={len(da.get('lines', []))}") + + # a function that is actually referenced, so xrefs_to is non-empty + callee = None + for f in funcs: + xr = (await c.call("xrefs_to", target=f["name"], limit=5)).get("result", []) + if xr: + callee = (f, xr) + break + check("xrefs_to returns structured references", + callee is not None and all("frm" in x for x in callee[1]), + "no referenced function found" if callee is None else "") + + # --- modal select: open xrefs on the callee, pick the first site --- # + if callee is not None: + f, xr = callee + await c.call("goto", target=f["name"], delay_ms=0) + # place the cursor on the function name so xrefs targets it + v = (await c.call("view", lines=1))["result"] + lt = v["lines"][0]["text"] if v.get("lines") else "" + col = lt.find(f["name"]) + if col >= 0: + await c.call("cursor", line=0, col=col + 1) + resp = await c.call("xrefs") + m = resp.get("result", {}).get("modal") or {} + check("xrefs opens the picker with items", + m.get("kind") == "XrefsScreen" and len(m.get("items", [])) > 0, + str(m)[:80]) + resp = await c.call("select", index=0) + check("select follows a picked xref (modal closes, we navigate)", + (resp.get("result", {}).get("modal") is None), str(resp.get("result", {}).get("modal"))) + + # --- in-view search ----------------------------------------------- # + await c.call("goto", target=target["name"], delay_ms=0) + resp = await c.call("search", term="return", delay_ms=0) + st = resp.get("result", {}) + check("search runs without error and returns state", + st.get("active") in ("decomp", "disasm"), str(st.get("active"))) + await c.call("keys", keys=["escape"]) + w.close() print(f"\n{PASS} passed, {FAIL} failed") return 1 if FAIL else 0 |
