from src.cpg.annotate import annotate_sample STRCPY_FUNC = """void copy_input(char *src) { char buf[64]; strcpy(buf, src); } """ def test_annotate_sample_vulnerable_row(): ann = annotate_sample("s1", STRCPY_FUNC, patch_lines_vuln=[3], cwe="CWE-119") assert ann.l1_lines == [3] assert 3 in ann.l2_lines assert ann.l3_lines == [1, 2, 3, 4] assert ann.trace_unverifiable is False assert len(ann.trace_gt) >= 1 assert ann.annotation_backend == "tree_sitter" def test_annotate_sample_benign_row_has_no_localization(): ann = annotate_sample("s2", STRCPY_FUNC, patch_lines_vuln=[], cwe=None) assert ann.l1_lines == [] assert ann.l2_lines == [] assert ann.l3_lines == [1, 2, 3, 4] assert ann.trace_unverifiable is True assert ann.trace_reason == "target_0_no_patch_lines" MULTI_HOP_FUNC = """void process(char *input) { int n = strlen(input); char buf[128]; memcpy(buf, input, n); } """ def test_annotate_sample_l2_includes_backward_slice(): ann = annotate_sample("s3", MULTI_HOP_FUNC, patch_lines_vuln=[4], cwe="CWE-119") assert 4 in ann.l1_lines # L2 should pull in line 2 (n = strlen(input)) via the def-use slice from # the memcpy call that uses n. assert 2 in ann.l2_lines NO_DANGEROUS_CALL_FUNC = """int add(int a, int b) { return a + b; } """ def test_annotate_sample_traces_even_without_a_dangerous_call(): # Amendment (DECISIONS.md "Phase 2 trace-mining redefinition"): the sink # is L1 itself, not a dangerous-call-table lookup. This function has no # "dangerous call" at all, but L1 (the return statement) directly uses # both parameters with no in-function predecessor, so it's a valid # zero-hop trace — previously this was unconditionally trace_unverifiable. ann = annotate_sample("s4", NO_DANGEROUS_CALL_FUNC, patch_lines_vuln=[2], cwe="CWE-119") assert ann.trace_unverifiable is False assert ann.trace_gt != [] assert ann.trace_lengths == [1] assert ann.used_fallback_sink_search is False NO_SOURCE_NO_SINK_FUNC = """void f(void) { do_something(); } """ def test_annotate_sample_genuinely_unverifiable_when_l1_has_no_inputs(): # L1 statement uses no identifiers at all (no params, no in-function # defs) -> genuinely no source to trace back to. ann = annotate_sample("s5", NO_SOURCE_NO_SINK_FUNC, patch_lines_vuln=[2], cwe=None) assert ann.trace_unverifiable is True assert ann.trace_reason == "no_source_found"