mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 12:39:28 +00:00
feat(facts): the [[layout]] fact -- vendored trees are out, test paths are not product
Two decisions the maintainer made, as rows rather than constants. A vendored tree (vendor/, node_modules/, third_party/) is somebody else's unit: out of the targets in preprocess and out of the graph through the frontend's --exclude, which the scan hands to the backend because the CPG seam imports only the standard library. A test path (tests/, test-*.php, *Test.php) stays in scope -- a test that reaches a sink still names the sink -- but its regions are not shipped and order after the product's. Measured before the row: 207 of WP Statistics's 357 PHP files were vendored, and 14 of the 67 regions ahead of MW WP Form's CVE were test files. The position benchmark applies both, so it measures the order the scan spends. 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
9142cb1fb4
commit
b821ac6e4e
@@ -0,0 +1,68 @@
|
||||
"""Where the `[[layout]]` facts meet a repository: which paths are vendored, which are tests.
|
||||
|
||||
One place, imported by the preprocessor (vendored trees leave the targets), the scan (test regions are
|
||||
not product, and vendored directories leave the graph) and the position benchmark (so it measures the
|
||||
order the scan spends). Facts are rows; this is only the matching.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
from collections.abc import Iterable, Sequence
|
||||
from pathlib import Path
|
||||
|
||||
from ..semantic.facts import LayoutFact, load_facts
|
||||
from .regions import ScanRegion
|
||||
|
||||
|
||||
def layout_facts(language: str | None = None) -> tuple[LayoutFact, ...]:
|
||||
"""The layout rows -- for one language, or for every language when the question is about a tree."""
|
||||
facts = load_facts()
|
||||
return tuple(facts.for_language(language).layouts if language else facts.layouts)
|
||||
|
||||
|
||||
def _matches(relative: str, patterns: Iterable[str]) -> bool:
|
||||
"""gitignore-style: `dir/` names a directory at any depth, anything else a path or basename glob."""
|
||||
parts = relative.split("/")
|
||||
for pattern in patterns:
|
||||
if pattern.endswith("/"):
|
||||
if pattern.rstrip("/") in parts[:-1] or pattern.rstrip("/") in parts:
|
||||
return True
|
||||
elif fnmatch.fnmatch(relative, pattern) or fnmatch.fnmatch(parts[-1], pattern):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_vendored(relative: str, facts: Sequence[LayoutFact] | None = None) -> bool:
|
||||
rows = facts if facts is not None else layout_facts()
|
||||
return _matches(relative, (p for fact in rows for p in fact.vendored))
|
||||
|
||||
|
||||
def is_test_path(relative: str, facts: Sequence[LayoutFact] | None = None) -> bool:
|
||||
rows = facts if facts is not None else layout_facts()
|
||||
return _matches(relative, (p for fact in rows for p in fact.tests))
|
||||
|
||||
|
||||
def vendored_directories(root: Path, facts: Sequence[LayoutFact] | None = None) -> tuple[str, ...]:
|
||||
"""The vendored directories actually present under ``root``, repository-relative, shallowest first --
|
||||
what a frontend's ``--exclude`` takes."""
|
||||
rows = facts if facts is not None else layout_facts()
|
||||
names = {p.rstrip("/") for fact in rows for p in fact.vendored if p.endswith("/")}
|
||||
found: list[str] = []
|
||||
for path in sorted(root.rglob("*")):
|
||||
if not path.is_dir() or path.name not in names:
|
||||
continue
|
||||
relative = path.relative_to(root).as_posix()
|
||||
if any(relative.startswith(f"{seen}/") for seen in found):
|
||||
continue # already covered by a shallower vendored directory
|
||||
found.append(relative)
|
||||
return tuple(found)
|
||||
|
||||
|
||||
def with_layout(regions: Sequence[ScanRegion], facts: Sequence[LayoutFact] | None = None) -> tuple[ScanRegion, ...]:
|
||||
"""Test-path regions marked not shipped -- ordered after the product's, never removed. A region a
|
||||
build manifest already declared shipped stays so: the manifest is the project's own word."""
|
||||
from dataclasses import replace
|
||||
|
||||
rows = facts if facts is not None else layout_facts()
|
||||
return tuple(replace(r, shipped=False) if r.shipped and is_test_path(r.path, rows) else r for r in regions)
|
||||
@@ -31,6 +31,7 @@ from .config_value import request_params as config_params
|
||||
from .dominance import request_params as dominance_params
|
||||
from .evidence import TIER_EXCLUDE, Evidence, evidence_from_rows
|
||||
from .ladder import Rung
|
||||
from .layout import with_layout
|
||||
from .pipeline import ModelFinding, scan_region
|
||||
from .regions import MODULE_SCOPE, ScanRegion
|
||||
from .specs import ConfigSpec, DominanceSpec, TaintSpec, config_specs, dominance_specs, taint_specs
|
||||
@@ -168,7 +169,7 @@ def scan_repository(
|
||||
|
||||
# Sort here rather than trusting the caller. The budget decides what goes unexamined, so the order it is
|
||||
# spent in belongs to whoever holds the budget.
|
||||
ordered_regions = sorted(regions, key=lambda r: (not r.shipped, -r.rank, r.path, r.function or ""))
|
||||
ordered_regions = sorted(with_layout(regions), key=lambda r: (not r.shipped, -r.rank, r.path, r.function or ""))
|
||||
|
||||
# The hook link table, built once for the whole scan. It is a property of the repository rather than of
|
||||
# any region, and it is read from the source text because the graph does not carry it: php2cpg drops a
|
||||
@@ -441,11 +442,17 @@ def _dispose(cpg: object) -> None:
|
||||
|
||||
|
||||
def _build(backend: Any, root: Path, language: str) -> Any:
|
||||
"""Build through the backend, passing the language when the backend can use it."""
|
||||
"""Build through the backend, passing the language and the vendored trees when the backend can use them."""
|
||||
from .layout import vendored_directories
|
||||
|
||||
excluded = vendored_directories(root) if root.is_dir() else ()
|
||||
try:
|
||||
return backend.build(root, language=language)
|
||||
return backend.build(root, language=language, exclude=excluded)
|
||||
except TypeError: # a backend from before the retry existed, including every test double
|
||||
return backend.build(root)
|
||||
try:
|
||||
return backend.build(root, language=language)
|
||||
except TypeError:
|
||||
return backend.build(root)
|
||||
|
||||
|
||||
def _prefetched(rows: list[object]): # type: ignore[no-untyped-def]
|
||||
|
||||
Reference in New Issue
Block a user