aboutsummaryrefslogtreecommitdiffstats
path: root/idatui
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-08-07 14:32:56 +0200
committerblasty <blasty@local>2026-08-07 14:32:56 +0200
commit21971d0a9ce9b8a2fb1db2ec67303696cea9cbbb (patch)
tree7122697940630116eae1b7e75756b68292cfe9b9 /idatui
parentcodemode: three defects the A/B benchmark found in the decompiler path (diff)
downloadida-tui-21971d0a9ce9b8a2fb1db2ec67303696cea9cbbb.tar.gz
ida-tui-21971d0a9ce9b8a2fb1db2ec67303696cea9cbbb.tar.xz
ida-tui-21971d0a9ce9b8a2fb1db2ec67303696cea9cbbb.zip
tests: blob_ui 39.8s -> 4.1s, and a fatal it was hiding
Three separate wastes, all of the same family: waiting on a guess instead of a signal, and paying for work that never had to be repeated. 1. The 64KB blob was built with os.urandom into a fresh TemporaryDirectory on every run. New bytes at a new path means the pristine-database cache can never apply, so full auto-analysis of 64KB of AArch64-decoded noise was paid every single run. It is now built from a seeded PRNG at a stable path (tests/.synthetic/, gitignored) and staged through the existing cache. Determinism is also a correctness fix: whether 64KB of chance bytes contains something IDA reads as a function is luck, and this suite asserts "and really has no functions". 2. `wait(lambda: lst.model is not old, ..., 30)` after commenting. The perf work made an item edit KEEP the listing's walk and re-render in place, so the model object is never replaced and this waited out its full 30s timeout on every run -- and then "commenting leaves the view where it was" passed vacuously, because nothing had happened at all. A test that burns 30s to check nothing is worse than no test. 3. Two `pause(2.0)`/`pause(2.5)` after a carve, replaced with settle() on a real condition. The second one deliberately has NO predicate: that spot is random data, so the carve may legitimately produce nothing, and "the row became code" would never hold -- gating on it cost another 30s timeout. What that check is about is the VIEW not moving, so the gate is "the app finished reacting". Fixing (1) exposed a real bug in the client, fixed here too: reopening a database that already exists while passing loader switches is FATAL in IDA -- FATAL ERROR: Switch '-b400' can be used only when loading a new file which kills the worker before it can report anything. Loader switches describe an IMPORT and are recorded in the database they produce, so they are now sent only when there is an import to describe. This was never reachable from the old suite (a fresh random blob never had a database to reopen), but it is reachable by any user who opens a raw blob with --ida-args twice. 30 passed, 0 failed.
Diffstat (limited to 'idatui')
-rw-r--r--idatui/codemode_client.py25
1 files changed, 22 insertions, 3 deletions
diff --git a/idatui/codemode_client.py b/idatui/codemode_client.py
index 3bf01cc..9d91335 100644
--- a/idatui/codemode_client.py
+++ b/idatui/codemode_client.py
@@ -1062,6 +1062,17 @@ class CodeModeClient:
self._last_entry: RegistryEntry | None = None
self._connect_lock = threading.Lock()
+ def _database_exists(self) -> bool:
+ """Whether the IDB this open would target is already on disk.
+
+ Its loader switches are baked in, so they must not be sent again.
+ """
+ try:
+ target = self._output_database or expected_idb_path(self._path)
+ except Exception: # noqa: BLE001 -- resolver unavailable: assume fresh
+ return False
+ return bool(target) and os.path.exists(target)
+
def connect(self, timeout: float = 1800.0, progress=None) -> "CodeModeClient":
_require_codemode()
with self._connect_lock:
@@ -1078,17 +1089,25 @@ class CodeModeClient:
deadline = time.monotonic() + min(timeout, 60.0)
while True:
try:
+ # Loader switches describe how to IMPORT a raw file and
+ # are recorded in the database it produces. Sending them
+ # again for a database that already exists is a FATAL
+ # error in IDA itself ("Switch '-b400' can be used only
+ # when loading a new file"), which kills the worker
+ # before it can report anything useful. So: describe the
+ # import only when there is an import to describe.
+ fresh = self._new_database or not self._database_exists()
handle = DatabaseHandle.open(
self._path,
spawn=self._spawn,
timeout=max(0.1, timeout),
output_database=self._output_database,
- processor=self._processor,
+ processor=self._processor if fresh else None,
# DatabaseHandle calls this image_base and wants the
# natural (16-byte aligned) address; it does the
# conversion to IDA's paragraph-based -b itself.
- image_base=self._loading_address,
- file_type=self._file_type,
+ image_base=self._loading_address if fresh else None,
+ file_type=self._file_type if fresh else None,
new_database=self._new_database,
)
break