mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 20:49:28 +00:00
learn: in-function def-use slice from the tree-sitter layer + leak audit (\$0)
slice.py renders source -> step -> sink lines with fixed wording for flow, no-flow and not-analysable; the prompt option is default-off (byte-identical default prompt). semantic IR: JS/C declarations count as assignments, augmented assignment keeps its target (gate, map_gate, pair_gate outputs unchanged). Audit over exp-004's 501 units: 59 flow, 161 no-flow, 281 not analysable; has-flow within-pair accuracy 0.52, pooled AUC 0.55 -- no leak. Fixes a numbering bug that rejected every excerpt with a blank line. With Codex. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2836b78819
commit
1b2c7993a7
@@ -17,7 +17,7 @@ _KIND = {
|
||||
},
|
||||
"javascript": {
|
||||
"function": frozenset({"function_declaration", "method_definition", "arrow_function", "function_expression"}),
|
||||
"assign": frozenset({"assignment_expression", "augmented_assignment_expression"}),
|
||||
"assign": frozenset({"assignment_expression", "augmented_assignment_expression", "variable_declarator"}),
|
||||
"call": frozenset({"call_expression"}),
|
||||
"args": frozenset({"arguments"}),
|
||||
"string": frozenset({"string"}),
|
||||
@@ -25,7 +25,7 @@ _KIND = {
|
||||
},
|
||||
"c": {
|
||||
"function": frozenset({"function_definition"}),
|
||||
"assign": frozenset({"assignment_expression"}),
|
||||
"assign": frozenset({"assignment_expression", "init_declarator"}),
|
||||
"call": frozenset({"call_expression"}),
|
||||
"args": frozenset({"argument_list"}),
|
||||
"string": frozenset({"string_literal"}),
|
||||
@@ -152,12 +152,17 @@ def _bind(node: Any, kinds: dict[str, frozenset[str]]) -> Bind | None:
|
||||
if not name:
|
||||
return None
|
||||
value_text = _text(value_node) if value_node is not None else ""
|
||||
augmented = node.type in {"augmented_assignment", "augmented_assignment_expression"}
|
||||
names = _identifiers(value_node) if value_node is not None else ()
|
||||
if augmented:
|
||||
names = (name, *names)
|
||||
return Bind(
|
||||
name=name,
|
||||
order=int(node.end_byte),
|
||||
line=_line(node),
|
||||
value_text=value_text,
|
||||
is_constant=_is_constant(value_node, kinds) if value_node is not None else False,
|
||||
names=_identifiers(value_node) if value_node is not None else (),
|
||||
is_constant=not augmented and _is_constant(value_node, kinds) if value_node is not None else False,
|
||||
names=names,
|
||||
call_name=_call_name(value_node, kinds) if value_node is not None and value_node.type in kinds["call"] else None,
|
||||
)
|
||||
|
||||
@@ -167,6 +172,7 @@ def _call(node: Any, kinds: dict[str, frozenset[str]]) -> CallSite:
|
||||
arg_nodes = list(getattr(args_node, "named_children", ()) or ()) if args_node is not None else []
|
||||
extra = len(arg_nodes) >= 2 and arg_nodes[1].type in _SEQUENCE
|
||||
return CallSite(
|
||||
order=int(node.end_byte),
|
||||
name=_callee_name(node, args_node),
|
||||
line=_line(node),
|
||||
arg_texts=tuple(_text(arg) for arg in arg_nodes),
|
||||
@@ -211,6 +217,9 @@ def _function_name(node: Any) -> str:
|
||||
|
||||
|
||||
def _params(node: Any, kinds: dict[str, frozenset[str]]) -> tuple[str, ...]:
|
||||
parameter = node.child_by_field_name("parameter")
|
||||
if parameter is not None:
|
||||
return _identifiers(parameter)
|
||||
for child in _walk(node, skip=frozenset()):
|
||||
if child.type in kinds["params"]:
|
||||
return _identifiers(child)
|
||||
|
||||
@@ -15,6 +15,7 @@ class Bind:
|
||||
is_constant: bool
|
||||
names: tuple[str, ...]
|
||||
call_name: str | None
|
||||
order: int = 0 # evaluation end position; calls precede their containing assignment
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -25,6 +26,7 @@ class CallSite:
|
||||
arg_is_constant: tuple[bool, ...]
|
||||
arg_names: tuple[tuple[str, ...], ...]
|
||||
extra_arg_is_sequence: bool
|
||||
order: int = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -119,6 +121,7 @@ def _collect_stmt(stmt: ast.stmt, binds: list[Bind], calls: list[CallSite]) -> N
|
||||
is_constant=value_bind[1],
|
||||
names=value_bind[2],
|
||||
call_name=value_bind[3],
|
||||
order=_end_position(stmt.value),
|
||||
)
|
||||
)
|
||||
_collect_expr_calls(stmt.value, calls)
|
||||
@@ -133,6 +136,7 @@ def _collect_stmt(stmt: ast.stmt, binds: list[Bind], calls: list[CallSite]) -> N
|
||||
is_constant=value_bind[1],
|
||||
names=value_bind[2],
|
||||
call_name=value_bind[3],
|
||||
order=_end_position(stmt.value),
|
||||
)
|
||||
)
|
||||
_collect_expr_calls(stmt.value, calls)
|
||||
@@ -147,6 +151,7 @@ def _collect_stmt(stmt: ast.stmt, binds: list[Bind], calls: list[CallSite]) -> N
|
||||
is_constant=False,
|
||||
names=tuple(sorted(set(value_bind[2]) | {stmt.target.id})),
|
||||
call_name=value_bind[3],
|
||||
order=_end_position(stmt.value),
|
||||
)
|
||||
)
|
||||
_collect_expr_calls(stmt.value, calls)
|
||||
@@ -190,6 +195,7 @@ def _call_site(node: ast.Call) -> CallSite:
|
||||
if len(node.args) >= 2:
|
||||
extra_sequence = isinstance(node.args[1], ast.Tuple | ast.List | ast.Set)
|
||||
return CallSite(
|
||||
order=_end_position(node),
|
||||
name=_call_name(node),
|
||||
line=node.lineno,
|
||||
arg_texts=tuple(arg_texts),
|
||||
@@ -199,6 +205,10 @@ def _call_site(node: ast.Call) -> CallSite:
|
||||
)
|
||||
|
||||
|
||||
def _end_position(node: ast.AST) -> int:
|
||||
return int(getattr(node, "end_lineno", 0) or 0) * 1_000_000 + int(getattr(node, "end_col_offset", 0) or 0)
|
||||
|
||||
|
||||
def _call_name(node: ast.AST) -> str:
|
||||
func = node.func if isinstance(node, ast.Call) else node
|
||||
return _unparse(func)
|
||||
|
||||
Reference in New Issue
Block a user