mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
fix(cpg): carry the engine's own account when a session request fails (M2)
The session-enabled NodeGoat run reported session_query_failed:census: session_request_failed and fell back, which is correct behaviour but says nothing about why. A bare transport failure cannot distinguish a timeout from a crash from an out-of-memory kill, and the only account of which it was is in the server's own log. A transport failure now carries the exception and the tail of that log, and the backend logs it with the process exit code alongside its own label. A control asserts an OutOfMemoryError in the log reaches the caller. This is diagnosis, not a repair: the run's timings were unchanged because the session died early and everything fell back to the disposable path. 1,397 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:
co-authored by
Claude Opus 5
parent
fc69055a32
commit
d2362cd18d
@@ -1163,7 +1163,11 @@ class JoernBackend:
|
||||
arguments = ", ".join(f"{name} = {json.dumps(_render(value))}" for name, value in sorted(params.items()))
|
||||
answer = session.evaluate(f"{symbol}({arguments})", timeout=self.query_timeout)
|
||||
if answer is None:
|
||||
# Carry the engine's own account, not just our label: a bare `session_request_failed`
|
||||
# cannot distinguish a timeout from a crash from an out-of-memory kill.
|
||||
detail = (session.last_body or session.log_tail())[-500:].replace("\n", " ")
|
||||
self._note("session_query_failed:" + query + ":" + (session.failure or "unknown"))
|
||||
logger.warning("session query %s failed (exit=%s): %s", query, session.exit_code(), detail)
|
||||
return None
|
||||
return answer.stdout
|
||||
|
||||
|
||||
@@ -87,6 +87,17 @@ class EngineSession:
|
||||
def alive(self) -> bool:
|
||||
return not self.failure and self._process is not None and self._process.poll() is None
|
||||
|
||||
def log_tail(self, limit: int = 2000) -> str:
|
||||
"""The end of the server's own log, which is where a crash or an OOM kill is recorded."""
|
||||
try:
|
||||
data = (self.scratch / "session.log").read_bytes()
|
||||
except OSError:
|
||||
return ""
|
||||
return data[-limit:].decode("utf-8", "replace")
|
||||
|
||||
def exit_code(self) -> int | None:
|
||||
return self._process.poll() if self._process is not None else None
|
||||
|
||||
def _poison(self, reason: str) -> None:
|
||||
if not self.failure:
|
||||
self.failure = reason
|
||||
@@ -143,8 +154,11 @@ class EngineSession:
|
||||
try:
|
||||
with urllib.request.urlopen(call, timeout=timeout) as response: # noqa: S310 -- fixed loopback URL
|
||||
body = json.load(response)
|
||||
except (urllib.error.URLError, TimeoutError, OSError, ValueError):
|
||||
self.last_body = ""
|
||||
except (urllib.error.URLError, TimeoutError, OSError, ValueError) as error:
|
||||
# A transport failure usually means the server is gone, and the only account of why is
|
||||
# in its own log. Without this the caller sees `session_request_failed` and nothing else,
|
||||
# which is indistinguishable from a timeout, a crash and an out-of-memory kill.
|
||||
self.last_body = f"{type(error).__name__}: {error}\n{self.log_tail()}"
|
||||
return None
|
||||
if not isinstance(body, dict):
|
||||
self.last_body = ""
|
||||
|
||||
@@ -278,3 +278,15 @@ def test_the_session_switch_is_explicit_and_defaults_off(monkeypatch):
|
||||
assert JoernBackend().session_transport is True
|
||||
# An explicit argument still wins over the environment in both directions.
|
||||
assert JoernBackend(session_transport=False).session_transport is False
|
||||
|
||||
|
||||
def test_a_transport_failure_carries_the_server_log(tmp_path, fake):
|
||||
"""M2: `session_request_failed` alone cannot tell a crash from an OOM kill."""
|
||||
with session(tmp_path, fake) as live:
|
||||
assert live.start()
|
||||
(tmp_path / "scratch" / "session.log").write_text("java.lang.OutOfMemoryError: Java heap space\n")
|
||||
live._port = 1 # nothing is listening there
|
||||
assert live.evaluate("cpg.file.size", timeout=2) is None
|
||||
assert live.failure == "session_request_failed"
|
||||
assert "OutOfMemoryError" in live.last_body
|
||||
assert "OutOfMemoryError" in live.log_tail()
|
||||
|
||||
Reference in New Issue
Block a user