use crate::analyze::function_summary::FunctionSummary;
use crate::utility::cert_c::ast_utils::get_node_text;
use crate::utility::cert_c::std_functions;
use lang_parsing_substrate::query;
use std::collections::{HashMap, HashSet};
use tree_sitter::Node;
pub fn callee_is_risky_source(callee: &str, summaries: &HashMap<String, FunctionSummary>) -> bool {
let ident = callee
.rsplit(|c: char| !c.is_alphanumeric() && c != '_')
.next()
.unwrap_or(callee)
.trim();
if std_functions::is_full_range_return_function(ident)
|| std_functions::is_untrusted_decode_function(ident)
{
return true;
}
if crate::analyze::function_summary::ENV03_TAINT_SOURCE_FUNCTIONS.contains(&ident) {
return true;
}
matches!(summaries.get(ident), Some(s) if s.has_env03_taint_source || s.returns_tainted)
}
pub fn collect_risky_vars(
body: &Node,
summaries: &HashMap<String, FunctionSummary>,
source: &str,
) -> HashSet<String> {
let mut set = HashSet::new();
collect_risky_vars_walk(body, summaries, source, &mut set);
set
}
pub fn operand_is_risky(
op: &Node,
risky_vars: &HashSet<String>,
summaries: &HashMap<String, FunctionSummary>,
global_writers: &HashMap<String, HashSet<String>>,
source: &str,
) -> bool {
match op.kind() {
"call_expression" => match op.child_by_field_name("function") {
Some(f) => callee_is_risky_source(&get_node_text(&f, source), summaries),
None => false,
},
"identifier" => {
let name = get_node_text(op, source);
risky_vars.contains(name) || global_is_tainted(name, global_writers, summaries)
}
"parenthesized_expression" => match op.named_child(0) {
Some(inner) => operand_is_risky(&inner, risky_vars, summaries, global_writers, source),
None => false,
},
"cast_expression" => match op.child_by_field_name("value") {
Some(value) => operand_is_risky(&value, risky_vars, summaries, global_writers, source),
None => false,
},
"binary_expression" => {
op.child_by_field_name("left").is_some_and(|l| {
operand_is_risky(&l, risky_vars, summaries, global_writers, source)
}) || op.child_by_field_name("right").is_some_and(|r| {
operand_is_risky(&r, risky_vars, summaries, global_writers, source)
})
}
"unary_expression" | "update_expression" => match op.child_by_field_name("argument") {
Some(arg) => operand_is_risky(&arg, risky_vars, summaries, global_writers, source),
None => false,
},
_ => false,
}
}
fn global_is_tainted(
name: &str,
global_writers: &HashMap<String, HashSet<String>>,
summaries: &HashMap<String, FunctionSummary>,
) -> bool {
match global_writers.get(name) {
Some(ws) => ws.iter().any(|w| {
matches!(summaries.get(w), Some(s) if s.has_env03_taint_source || s.returns_tainted)
}),
None => false,
}
}
fn collect_risky_vars_walk(
node: &Node,
summaries: &HashMap<String, FunctionSummary>,
source: &str,
set: &mut HashSet<String>,
) {
let candidates = query::find_descendants_of_kinds(
*node,
&[
"assignment_expression",
"init_declarator",
"call_expression",
],
);
for node in candidates {
match node.kind() {
"assignment_expression" => {
if let (Some(lhs), Some(rhs)) = (
node.child_by_field_name("left"),
node.child_by_field_name("right"),
) {
if lhs.kind() == "identifier" && rhs_is_risky_call(&rhs, summaries, source) {
set.insert(get_node_text(&lhs, source).trim().to_string());
}
}
}
"init_declarator" => {
if let (Some(decl), Some(value)) = (
node.child_by_field_name("declarator"),
node.child_by_field_name("value"),
) {
if rhs_is_risky_call(&value, summaries, source) {
if let Some(name) = init_declarator_name(&decl, source) {
set.insert(name);
}
}
}
}
"call_expression" => {
if let Some(f) = node.child_by_field_name("function") {
if callee_is_risky_source(&get_node_text(&f, source), summaries) {
if let Some(args) = node.child_by_field_name("arguments") {
collect_arg_identifiers(&args, source, set);
}
}
}
}
_ => {}
}
}
}
fn collect_arg_identifiers(args: &Node, source: &str, set: &mut HashSet<String>) {
for ident in query::find_descendants_of_kind(*args, "identifier") {
set.insert(get_node_text(&ident, source).trim().to_string());
}
}
fn init_declarator_name(decl: &Node, source: &str) -> Option<String> {
let mut current = *decl;
loop {
if current.kind() == "identifier" || current.kind() == "field_identifier" {
return Some(get_node_text(¤t, source).trim().to_string());
}
match current.child_by_field_name("declarator") {
Some(inner) => current = inner,
None => return None,
}
}
}
fn rhs_is_risky_call(
rhs: &Node,
summaries: &HashMap<String, FunctionSummary>,
source: &str,
) -> bool {
let mut node = *rhs;
loop {
match node.kind() {
"parenthesized_expression" => match node.named_child(0) {
Some(inner) => node = inner,
None => return false,
},
"cast_expression" => match node.child_by_field_name("value") {
Some(value) => node = value,
None => return false,
},
"call_expression" => {
return match node.child_by_field_name("function") {
Some(f) => callee_is_risky_source(&get_node_text(&f, source), summaries),
None => false,
};
}
_ => return false,
}
}
}