"""Resumable, serial labelled-function traces; host fetches pinned sources, Docker stays offline. Pair blob identities resolve through catalogs to real source commits. The analyzer is git archive HEAD src plus a hashed snapshot of this uncommitted tool. """ from __future__ import annotations import argparse import ast import fcntl import hashlib import json import os import re import shutil import subprocess import tempfile import textwrap import time import tomllib from collections import Counter, defaultdict from pathlib import Path from engine_trace_worker import EXCLUDED_DIRS, safe_path, unit_record, write_json from openultrasast.learn.examples import read_jsonl from openultrasast.learn.labels import load_sources, repo_name from openultrasast.model.specs import taint_specs from openultrasast.model.taint import request_params from openultrasast.pairs import _materialize_side, load_pair_catalog from openultrasast.plane.engine_alerts import export_pin from openultrasast.plane.memory import FileStore from openultrasast.plane.tasks.alerts import engine_languages from openultrasast.preprocess import detect_language ROOT = Path(__file__).resolve().parents[2] DEFAULT_OUT = Path.home() / "ousast-results/plane/engine-trace" SNAPSHOT_FILES = ( "benchmarks/learn/engine_trace.py", "benchmarks/learn/engine_trace_worker.py", "benchmarks/learn/engine_trace_parse.py", "src/openultrasast/model/taint.py", "src/openultrasast/cpg/queries/taint.sc", ) MATERIALIZATION_VERSION = 3 EXPORT_CAP = 20 * 1024 * 1024 STATUSES = ("path", "asked-nothing", "failed", "timeout", "unsupported") def digest(value): return hashlib.sha256(json.dumps(value, sort_keys=True).encode()).hexdigest() def pin_name(pin): return digest([pin["repo"], pin["pin"]]) + ".json" def join_units(units, examples, include_typescript=False): index = {row["id"]: row for row in examples} missing = [u["unit"] for u in units if u["unit"] not in index] if missing: raise ValueError(f"example snapshot missing {len(missing)}/{len(units)} unit IDs; supply --examples JSONL; first: {missing[0]}") covered = engine_languages() - {"typescript"} if include_typescript: covered |= {"typescript"} result = [] for unit in units: example = index[unit["unit"]] if any(example[key] != unit[key] for key in ("family", "label", "group")): raise ValueError(f"unit/example identity mismatch: {unit['unit']}") file, sep, function = example["candidate"].partition("::") if not sep or not function: raise ValueError(f"unit has no labelled function: {unit['unit']}") language = detect_language(Path(file)) or example.get("language", "other") model_language = "javascript" if language == "typescript" else language spec = taint_specs(language=model_language).get(unit["family"]) if language in covered else None result.append( { **unit, "repo": example["repo"], "pin": example["pin"], "file": file, "function": function, "language": language, "source": example["source"], "excerpt": example.get("excerpt", example["source"] == "pairs" or example["source"].startswith("advisory-fixes")), "pin_role": example.get("pin_role", ""), "supported": spec is not None, "reason": "" if spec else f"no admitted taint model: {language}/{unit['family']}", } ) return result def make_plan( units, out, only_family=None, limit=0, *, include_typescript=False, only_source=(), rerun_status=("failed",), allow_excerpt_fallback=False, ): grouped = defaultdict(list) for unit in units: if (not only_family or unit["family"] == only_family) and (not only_source or unit["source"] in only_source): # Include admission options even when a unit's support happens to stay # unchanged. Old results cannot mark a different selection as complete. identity = { "unit": unit, "include_typescript": include_typescript, "materialization_version": MATERIALIZATION_VERSION, "allow_excerpt_fallback": allow_excerpt_fallback, } grouped[unit["repo"], unit["pin"]].append({**unit, "input_digest": digest(identity)}) plan = [] for (repo, pin), rows in sorted(grouped.items()): item = { "repo": repo, "pin": pin, "materialization_version": MATERIALIZATION_VERSION, "units": sorted(rows, key=lambda r: r["unit"]), } item["selection"] = digest(item["units"]) done = out / pin_name(item) if done.exists(): old = json.loads(done.read_text()) recorded = {r["unit"]: r.get("input_digest") for r in old.get("units", [])} if ( old.get("done") and old.get("materialization_version") == MATERIALIZATION_VERSION and not any(r.get("status") in rerun_status for r in old.get("units", []) if r["unit"] in {u["unit"] for u in rows}) and all(recorded.get(r["unit"]) == r["input_digest"] for r in rows) ): continue sources = {r["source"] for r in rows} # Inventory medians: harvest sides 31s, advisory sides 47s; v1 pins 182s, v2 pins 498s. item["estimated_seconds"] = ( (47 if any(s.startswith("advisory") for s in sources) else 31) if any(r.get("excerpt") for r in rows) or any(s.startswith("advisory") for s in sources) else (182 if sources == {"population-v1"} else 498) ) if not any(r["supported"] for r in rows): item["estimated_seconds"] = 0 plan.append(item) # limit counts runnable pins, not unsupported records. if limit: chosen = [] count = 0 for pin in plan: runnable = any(r["supported"] for r in pin["units"]) if runnable and count >= limit: continue chosen.append(pin) count += runnable return chosen return plan class Inputs: """Resolve spent corpora; fetch pair source commits into the host cache.""" def __init__(self, root, cache, allow_excerpt_fallback=False): self.cache = cache self.allow_excerpt_fallback = allow_excerpt_fallback self.catalog_rows = defaultdict(list) for catalog in (root / "benchmarks/pairs").rglob("catalog.toml"): for item in tomllib.loads(catalog.read_text()).get("pair", []): self.catalog_rows[item["name"]].append((catalog.parent.name, item)) self.pairs = [] self.repos = defaultdict(list) sources = load_sources() for source in sources.sources: if source.kind == "pairs": self.pairs.extend(load_pair_catalog(root / source.files[0])) elif source.kind == "population": if source.id not in {"population-v1", "population-v2"}: continue data = tomllib.loads((root / source.files[0]).read_text()) for case in data.get("case", []): self.repos[repo_name(case["repo"])].append((cache / "independent" / case["id"], case)) elif source.kind == "recipes": for file in source.files: data = tomllib.loads((root / file).read_text()) recipe = data.get("repo", data) # Recipes carry a top-level URL and a pinned checkout cache. if isinstance(recipe, dict): self.repos[repo_name(str(recipe.get("url", "")))].append((cache / "repos" / str(data.get("name", "")), data)) self.hashes = {} def pair_for(self, row): if row["pin_role"] not in {"fixed", "vulnerable", "vuln"}: raise ValueError(f"ambiguous pair side: {row['pin_role']}") side = "fixed" if row["pin_role"] == "fixed" else "vuln" candidates = [ c for c in self.pairs if c.relpath == row["file"] and repo_name(c.repo or f"{c.slice}:{c.name}") == repo_name(row["repo"]) ] matches = [] for case in candidates: path = case.fixed_file if side == "fixed" else case.vuln_file if path.is_file(): if path not in self.hashes: data = path.read_bytes() # Harvest hashes decoded text, so universal newline conversion is part # of its identity (one recorded ThreatByte excerpt uses CRLF). normalized = path.read_text().encode("utf-8") self.hashes[path] = { hashlib.sha1(b"blob " + str(len(body)).encode() + b"\0" + body).hexdigest() for body in (data, normalized) } if row["pin"] in self.hashes[path]: matches.append(case) if len(matches) == 1: return matches[0], side if len(candidates) == 1: # Earlier stores used label_pin rather than the excerpt digest. from openultrasast.learn.examples import label_pin case = candidates[0] if row["pin"] == label_pin({"source": "pairs", "source_ref": case.name, "pin_role": row["pin_role"]}): return case, side raise ValueError(f"cannot uniquely resolve excerpt {row['repo']}@{row['pin']}:{row['file']}") def catalog_for(self, case): matches = [ (source, item) for source, item in self.catalog_rows[case.name] if item.get("relpath") == case.relpath and repo_name(item.get("repo", "")) == repo_name(case.repo) ] if len(matches) != 1: raise ValueError(f"ambiguous catalog source for {case.name}") return matches[0] def source_identity(self, case, side): _, item = self.catalog_for(case) parent, commit = item.get("parent", ""), item.get("commit", "") if side not in {"vuln", "fixed"} or not all(re.fullmatch(r"[0-9a-fA-F]{40}", sha) for sha in (parent, commit)) or parent == commit: raise ValueError(f"ambiguous catalog commits for {case.name}: need distinct parent and commit SHAs") repo = item.get("repo", "") if not repo: raise ValueError(f"missing catalog repository for {case.name}") return repo, parent if side == "vuln" else commit def fetch_source(self, repo, commit, roots): clone = self.cache / "trace-sources" / digest([repo, commit, roots]) clone.mkdir(parents=True, exist_ok=True) url = repo if repo.startswith("https://") else f"https://github.com/{repo.removesuffix('.git')}.git" env = {**os.environ, "GIT_TERMINAL_PROMPT": "0"} env.pop("GIT_NO_LAZY_FETCH", None) # sparse checkout may fetch the selected blobs def git(*args): return subprocess.run(["git", "-C", str(clone), *args], env=env, capture_output=True, text=True, check=True, timeout=300) git("init") git("config", "remote.origin.url", url) git("config", "remote.origin.promisor", "true") git("config", "remote.origin.partialclonefilter", "blob:none") git("fetch", "--depth", "1", "--filter=blob:none", url, commit) git("sparse-checkout", "init", "--cone") git("sparse-checkout", "set", "--", *roots) git("checkout", "--detach", "--force", commit) actual = git("rev-parse", "HEAD").stdout.strip() if actual != commit: raise ValueError(f"fetched commit mismatch: {actual} != {commit}") return clone def export_local(self, clone, pin, target): with tempfile.TemporaryDirectory(prefix="trace-export-", dir=target.parent) as scratch: export_pin(clone, pin["pin"], Path(scratch)) roots = sorted({source_root(r["file"], r["language"]) for r in pin["units"]}) proof = export_sources(Path(scratch), target, pin["units"], roots) return {**proof, "mode": "source", "commit": pin["pin"]} def materialize(self, pin, target): rows = pin["units"] for row in rows: safe_path(target, row["file"]) if row.get("mapping_error"): raise ValueError(row["mapping_error"]) if any(r.get("excerpt") or r["source"].startswith("advisory-fixes") for r in rows): resolved = [self.pair_for(row) for row in rows] identities = set() catalog_files = [] for case, side in resolved: _, item = self.catalog_for(case) parent, commit = item.get("parent", ""), item.get("commit", "") if parent != commit and all(re.fullmatch(r"[0-9a-fA-F]{40}", sha) for sha in (parent, commit)): identities.add(self.source_identity(case, side)) else: file = case.fixed_file if side == "fixed" else case.vuln_file data = file.read_text() if is_catalog_excerpt(data): data = parseable_excerpt(file, data) mode = "excerpt-parseable" else: # Validate BOTH twins before treating this as a complete-file pair. for twin in (case.vuln_file, case.fixed_file): verify_catalog_file(twin) mode = "catalog-file" catalog_files.append((case, side, mode, data)) if catalog_files: if identities: raise ValueError("mixed source and catalog-file materialization at one pair pin") for case, side, mode, data in catalog_files: _materialize_side(target, case, side=side) if mode == "excerpt-parseable": safe_path(target, case.relpath).write_text(data) verify_functions(target, rows) modes = {case.relpath: mode for case, _, mode, _ in catalog_files} for row in rows: if modes[row["file"]] == "excerpt-parseable": row["evidence_scope"] = "in-function-only" return { "mode": "excerpt-parseable" if "excerpt-parseable" in modes.values() else "catalog-file", "files": sum(p.is_file() for p in target.rglob("*")), "bytes": sum(p.stat().st_size for p in target.rglob("*") if p.is_file()), "roots": sorted({source_root(r["file"], r["language"]) for r in rows}), } if len(identities) != 1: raise ValueError("ambiguous source commits at one pair pin") repo, commit = identities.pop() roots = sorted({source_root(row["file"], row["language"]) for row in rows}) try: clone = self.fetch_source(repo, commit, roots) proof = export_sources(clone, target, rows, roots) except (OSError, ValueError, subprocess.SubprocessError) as exc: if not self.allow_excerpt_fallback: raise shutil.rmtree(target, ignore_errors=True) for case, side in resolved: _materialize_side(target, case, side=side) return {"mode": "excerpt-fallback", "reason": str(exc), "repo": repo, "commit": commit} verify_functions(target, rows) return {**proof, "mode": "source", "repo": repo, "commit": commit} if all(r["source"] == "dev-php" for r in rows): # Export the exact requested commit from the local recipe checkout. for recipe in (ROOT / "benchmarks/repos").glob("*.toml"): data = tomllib.loads(recipe.read_text()) if repo_name(str(data.get("url", ""))) == repo_name(pin["repo"]): checkout = self.cache / "repos" / data["name"] / pin["pin"][:12] return self.export_local(checkout, pin, target) for clone, _ in self.repos[repo_name(pin["repo"])]: check = subprocess.run(["git", "-C", str(clone), "cat-file", "-e", pin["pin"] + "^{commit}"], capture_output=True) if check.returncode == 0: return self.export_local(clone, pin, target) raise ValueError(f"no local clone contains {pin['repo']}@{pin['pin']}") def is_catalog_excerpt(data): # Older Python exports have this header but no upstream_start field. return re.match(r"\s*(?://|#)\s*Provenance:", data) is not None def parseable_excerpt(path, data): """Dedent the code independently of its unindented provenance comments.""" language = detect_language(path) lines = data.splitlines(keepends=True) start = 0 while start < len(lines) and (not lines[start].strip() or lines[start].lstrip().startswith(("#", "//"))): start += 1 header = "".join(lines[:start]) if language == "python": header = re.sub(r"(?m)^(\s*)//", r"\1#", header) data = header + textwrap.dedent("".join(lines[start:])) if not "".join(lines[start:]).strip(): raise ValueError(f"excerpt does not parse: empty code ({path.name})") if language == "python": try: ast.parse(data, filename=str(path)) except SyntaxError as exc: raise ValueError(f"excerpt does not parse: {exc}") from exc else: from openultrasast.semantic.cst import parse_with_cst parsed = parse_with_cst(str(path), data, language) if parsed is None: raise ValueError(f"excerpt does not parse: no cheap parser for {language} ({path.name})") if not parsed.parse_ok: raise ValueError(f"excerpt does not parse: {language} {parsed.reason} ({path.name})") return data def verify_catalog_file(path): """Admit complete source only; syntactically valid provenance excerpts still fail.""" data = path.read_text() if is_catalog_excerpt(data): raise ValueError(f"catalog side is an upstream excerpt without distinct commits: {path.name}") language = detect_language(path) if not data.strip(): raise ValueError(f"catalog side is empty: {path.name}") if language == "python": try: ast.parse(data, filename=str(path)) except SyntaxError as exc: raise ValueError(f"catalog side is not complete Python source: {path.name}: {exc.msg}") from exc else: from openultrasast.semantic.extra import grammar_for grammar = grammar_for(language) if grammar is None: raise ValueError(f"cannot verify complete catalog source for {language}: {path.name}") from tree_sitter import Parser if Parser(grammar).parse(data.encode()).root_node.has_error: raise ValueError(f"catalog side is not complete {language} source: {path.name}") def source_root(file, language): parts = Path(file).parts if language == "java": for i in range(len(parts) - 2): if parts[i : i + 3] == ("src", "main", "java"): return "/".join(parts[: i + 3]) # Monorepo and source-layout containers are not themselves a package. if len(parts) > 2 and parts[0] in {"packages", "apps", "src", "web"}: return "/".join(parts[:2]) return parts[0] if len(parts) > 1 else "." def local_imports(source, rows): """Cheap one-level Python imports from labelled files, without importing code.""" found = set() for row in rows: if row["language"] != "python": continue file = safe_path(source, row["file"]) try: tree = ast.parse(file.read_bytes()) except (SyntaxError, ValueError): continue for node in ast.walk(tree): if isinstance(node, ast.Import): modules, bases = [alias.name for alias in node.names], [source, source / "src"] elif isinstance(node, ast.ImportFrom): modules = [node.module or ""] modules += [".".join(filter(None, (node.module, alias.name))) for alias in node.names if alias.name != "*"] bases = [file.parents[node.level - 1]] if node.level and node.level <= len(file.parents) else [source, source / "src"] else: continue for base in bases: for module in modules: path = base.joinpath(*module.split(".")) for candidate in (path.with_suffix(".py"), path / "__init__.py"): if candidate.is_relative_to(source) and candidate.is_file() and not candidate.is_symlink(): rel = candidate.relative_to(source).as_posix() if not any(part in EXCLUDED_DIRS for part in Path(rel).parts): found.add(rel) return found def export_sources(source, target, rows, roots=(".",), cap=EXPORT_CAP): """Copy labels intact, narrowing oversized roots before bounding context bytes.""" labelled = {r["file"] for r in rows} languages = {r["language"] for r in rows} files = set() protected = {parent.as_posix() for rel in labelled for parent in Path(rel).parents} for directory, dirs, names in os.walk(source): dirs[:] = sorted( d for d in dirs if d != ".git" and not (Path(directory) / d).is_symlink() and (d not in EXCLUDED_DIRS or (Path(directory) / d).relative_to(source).as_posix() in protected) ) for name in names: file = Path(directory) / name rel = file.relative_to(source).as_posix() excluded = any(part in EXCLUDED_DIRS for part in Path(rel).parts[:-1]) if not file.is_symlink() and ( rel in labelled or ( not excluded and detect_language(file) in languages and any(root == "." or rel == root or rel.startswith(root + "/") for root in roots) ) ): files.add(rel) missing = labelled - files if missing: raise ValueError(f"labelled files missing or excluded from export: {sorted(missing)}") imports = local_imports(source, rows) files.update(imports) sizes = {rel: safe_path(source, rel).stat().st_size for rel in files} original_roots = sorted(roots) roots = original_roots # Avoid lexicographically truncating a whole large package such as vllm. if sum(sizes.values()) > cap: roots = sorted({str(Path(rel).parent) for rel in labelled}) files = { rel for rel in files if rel in labelled or rel in imports or any(root == "." or rel.startswith(root + "/") for root in roots) } proof = { "bytes": 0, "files": 0, "cap_bytes": cap, "cap_hit": False, "excludes": sorted(EXCLUDED_DIRS), "roots": roots, "requested_roots": original_roots, "scope_narrowed": roots != original_roots, "import_files": sorted(imports), } for rel in sorted(files, key=lambda rel: (rel not in labelled, rel)): file = safe_path(source, rel) size = sizes[rel] # The cap bounds context only. No labelled source is ever cut or omitted. if proof["bytes"] + size > cap: proof["cap_hit"] = True if rel not in labelled: continue dest = safe_path(target, rel) dest.parent.mkdir(parents=True, exist_ok=True) dest.write_bytes(file.read_bytes()) proof["bytes"] += size proof["files"] += 1 return proof def verify_functions(root, rows): for row in rows: data = safe_path(root, row["file"]).read_text(errors="replace") if not re.search(r"(?