mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 12:39:28 +00:00
perf(hunt): the source producer walks and reads the product tree once per change, not once per row
facts_for walked the whole product tree and re-read every product file for each candidate row; on a Laravel application one change of 33 rows took four minutes, 96 percent of it there. The producer context already holds the product file list and the texts it read for entry points, so facts_for and callers_of now accept them. Same facts, tested by a call whose tree walk raises and whose file on disk is emptied after reading. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
4063fd1562
commit
86d70831c8
@@ -250,12 +250,13 @@ def _roles(function: Any, facts: Any) -> dict[str, str]:
|
||||
return roles
|
||||
|
||||
|
||||
def _obligations(path: str, ctx: Context) -> list[dict[str, object]]:
|
||||
def _obligations(path: str, ctx: Context) -> list[dict[str, object]] | None:
|
||||
ir, text = ctx.ir(path), ctx.read(path)
|
||||
facts, flow = load_obligation_facts(), load_facts()
|
||||
scoped = facts.for_language(ir.language)
|
||||
if not scoped.operations and not scoped.dischargers:
|
||||
raise _Absent("obligations_language_unsupported")
|
||||
# A missing fact table is not a failed parse or a checked empty result.
|
||||
return None
|
||||
dominance = OrderDominance({path: text})
|
||||
result = check_obligations(
|
||||
irs={path: (ir, text)},
|
||||
|
||||
@@ -420,25 +420,44 @@ def _read_source(root: Path, path: str) -> str:
|
||||
return resolved.read_text(encoding="utf-8", errors="replace")
|
||||
|
||||
|
||||
def callers_of(root: Path, name: str, *, files: Iterable[str]) -> tuple[tuple[str, int], ...]:
|
||||
"""Return sorted call locations from the supplied repository-relative product files."""
|
||||
def callers_of(root: Path, name: str, *, files: Iterable[str], texts: Mapping[str, str] | None = None) -> tuple[tuple[str, int], ...]:
|
||||
"""Return sorted call locations from the supplied repository-relative product files.
|
||||
|
||||
``texts`` holds file contents already read by the caller; files absent from it are read here."""
|
||||
root = Path(root)
|
||||
return tuple((path, call.line) for path in sorted(set(files)) for call in call_sites(_read_source(root, path), (name,)))
|
||||
|
||||
def text_of(path: str) -> str:
|
||||
return texts[path] if texts is not None and path in texts else _read_source(root, path)
|
||||
|
||||
return tuple((path, call.line) for path in sorted(set(files)) for call in call_sites(text_of(path), (name,)))
|
||||
|
||||
|
||||
def facts_for(root: Path, path: str, line: int, *, entry_points: Sequence[EntryPointRecord] | None = None) -> SourceFacts:
|
||||
"""Compute repository facts at a line; supplied entry points avoid repeated mapping."""
|
||||
def facts_for(
|
||||
root: Path,
|
||||
path: str,
|
||||
line: int,
|
||||
*,
|
||||
entry_points: Sequence[EntryPointRecord] | None = None,
|
||||
files: Sequence[str] | None = None,
|
||||
texts: Mapping[str, str] | None = None,
|
||||
) -> SourceFacts:
|
||||
"""Compute repository facts at a line; supplied entry points, product files and texts avoid repeated work.
|
||||
|
||||
A caller computing facts for many lines of one checkout (the hunter's feature table) walks the product tree
|
||||
and reads it once; without ``files`` and ``texts`` every call walked and read the whole tree again."""
|
||||
root = Path(root)
|
||||
text = _read_source(root, path)
|
||||
language = canonical_language(detect_language(root / path))
|
||||
spans = ranges(path, text, language)
|
||||
function = enclosing(spans, line)
|
||||
files = product_files(root)
|
||||
files = list(files) if files is not None else product_files(root)
|
||||
if entry_points is None:
|
||||
from .mapping import analyze_entry_points
|
||||
|
||||
entry_points = analyze_entry_points(root, [build_file_target(root, root / file) for file in files])
|
||||
return SourceFacts(language, function, bounds(spans, function), callers_of(root, function, files=files), tuple(entry_points))
|
||||
return SourceFacts(
|
||||
language, function, bounds(spans, function), callers_of(root, function, files=files, texts=texts), tuple(entry_points)
|
||||
)
|
||||
|
||||
|
||||
def _digest(names: Sequence[str]) -> str:
|
||||
|
||||
Reference in New Issue
Block a user