mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 04:29:27 +00:00
- variant_search.search_tree skips a malformed store row with a variants_store_row_invalid degradation instead of aborting the scan; a hit on a demoted or flowless overlay record is never merged (test). - seed.pair_lessons is the one shape derivation shared by the exporter and leave-one-out; labels pass line and sink together; pairs without a labeled function are reported as no_labeled_function. - config parses [variants].max_mechanisms with the shared int helper. - regress.candidate.hotspot_from_finding is the one zero-score hotspot helper for forced inventory and variant findings. - design synced (SearchResult, hits_to_findings summaries, candidate.py). Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
125 lines
5.0 KiB
Python
125 lines
5.0 KiB
Python
"""Variant search over a scanned tree and its merge with overlay flows (corpus-seeded-mechanisms, Req 3).
|
|
|
|
Depends on the shapes (``variants``), the store (``mechanisms``), the overlay records and ``StaticFinding``; the shape
|
|
module itself stays free of those imports.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping, Sequence
|
|
from dataclasses import dataclass, replace
|
|
from pathlib import Path
|
|
|
|
from ..findings import StaticFinding
|
|
from ..preprocess import FileTarget
|
|
from .facts import SemanticFacts
|
|
from .ir import parse_file
|
|
from .mechanisms import MechanismStore, corpus_mechanisms
|
|
from .overlay import OverlayRecord
|
|
from .variants import Shape, VariantHit, match_shapes
|
|
|
|
LANGUAGE_UNSUPPORTED = "variants_language_unsupported"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SearchResult:
|
|
hits: tuple[VariantHit, ...]
|
|
mechanisms_searched: int
|
|
files_searched: int
|
|
degradations: tuple[dict[str, object], ...] = ()
|
|
|
|
|
|
def search_tree(
|
|
root: Path,
|
|
targets: Sequence[FileTarget],
|
|
store: MechanismStore,
|
|
facts: SemanticFacts,
|
|
*,
|
|
max_mechanisms: int,
|
|
) -> SearchResult:
|
|
"""Variant search over the parsed files of a tree with the store's corpus shapes (bounded by ``max_mechanisms``)."""
|
|
records = corpus_mechanisms(store.load())[: max(max_mechanisms, 0)]
|
|
shapes: list[Shape] = []
|
|
ids: dict[str, str] = {}
|
|
degradations: list[dict[str, object]] = []
|
|
for record in records:
|
|
assert record.shape is not None
|
|
try:
|
|
shape = Shape.from_dict(record.shape)
|
|
except (ValueError, TypeError) as exc: # one malformed operator-visible row must not abort the scan
|
|
degradations.append(
|
|
{"stage": "variants", "reason": "variants_store_row_invalid", "mechanism_id": record.id, "detail": str(exc)[:120]}
|
|
)
|
|
continue
|
|
shapes.append(shape)
|
|
ids[shape.key()] = record.id
|
|
if not shapes:
|
|
return SearchResult(hits=(), mechanisms_searched=0, files_searched=0, degradations=tuple(degradations))
|
|
hits: list[VariantHit] = []
|
|
files = 0
|
|
unsupported: set[str] = set()
|
|
for target in targets:
|
|
try:
|
|
text = (root / target.path).read_text(errors="ignore")
|
|
except OSError:
|
|
continue
|
|
ir = parse_file(target.path, text, target.language)
|
|
if not ir.parse_ok:
|
|
if ir.reason == "language_unsupported" and target.language not in unsupported:
|
|
unsupported.add(target.language)
|
|
degradations.append({"stage": "variants", "reason": "variants_language_unsupported", "language": target.language})
|
|
continue
|
|
files += 1
|
|
hits.extend(match_shapes(ir, shapes, facts, mechanism_ids=ids))
|
|
return SearchResult(hits=tuple(hits), mechanisms_searched=len(shapes), files_searched=files, degradations=tuple(degradations))
|
|
|
|
|
|
def hits_to_findings(
|
|
hits: Sequence[VariantHit],
|
|
records: Sequence[OverlayRecord],
|
|
summaries: Mapping[str, tuple[str, tuple[str, ...], str]],
|
|
) -> tuple[list[StaticFinding], list[OverlayRecord]]:
|
|
"""Merge hits into overlay records at the same call site (Req 3.3); the rest become ``suspicion`` findings (Req 3.2)."""
|
|
by_site: dict[tuple[str, int | None], list[int]] = {}
|
|
for index, record in enumerate(records):
|
|
if record.disposition in {"promote", "coverage"} and record.sources and record.sinks:
|
|
by_site.setdefault((record.path, record.line), []).append(index)
|
|
updated = list(records)
|
|
findings: list[StaticFinding] = []
|
|
emitted: set[str] = set()
|
|
for hit in hits:
|
|
indexes = by_site.get((hit.path, hit.line))
|
|
if indexes:
|
|
for index in indexes:
|
|
if updated[index].mechanism_id is None:
|
|
updated[index] = replace(updated[index], mechanism_id=hit.mechanism_id)
|
|
continue
|
|
finding_id = f"variant:{hit.mechanism_id}:{hit.path}:{hit.line}"
|
|
if finding_id in emitted:
|
|
continue
|
|
emitted.add(finding_id)
|
|
summary, pairs, cwe = summaries.get(hit.mechanism_id, (hit.mechanism_id, (), ""))
|
|
taught_by = ", ".join(pairs) if pairs else "the mechanism store"
|
|
findings.append(
|
|
StaticFinding(
|
|
finding_id=finding_id,
|
|
path=hit.path,
|
|
title=f"Variant of known mechanism: {hit.sink_name}",
|
|
severity="medium",
|
|
confidence="low",
|
|
evidence_level="suspicion",
|
|
rationale=(
|
|
f"{summary}. Structural variant ({hit.source_kind} reaches {hit.sink_name}) of a mechanism learned from "
|
|
f"{taught_by}; suspicion until the overlay or the sandbox agrees."
|
|
),
|
|
line=hit.line,
|
|
function_name=None,
|
|
reachability_status="unknown",
|
|
reachability_evidence=[],
|
|
reachability_conditions=[],
|
|
tags=["variant", f"mechanism:{hit.mechanism_id}", *([f"cwe:{cwe}"] if cwe else [])],
|
|
ranking_priority=0.0,
|
|
)
|
|
)
|
|
return findings, updated
|