mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
feat(semantic): the hunter's semantic producer covers Java and PHP
Java parsed already, but the producer failed every Java row because no obligation facts existed for the language and that failure took roles and def_use down with it. PHP had no grammar. Now: tree-sitter-php in the semantic extra with PHP node kinds in the CST reader; obligation facts for Jenkins/Stapler, Spring Security and Servlet code (java.toml) and for Laravel, WordPress and Symfony (php.toml), each header naming the training clones and references the names were checked against; a language with an IR but no obligation facts keeps roles and def_use and records obligations as unchecked (None), which the engineered features treat as missing, never as zero. Proven on the committed pool: 6 of 6 Java rows and 33 of 33 PHP rows of one fix change each now carry semantic features. Implemented by Codex; gated here with ruff, mypy and the full suite. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
6fe46c3062
commit
4063fd1562
@@ -68,3 +68,72 @@ def test_c_constant_system_demotes(tmp_path: Path) -> None:
|
||||
assert demoted
|
||||
assert demoted[0].dominating_fact == "constant"
|
||||
assert demoted[0].evidence_level == "static_corroboration"
|
||||
|
||||
|
||||
def test_php_functions_binds_calls_and_parameters(tmp_path: Path) -> None:
|
||||
_require_extra()
|
||||
from tree_sitter import Parser
|
||||
|
||||
from openultrasast.semantic.cst import _KIND, _walk
|
||||
from openultrasast.semantic.extra import grammar_for
|
||||
|
||||
text = """<p>Text mode</p>
|
||||
<?php
|
||||
class Example {
|
||||
public function run(?Thing $x, &$y, ...$rest) {
|
||||
$value = $this->g($y);
|
||||
$value .= "hello $x";
|
||||
$allowed = Gate::authorize('read', $x);
|
||||
$this?->g($y);
|
||||
}
|
||||
}
|
||||
function free_fn($x) { return consume($x); }
|
||||
$closure = function ($z) { return consume($z); };
|
||||
$arrow = fn($a) => consume($a);
|
||||
"""
|
||||
path = tmp_path / "sample.php"
|
||||
path.write_text(text)
|
||||
data = path.read_bytes()
|
||||
assert len(data) == len(text.encode()) > 0
|
||||
tree = Parser(grammar_for("php")).parse(data)
|
||||
assert not tree.root_node.has_error
|
||||
seen = {node.type for node in _walk(tree.root_node)}
|
||||
# Exercise every registered kind against the installed grammar, including fn.
|
||||
assert set().union(*_KIND["php"].values()) <= seen
|
||||
ir = parse_file(path.name, data.decode(), "php")
|
||||
assert ir.parse_ok and ir.engine == "tree-sitter"
|
||||
assert [(fn.name, fn.start_line, fn.end_line, fn.params) for fn in ir.functions] == [
|
||||
("<module>", 1, 14, ()),
|
||||
("run", 4, 9, ("$x", "$y", "$rest")),
|
||||
("free_fn", 11, 11, ("$x",)),
|
||||
("<anon>", 12, 12, ("$z",)),
|
||||
("<anon>", 13, 13, ("$a",)),
|
||||
]
|
||||
module, method, free, closure, arrow = ir.functions
|
||||
assert module.calls == ()
|
||||
assert [b.name for b in module.binds] == ["$closure", "$arrow"]
|
||||
assert [(b.name, b.line, b.names, b.call_name) for b in method.binds] == [
|
||||
("$value", 5, ("$this", "$y"), "g"),
|
||||
("$value", 6, ("$value", "$x"), None),
|
||||
("$allowed", 7, ("$x",), "authorize"),
|
||||
]
|
||||
assert [(c.name, c.line, c.arg_texts, c.arg_names) for c in method.calls] == [
|
||||
("g", 5, ("$y",), (("$y",),)),
|
||||
("authorize", 7, ("'read'", "$x"), ((), ("$x",))),
|
||||
("g", 8, ("$y",), (("$y",),)),
|
||||
]
|
||||
assert method.calls[1].arg_is_constant == (True, False)
|
||||
for fn in (free, closure, arrow):
|
||||
assert [call.name for call in fn.calls] == ["consume"]
|
||||
assert fn.calls[0].arg_names == (fn.params,)
|
||||
|
||||
|
||||
def test_php_interpolation_and_named_arguments() -> None:
|
||||
_require_extra()
|
||||
ir = parse_file("sample.php", '<?php function f($x) { $s = "hello $x"; consume(value: "hello", other: "$x"); }', "php")
|
||||
assert ir.parse_ok
|
||||
fn = ir.functions[1]
|
||||
assert fn.binds[0].names == ("$x",) and not fn.binds[0].is_constant
|
||||
assert fn.calls[0].arg_texts == ('"hello"', '"$x"')
|
||||
assert fn.calls[0].arg_names == ((), ("$x",))
|
||||
assert fn.calls[0].arg_is_constant == (True, False)
|
||||
|
||||
Reference in New Issue
Block a user