fix(cpg): report the line that names an engine failure, not its frames

Every PHP failure logged `cpg batch taint failed: dure1.java:10)`, which is the
middle of a stack frame and says nothing. A trace puts its exception on the FIRST
line and frames after it, and the backend logged the last 400 characters, so the
cause was discarded and the noise kept. Four investigations were spent on that.

Failures now report the leading non-frame lines, which is where the exception and
its message are, with the frames dropped. A control asserts an OutOfMemoryError
survives while a frame does not, and that a timeout and an empty exit stay
distinguishable.

1,411 tests passed, nine skipped.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-18 17:58:42 +02:00
co-authored by Claude Opus 5
parent dfc29238b5
commit 6089c67bf6
2 changed files with 32 additions and 2 deletions
+19 -2
View File
@@ -1129,7 +1129,7 @@ class JoernBackend:
command += ["--param", f"{name}={value}"]
completed = self._run(command, timeout=self.query_timeout, cwd=cpg_path.parent)
if completed is None or completed.returncode != 0:
detail = (completed.stderr or "")[-400:] if completed is not None else "timeout"
detail = _engine_failure(completed)
logger.warning("cpg batch %s failed: %s", query, detail)
return None
body = completed.stdout or ""
@@ -1162,7 +1162,7 @@ class JoernBackend:
# it can never land in the repository being analysed.
completed = self._run(command, timeout=self.query_timeout, cwd=cpg_path.parent)
if completed is None or completed.returncode != 0:
detail = (completed.stderr or "")[-400:] if completed is not None else "timeout"
detail = _engine_failure(completed)
logger.warning("cpg query %s failed: %s", query, detail)
return None
body = completed.stdout or ""
@@ -1541,6 +1541,23 @@ def _configured_timeout() -> int:
return QUERY_TIMEOUT_SECONDS
def _engine_failure(completed: subprocess.CompletedProcess[str] | None) -> str:
"""What the engine said, keeping the line that names the cause.
A stack trace's exception is on its FIRST line and its frames follow, so logging only the tail
reports frames and discards the cause. Four PHP investigations were spent on `cpg batch taint
failed: dure1.java:10)`, which is the middle of a frame and says nothing at all.
"""
if completed is None:
return "timeout"
text = (completed.stderr or completed.stdout or "").strip()
if not text:
return f"exit {completed.returncode} with no output"
lines = [line for line in text.splitlines() if line.strip() and not line.strip().startswith("at ")]
head = " | ".join(lines[:4])[:600]
return head or text[:300]
def _render(value: object) -> str:
"""CPGQL parameters are strings; a sequence becomes a comma-separated list the script splits."""
if isinstance(value, str):