From b8d9f1f06090fb96464304c84dcbd0872e17e873 Mon Sep 17 00:00:00 2001 From: blasty Date: Thu, 9 Jul 2026 20:48:19 +0200 Subject: rename: refuse pseudocode goto-labels with a clear message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- idatui/app.py | 19 +++++++++++++++++++ tests/test_tui.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) 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) -- cgit v1.3.1-sl0p