bench(independent): freeze population v3 (PHP)

freeze.py, extended for v3 and still reading only git history, file lists and
fix diffs:
- a file the advisory names without a function resolves to the function(s)
  the fix's diff changes at the vulnerable pin (maintainer's decision 2),
  recorded with its basis (advisory-named / resolved-from-fix-diff); a new
  helper inserted at file scope adds no site;
- an inferred fix must change a declared site function, else the case drops;
- a single-language population gets a strict benign proposal (PHP change,
  merges judged with all they bring in, pre-vulnerable history as fallback).

All 15 cases verified, none dropped (pagible and podlove fixes confirmed).
Nine sites in eight cases resolved from diffs; five benign proposals
replaced by hand.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-30 18:15:52 +02:00
co-authored by Claude Opus 5.5
parent edcf145b00
commit 0a04dbfada
4 changed files with 852 additions and 18 deletions
+59
View File
@@ -0,0 +1,59 @@
"""freeze.py resolves an advisory's file-only site to the function the fix changes (population v3, decision 2).
The resolver reads PHP text only; it must not be fooled by braces in strings, comments or inline HTML, and a line
outside every function is `<global>`."""
from __future__ import annotations
import importlib.util
from pathlib import Path
SPEC = importlib.util.spec_from_file_location("freeze", Path(__file__).resolve().parents[1] / "benchmarks" / "independent" / "freeze.py")
freeze = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(freeze)
PHP = """<html>{ not code }</html>
<?php
// a comment with { brace
class Box
{
public function open($x)
{
$s = "}{ in a string";
return function ($y) { return $y; };
}
abstract protected function shape();
private static function &close() {
/* } */
return <<<EOT
} heredoc {
EOT;
}
}
$top = 1;
?>
<div>{</div>
<?php function helper() { return '#{'; }
"""
def test_named_functions_and_their_lines() -> None:
spans = {name: (first, last) for name, first, last in freeze.php_functions(PHP)}
assert spans == {"open": (6, 10), "close": (14, 19), "helper": (24, 24)}
def test_a_line_resolves_to_its_innermost_named_function_or_global() -> None:
spans = freeze.php_functions(PHP)
assert freeze.enclosing(spans, 9) == "open" # inside the closure: the named function owns it
assert freeze.enclosing(spans, 17) == "close"
assert freeze.enclosing(spans, 21) == "<global>"
assert freeze.enclosing(spans, 1) == "<global>"
def test_an_insertion_after_a_closing_brace_is_outside_the_function() -> None:
spans = freeze.php_functions(PHP)
assert freeze.enclosing(spans, 10) == "open" # a changed line on the closing brace
assert freeze.enclosing(spans, 10, inserted=True) == "<global>" # new code that follows it
assert freeze.enclosing(spans, 8, inserted=True) == "open"