mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-17 03:59:28 +00:00
fix(bench,cpg): the position benchmark builds in the recipe's language and reports per repository; a doubly-failed build disposes its scratch
WP Statistics's first region is JavaScript, and a build asked for as JavaScript went looking for jssrc2cpg, failed both launchers, lost the whole run's output and left a 9.6 MB scratch directory behind. Also notes upstream: joern#6281 is fixed in v4.0.625. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
c955721f92
commit
b6cccd1444
@@ -920,6 +920,12 @@
|
|||||||
findings across 40 files. First completion of this scan on this repository. The 2,400 s default is
|
findings across 40 files. First completion of this scan on this repository. The 2,400 s default is
|
||||||
still too low for it and stays -- a per-push scan of 4,463 regions is the flow-aware-ranking spec's
|
still too low for it and stays -- a per-push scan of 4,463 regions is the flow-aware-ranking spec's
|
||||||
problem, and 250 findings is the number that spec now has to concentrate.
|
problem, and 250 findings is the number that spec now has to concentrate.
|
||||||
|
- **Upstream, 2026-09-11:** joernio/joern#6281 (the `global`-inside-a-closure miscompile) is closed as
|
||||||
|
fixed -- same root cause as #6269, fixed by #6270, released in **v4.0.625** (we run 4.0.623). The
|
||||||
|
maintainer verified our reproducer on master: no node with two AST parents, overlays apply cleanly.
|
||||||
|
So `_files_with_frontend_defect` and its exclusion are a workaround for two releases and should become
|
||||||
|
version-gated: verify on 4.0.625 (the reproducer, then pmpro and WP Statistics with NO exclusions), and
|
||||||
|
check whether #6283 (`--overlaysonly`) is also gone there.
|
||||||
- **Open, and the maintainer's call, not mine:** `includes/vendor/` is 207 of WP Statistics's 357 PHP
|
- **Open, and the maintainer's call, not mine:** `includes/vendor/` is 207 of WP Statistics's 357 PHP
|
||||||
files and is not excluded by `preprocess.IGNORED_DIRS`, so vendored third-party code is both scanned
|
files and is not excluded by `preprocess.IGNORED_DIRS`, so vendored third-party code is both scanned
|
||||||
and in the graph. Whether a project's vendored code is in scope is policy; the size guard above is
|
and in the graph. Whether a project's vendored code is in scope is policy; the size guard above is
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def ordered_regions(root: Path) -> list[ScanRegion]:
|
|||||||
return sorted(regions, key=lambda r: (not r.shipped, -r.rank, r.path, r.function or ""))
|
return sorted(regions, key=lambda r: (not r.shipped, -r.rank, r.path, r.function or ""))
|
||||||
|
|
||||||
|
|
||||||
def evidence_ordered_regions(root: Path, regions: list[ScanRegion]) -> tuple[list[ScanRegion], dict[str, object]]:
|
def evidence_ordered_regions(root: Path, regions: list[ScanRegion], language: str) -> tuple[list[ScanRegion], dict[str, object]]:
|
||||||
"""The regions in the order phase 2 spends the budget: by each region's best (tier, score), from a real
|
"""The regions in the order phase 2 spends the budget: by each region's best (tier, score), from a real
|
||||||
evidence pass over EVERY region. Needs Joern. The ordering function is the scan's own, imported."""
|
evidence pass over EVERY region. Needs Joern. The ordering function is the scan's own, imported."""
|
||||||
import time
|
import time
|
||||||
@@ -48,7 +48,9 @@ def evidence_ordered_regions(root: Path, regions: list[ScanRegion]) -> tuple[lis
|
|||||||
|
|
||||||
backend = resolve_cpg_backend()
|
backend = resolve_cpg_backend()
|
||||||
started = time.monotonic()
|
started = time.monotonic()
|
||||||
cpg = backend.build(root, language=regions[0].language if regions else "")
|
# The RECIPE's language, never the first region's: WP Statistics's first region is JavaScript, and a
|
||||||
|
# build asked for as JavaScript went looking for jssrc2cpg and lost the whole repository's measurement.
|
||||||
|
cpg = backend.build(root, language=language)
|
||||||
build_seconds = round(time.monotonic() - started, 1)
|
build_seconds = round(time.monotonic() - started, 1)
|
||||||
if cpg is None:
|
if cpg is None:
|
||||||
raise SystemExit(f"no graph for {root}: {getattr(backend, 'last_failure', '')}")
|
raise SystemExit(f"no graph for {root}: {getattr(backend, 'last_failure', '')}")
|
||||||
@@ -70,11 +72,11 @@ def evidence_ordered_regions(root: Path, regions: list[ScanRegion]) -> tuple[lis
|
|||||||
return order_by_evidence(regions, evidence), facts
|
return order_by_evidence(regions, evidence), facts
|
||||||
|
|
||||||
|
|
||||||
def measure(name: str, root: Path, known: list[dict[str, object]], *, by_evidence: bool = False) -> dict[str, object]:
|
def measure(name: str, root: Path, known: list[dict[str, object]], *, by_evidence: bool = False, language: str = "") -> dict[str, object]:
|
||||||
regions = ordered_regions(root)
|
regions = ordered_regions(root)
|
||||||
facts: dict[str, object] = {}
|
facts: dict[str, object] = {}
|
||||||
if by_evidence:
|
if by_evidence:
|
||||||
regions, facts = evidence_ordered_regions(root, regions)
|
regions, facts = evidence_ordered_regions(root, regions, language)
|
||||||
tiers = Counter(r.rank for r in regions)
|
tiers = Counter(r.rank for r in regions)
|
||||||
largest = max(tiers.values()) if tiers else 0
|
largest = max(tiers.values()) if tiers else 0
|
||||||
rows: list[dict[str, object]] = []
|
rows: list[dict[str, object]] = []
|
||||||
@@ -136,7 +138,10 @@ def main() -> int:
|
|||||||
{"id": k.id, "family": k.family, "file": k.file, "function": k.function, "line": k.line, "in_scope": k.in_scope}
|
{"id": k.id, "family": k.family, "file": k.file, "function": k.function, "line": k.line, "in_scope": k.in_scope}
|
||||||
for k in recipe.known
|
for k in recipe.known
|
||||||
]
|
]
|
||||||
results.append(measure(recipe.name, root, known, by_evidence=args.evidence))
|
result = measure(recipe.name, root, known, by_evidence=args.evidence, language=recipe.language)
|
||||||
|
results.append(result)
|
||||||
|
# One line per repository as it lands, so a failure on the next repository cannot lose this one.
|
||||||
|
print(json.dumps(result), file=sys.stderr, flush=True)
|
||||||
|
|
||||||
if args.json:
|
if args.json:
|
||||||
print(json.dumps(results, indent=2))
|
print(json.dumps(results, indent=2))
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ class JoernBackend:
|
|||||||
logger.warning("cpg build failed for %s: %s", root, detail)
|
logger.warning("cpg build failed for %s: %s", root, detail)
|
||||||
retried = self._build_with_frontend(root, cpg_path, scratch, language)
|
retried = self._build_with_frontend(root, cpg_path, scratch, language)
|
||||||
if retried is None:
|
if retried is None:
|
||||||
|
dispose() # a failed build's scratch is nobody's to sweep six hours later
|
||||||
return None
|
return None
|
||||||
self._apply_overlays(cpg_path, scratch)
|
self._apply_overlays(cpg_path, scratch)
|
||||||
unparsed = retried
|
unparsed = retried
|
||||||
|
|||||||
@@ -1018,3 +1018,27 @@ def test_the_census_checks_the_instrument(caplog: pytest.LogCaptureFixture, monk
|
|||||||
)._graph_census(tmp_path / "c.bin", tmp_path, "php")
|
)._graph_census(tmp_path / "c.bin", tmp_path, "php")
|
||||||
assert "512 MB heap where 2048 MB is configured" in caplog.text
|
assert "512 MB heap where 2048 MB is configured" in caplog.text
|
||||||
assert "recompute" not in caplog.text
|
assert "recompute" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_build_that_fails_on_both_launchers_leaves_no_scratch_behind(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""Measured: a measurement that asked for the wrong language failed both `joern-parse` and the frontend
|
||||||
|
and left a 9.6 MB `ousast-cpg-*` directory in /tmp. Failure paths dispose too."""
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from openultrasast.cpg.backend import JoernBackend
|
||||||
|
|
||||||
|
(tmp_path / "a.php").write_text("<?php\n")
|
||||||
|
scratch_root = tmp_path / "scratch"
|
||||||
|
scratch_root.mkdir()
|
||||||
|
monkeypatch.setattr(tempfile, "tempdir", str(scratch_root))
|
||||||
|
|
||||||
|
def runner(command, **kwargs): # type: ignore[no-untyped-def]
|
||||||
|
if "-r" in command:
|
||||||
|
return subprocess.CompletedProcess(args=command, returncode=0, stdout="", stderr="")
|
||||||
|
return subprocess.CompletedProcess(args=command, returncode=1, stdout="", stderr="boom")
|
||||||
|
|
||||||
|
monkeypatch.setenv("OPENULTRASAST_JOERN_PROBE", "on")
|
||||||
|
monkeypatch.setattr("shutil.which", lambda name: f"/opt/bin/{name}")
|
||||||
|
assert JoernBackend(runner=runner).build(tmp_path, language="javascript") is None
|
||||||
|
assert list(scratch_root.iterdir()) == [], "the failed build's scratch directory is gone"
|
||||||
|
|||||||
Reference in New Issue
Block a user