Download src/cpg/annotate.py from Dhruv1000/tracegrpo: direct link, hf CLI and curl.
- Browser
- Download file 4.44 kB
-
https://huggingface.co/Dhruv1000/tracegrpo/resolve/main/src/cpg/annotate.py
- Command line
-
hf download hf://Dhruv1000/tracegrpo/src/cpg/annotate.py
-
curl -L -o annotate.py https://huggingface.co/Dhruv1000/tracegrpo/resolve/main/src/cpg/annotate.py
4.44 kB
| """Phase 2 per-sample annotation (spec §4.2-§4.4): ties statement_mapper, | |
| defuse, and trace_miner together to produce, for one corpus row, the L1/L2/L3 | |
| localization targets and trace ground truth. | |
| Uses the tree-sitter fallback (defuse.py) by default — not real Joern — since | |
| running Joern per-sample across the full ~13.6k-pair corpus is a real, | |
| multi-hour, CPU-parallelized batch job (spec §4: "budget accordingly... | |
| target 16 workers... resumable") that hasn't been executed yet (see | |
| DECISIONS.md). `annotate_sample(..., use_joern=True)` is wired for when that | |
| batch job runs; this module's job is to define *what* an annotation record | |
| looks like and compute it correctly, independent of which backend produced | |
| the underlying graph. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import asdict, dataclass, field | |
| from src.cpg.defuse import backward_slice, build_defuse_graph | |
| from src.cpg.statement_mapper import map_patch_lines_to_statements_detailed, statement_lines | |
| from src.cpg.trace_miner import mine_trace | |
| class SampleAnnotation: | |
| sample_id: str | |
| l1_lines: list[int] | |
| l2_lines: list[int] | |
| l3_lines: list[int] # whole function (degenerate upper bound, spec §4.2) | |
| trace_gt: list[list[str]] # stringified node ids (JSON-safe), spec §4.3 | |
| trace_unverifiable: bool | |
| trace_reason: str | |
| annotation_backend: str # "tree_sitter" | "joern" | |
| trace_lengths: list[int] = field(default_factory=list) # for the trace-length distribution report | |
| used_fallback_sink_search: bool = False # True if L1 was empty and the old dangerous-call search ran | |
| n_dangerous_call_sinks: int = 0 # enrichment count, spec amendment §1.3-a | |
| cwe: str | None = None # carried through for the per-CWE trace-coverage table | |
| l1_mapping_reasons: list[str] = field(default_factory=list) # spec amendment §2: per-line reason log | |
| def annotate_sample( | |
| sample_id: str, | |
| func_vuln: str, | |
| patch_lines_vuln: list[int], | |
| cwe: str | None, | |
| func_len_lines: int | None = None, | |
| ) -> SampleAnnotation: | |
| """spec §4.2-§4.3 for a single sample. Only meaningful for target=1 rows | |
| (patch_lines_vuln is empty for target=0 benign rows by construction — | |
| spec §3.2 — so L1/L2/trace are empty/unverifiable for those, correctly).""" | |
| if not patch_lines_vuln: | |
| n_lines = func_len_lines or len(func_vuln.splitlines()) | |
| return SampleAnnotation( | |
| sample_id=sample_id, l1_lines=[], l2_lines=[], l3_lines=list(range(1, n_lines + 1)), | |
| trace_gt=[], trace_unverifiable=True, trace_reason="target_0_no_patch_lines", | |
| annotation_backend="tree_sitter", cwe=cwe, | |
| ) | |
| detailed = map_patch_lines_to_statements_detailed(func_vuln, patch_lines_vuln) | |
| l1_mapping_reasons = [reason for _stmt, reason in detailed] | |
| l1_statements = [stmt for stmt, _reason in detailed if stmt is not None] | |
| l1_lines = sorted(statement_lines(l1_statements)) | |
| graph = build_defuse_graph(func_vuln) | |
| l1_ids = {s.node_id for s in l1_statements} | |
| l2_ids = backward_slice(graph, l1_ids, max_depth=3) | |
| l2_statement_objs = [s for s in graph.statements if s.node_id in l2_ids] | |
| l2_lines = sorted(statement_lines(l2_statement_objs)) | |
| n_lines = func_len_lines or len(func_vuln.splitlines()) | |
| l3_lines = list(range(1, n_lines + 1)) | |
| # Trace amendment: sinks = L1 statements themselves, not a dangerous-call | |
| # table lookup (see DECISIONS.md "Phase 2 trace-mining redefinition"). | |
| trace_result = mine_trace(func_vuln, cwe, l1_stmt_ids=sorted(l1_ids), graph=graph) | |
| # stmt ids are (start_byte, end_byte) tuples — stringify for JSON safety | |
| trace_gt = [[f"{a}-{b}" for (a, b) in path] for path in trace_result.trace_gt] | |
| return SampleAnnotation( | |
| sample_id=sample_id, | |
| l1_lines=l1_lines, | |
| l2_lines=sorted(set(l1_lines) | set(l2_lines)), | |
| l3_lines=l3_lines, | |
| trace_gt=trace_gt, | |
| trace_unverifiable=trace_result.trace_unverifiable, | |
| trace_reason=trace_result.reason, | |
| annotation_backend="tree_sitter", | |
| trace_lengths=[len(p) for p in trace_result.trace_gt], | |
| used_fallback_sink_search=trace_result.used_fallback_sink_search, | |
| n_dangerous_call_sinks=len(trace_result.dangerous_call_sinks), | |
| cwe=cwe, | |
| l1_mapping_reasons=l1_mapping_reasons, | |
| ) | |
| def annotation_to_dict(ann: SampleAnnotation) -> dict: | |
| return asdict(ann) | |