mirror of
https://github.com/elicpeter/nyx.git
synced 2026-07-15 21:11:02 +02:00
feat: Add SSA summaries support for validated parameter propagation and enhance loop body error handling
This commit is contained in:
parent
92aaa36ed6
commit
48bc43e1a6
11 changed files with 438 additions and 69 deletions
|
|
@ -663,7 +663,6 @@ pub(crate) fn collect_idents_with_paths(
|
|||
if let Some(path) = member_expr_text(n, code) {
|
||||
paths.push(path);
|
||||
}
|
||||
// Also collect individual idents as fallback
|
||||
collect_idents(n, code, idents);
|
||||
}
|
||||
"identifier"
|
||||
|
|
|
|||
|
|
@ -3481,15 +3481,27 @@ pub(super) fn build_sub<'a>(
|
|||
let mut loop_breaks = Vec::new();
|
||||
let mut loop_continues = Vec::new();
|
||||
|
||||
// Body = first (and usually only) block child.
|
||||
let body = ast
|
||||
.child_by_field_name("body")
|
||||
.or_else(|| {
|
||||
let mut c = ast.walk();
|
||||
ast.children(&mut c)
|
||||
.find(|n| lookup(lang, n.kind()) == Kind::Block)
|
||||
})
|
||||
.expect("loop without body");
|
||||
// Body = first (and usually only) block child. Tree-sitter error
|
||||
// recovery (or a fuzz mutation that truncates a `for`/`while`
|
||||
// header before the block) can leave a loop node with no body
|
||||
// child at all. Match the InfiniteLoop arm above and degrade
|
||||
// gracefully instead of panicking — header alone is a valid CFG
|
||||
// skeleton for the malformed input.
|
||||
let body = match ast.child_by_field_name("body").or_else(|| {
|
||||
let mut c = ast.walk();
|
||||
ast.children(&mut c)
|
||||
.find(|n| lookup(lang, n.kind()) == Kind::Block)
|
||||
}) {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
warn!(
|
||||
"loop without body (error recovery?): kind={} byte={}",
|
||||
ast.kind(),
|
||||
ast.start_byte()
|
||||
);
|
||||
return vec![header];
|
||||
}
|
||||
};
|
||||
|
||||
if has_short_circuit {
|
||||
let cond_ast = cond_subtree.unwrap();
|
||||
|
|
@ -3625,9 +3637,14 @@ pub(super) fn build_sub<'a>(
|
|||
// swallowed and the gated sinks they contain become invisible
|
||||
// to classification. Mirrors the same recursion done by the
|
||||
// CallWrapper / CallFn arms. Motivated by CVE-2025-64430.
|
||||
//
|
||||
// Disconnect the placeholder Seq edge from the call after
|
||||
// build_sub returns; the inner body is independently
|
||||
// registered, so the outer call should flow straight to its
|
||||
// real successor (the Return below) without a phantom branch.
|
||||
let nested = collect_nested_function_nodes(ast, lang);
|
||||
for func_node in nested {
|
||||
build_sub(
|
||||
let placeholders = build_sub(
|
||||
func_node,
|
||||
&[call_idx],
|
||||
g,
|
||||
|
|
@ -3645,6 +3662,13 @@ pub(super) fn build_sub<'a>(
|
|||
next_body_id,
|
||||
current_body_id,
|
||||
);
|
||||
for ph in placeholders {
|
||||
let to_remove: Vec<_> =
|
||||
g.edges_connecting(call_idx, ph).map(|e| e.id()).collect();
|
||||
for eid in to_remove {
|
||||
g.remove_edge(eid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vec::new()
|
||||
|
|
@ -3703,10 +3727,11 @@ pub(super) fn build_sub<'a>(
|
|||
|
||||
// Same nested-function recursion as the Return arm: a
|
||||
// `throw new Promise(() => { ... })` would otherwise lose
|
||||
// any inner gated sinks.
|
||||
// any inner gated sinks. Disconnect the placeholder edge
|
||||
// (see Return arm comment).
|
||||
let nested = collect_nested_function_nodes(ast, lang);
|
||||
for func_node in nested {
|
||||
build_sub(
|
||||
let placeholders = build_sub(
|
||||
func_node,
|
||||
&[call_idx],
|
||||
g,
|
||||
|
|
@ -3724,6 +3749,13 @@ pub(super) fn build_sub<'a>(
|
|||
next_body_id,
|
||||
current_body_id,
|
||||
);
|
||||
for ph in placeholders {
|
||||
let to_remove: Vec<_> =
|
||||
g.edges_connecting(call_idx, ph).map(|e| e.id()).collect();
|
||||
for eid in to_remove {
|
||||
g.remove_edge(eid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vec::new()
|
||||
|
|
@ -4370,10 +4402,18 @@ pub(super) fn build_sub<'a>(
|
|||
|
||||
// Recurse into any function expressions nested in arguments
|
||||
// (e.g. `app.get('/path', function(req, res) { ... })`)
|
||||
// so that they get proper function summaries.
|
||||
// so that they get proper function summaries. The build_sub
|
||||
// invocation registers the inner body but also adds a
|
||||
// Seq-edge `node → placeholder` from the inner Kind::Function
|
||||
// arm. That phantom successor turns the outer call into a
|
||||
// 2-successor branch with an empty Return(None) leg, which
|
||||
// breaks `validated_params_to_return` summary extraction
|
||||
// (CVE-2026-25544). Disconnect the spurious edge after
|
||||
// build_sub returns; the inner body is still reachable to
|
||||
// closure-capture passes via `parent_body_id` metadata.
|
||||
let nested = collect_nested_function_nodes(ast, lang);
|
||||
for func_node in nested {
|
||||
build_sub(
|
||||
let placeholders = build_sub(
|
||||
func_node,
|
||||
&[node],
|
||||
g,
|
||||
|
|
@ -4391,6 +4431,12 @@ pub(super) fn build_sub<'a>(
|
|||
next_body_id,
|
||||
current_body_id,
|
||||
);
|
||||
for ph in placeholders {
|
||||
let to_remove: Vec<_> = g.edges_connecting(node, ph).map(|e| e.id()).collect();
|
||||
for eid in to_remove {
|
||||
g.remove_edge(eid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rust match-guard synthesis: `let <name> = match <scrutinee> { <arm> if <guard> => .., ... }`
|
||||
|
|
@ -4462,9 +4508,12 @@ pub(super) fn build_sub<'a>(
|
|||
|
||||
// Recurse into any function expressions nested in arguments.
|
||||
// Each nested function hits Kind::Function and becomes a separate body.
|
||||
// See sibling comment in CallWrapper arm: disconnect the
|
||||
// declaration-marker placeholder Seq edge after build_sub
|
||||
// returns, so the outer body's CFG isn't artificially branched.
|
||||
let nested = collect_nested_function_nodes(ast, lang);
|
||||
for func_node in nested {
|
||||
build_sub(
|
||||
let placeholders = build_sub(
|
||||
func_node,
|
||||
&[n],
|
||||
g,
|
||||
|
|
@ -4482,6 +4531,12 @@ pub(super) fn build_sub<'a>(
|
|||
next_body_id,
|
||||
current_body_id,
|
||||
);
|
||||
for ph in placeholders {
|
||||
let to_remove: Vec<_> = g.edges_connecting(n, ph).map(|e| e.id()).collect();
|
||||
for eid in to_remove {
|
||||
g.remove_edge(eid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec![n]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue