From 5c0b04fb20c92d552f6dc0e8d3124165c5ad2a64 Mon Sep 17 00:00:00 2001 From: blasty Date: Fri, 7 Aug 2026 00:33:12 +0200 Subject: diag: somewhere for swallowed errors to go A TUI must not die because one background load failed, so this codebase catches broadly -- ~50 `except Exception` sites, two dozen resolving to `pass`. Right policy, one bad consequence: with 44 `@work(thread=True)` workers, a failure in a background load leaves no trace whatsoever. The view stays empty and there is nothing to read afterwards, because the app owns the screen. kittygfx already solved this for itself with $IDATUI_KITTY_LOG. idatui/diag.py is the same idea for everything else: $IDATUI_LOG writes every swallowed error plus its traceback to a file, and the last 50 are kept in memory regardless so a driver can ask a live app what went wrong. Unset, it costs an environ lookup. Wired in where losing the error changes a DECISION rather than just a pixel: * rename: a resolve() that throws renames as DATA instead of as a function. * name: a function_of() that throws means we never learn the address is a function start, so the index keeps the old name and every readback says the rename didn't happen. * retype: a resolve() that throws retypes the ENCLOSING function instead. * decompile: a failed full-body fetch silently returns CLIPPED pseudocode. * trail: a failed decomp_map stops the pseudocode being painted, silently. Deliberately NOT wired into the query_one guards -- a modal owning the screen is normal and constant, and logging it would bury the real entries in noise. New RPC verb `diag {n?, clear?}`, documented in docs/RPC.md: the answer to "the verb reported success and the pane shows nothing". Also a flake, same shape as the others: follow_xrefs waited on the nav depth but asserted on _cur, and a follow pushes the source entry BEFORE opening the target -- so the check could run in between and see the function it jumped from. About one run in ten. It waits on the postcondition it asserts now; three clean full runs since. 833 checks; --fast is 344 in 3.5s. --- idatui/domain.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'idatui/domain.py') diff --git a/idatui/domain.py b/idatui/domain.py index 222f7bb..b496638 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -30,6 +30,7 @@ from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass, field, replace from typing import Callable, TYPE_CHECKING +from . import diag from .errors import IDAToolError if TYPE_CHECKING: # type hint only @@ -1605,7 +1606,10 @@ class Program: try: with urllib.request.urlopen(url, timeout=timeout) as r: return json.loads(r.read().decode("utf-8", "replace")) - except Exception: # noqa: BLE001 -- fall back to the truncated preview + except Exception as e: # noqa: BLE001 -- fall back to the truncated preview + # The user gets CLIPPED pseudocode with no indication that a fetch + # failed rather than the function genuinely being that short. + diag.note(f"decompile: full-body fetch {url}", e) return None def strings(self, min_len: int = 4, refresh: bool = False) -> list[StrLit]: -- cgit v1.3.1-sl0p