aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-09 20:48:19 +0200
committerblasty <blasty@local>2026-07-09 20:48:19 +0200
commitb8d9f1f06090fb96464304c84dcbd0872e17e873 (patch)
tree6f7b733f6390aae4e3c67bb9a4f4df84993afcb5
parenttest: --stop-after for fast single-feature iteration; skip needless decompile (diff)
downloadida-tui-b8d9f1f06090fb96464304c84dcbd0872e17e873.tar.gz
ida-tui-b8d9f1f06090fb96464304c84dcbd0872e17e873.tar.xz
ida-tui-b8d9f1f06090fb96464304c84dcbd0872e17e873.zip
rename: refuse pseudocode goto-labels with a clear message
Pressing 'n' on a Hex-Rays goto label (LABEL_n, or any 'goto TARGET') in the decompiler routed to a 'local' rename, which the API rejects with a misleading "Local variable 'LABEL_114' not found" — the user just saw a cryptic failure. The rename tool has no label category (only func/global/local/stack) and idalib exposes no other way to rename pseudocode labels. Detect a label token under the cursor (_is_pseudocode_label) and report the limitation up front instead of opening a prompt that's doomed to fail. Add a pilot check (uses main's labels when the current function has none). full suite 61/61.
-rw-r--r--idatui/app.py19
-rw-r--r--tests/test_tui.py37
2 files changed, 56 insertions, 0 deletions
diff --git a/idatui/app.py b/idatui/app.py
index d1f3e01..0bd6a7c 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -1437,10 +1437,29 @@ class IdaTui(App):
self._goto_ea(addr, push=True)
# -- rename ------------------------------------------------------------ #
+ @staticmethod
+ def _is_pseudocode_label(view, name: str) -> bool:
+ """True if ``name`` is a Hex-Rays goto label in ``view``. The rename tool
+ has no label category (only func/global/local/stack), so renaming one
+ fails with a misleading 'local variable not found'; detect it up front
+ and explain instead. A label is the default ``LABEL_n`` or any token used
+ as a ``goto`` target."""
+ if not isinstance(view, DecompView):
+ return False
+ if re.fullmatch(r"LABEL_\d+", name):
+ return True
+ body = "\n".join(getattr(view, "_texts", []) or [])
+ return re.search(rf"\bgoto\s+{re.escape(name)}\b", body) is not None
+
def on_rename_requested(self, msg: RenameRequested) -> None:
if not msg.name:
self._status("nothing to rename under the cursor")
return
+ if self._is_pseudocode_label(msg.view, msg.name):
+ self._status(
+ f"can't rename pseudocode label '{msg.name}' "
+ "(Hex-Rays goto labels aren't renamable via the API)")
+ return
self._rename_ctx = (msg.view, msg.name)
self.query_one("#status", Static).display = False
inp = self.query_one("#rename", Input)
diff --git a/tests/test_tui.py b/tests/test_tui.py
index f74863a..ca5cea9 100644
--- a/tests/test_tui.py
+++ b/tests/test_tui.py
@@ -592,6 +592,43 @@ async def run(db):
check("rename reverted cleanly",
rr.get("summary", {}).get("ok", 0) == 1, str(rr.get("summary")))
+ # A Hex-Rays goto label has no rename API; pressing 'n' on one must be
+ # refused up front with a clear message, not open a prompt that then
+ # fails with a misleading 'local variable not found'.
+ def _find_label():
+ for i, t in enumerate(dec._texts):
+ mm = re.search(r"\bLABEL_\d+\b", t)
+ if mm:
+ return i, mm.start(), mm.group(0)
+ return None
+ lab = _find_label()
+ if lab is None: # current func has none; main reliably has labels
+ await pilot.press("g")
+ await pilot.pause(0.1)
+ for ch in "main":
+ await pilot.press(ch)
+ await pilot.press("enter")
+ await wait_until(pilot, lambda: app._cur and app._cur.name == "main", 20)
+ if app._active != "decomp":
+ await pilot.press("tab")
+ await wait_until(pilot, lambda: dec.loaded_ea == app._cur.ea, 25)
+ lab = _find_label()
+ if lab is not None:
+ li, lc, ltok = lab
+ dec.focus()
+ dec.cursor, dec.cursor_x = li, lc + 1
+ dec.refresh()
+ await pilot.pause(0.05)
+ await pilot.press("n")
+ await pilot.pause(0.1)
+ ri2 = app.query_one("#rename", Input)
+ st = str(app.query_one("#status").render())
+ check("renaming a pseudocode label is refused with a clear message",
+ (not ri2.display) and "label" in st.lower(),
+ f"display={ri2.display} status={st!r}")
+ else:
+ check("found a pseudocode label to test", False, "no LABEL_ found")
+
# Decompiler follow of a name NOT in refs (the function's own name).
await pilot.press("g")
await pilot.pause(0.1)