use decy_hir::{HirExpression, HirFunction, HirStatement};
#[derive(Debug, Clone, Default)]
pub struct ForkExecPattern {
pub has_fork: bool,
pub has_exec: bool,
pub has_wait: bool,
pub command: Option<String>,
pub args: Vec<String>,
pub pid_var: Option<String>,
}
pub struct SubprocessDetector {
exec_functions: Vec<&'static str>,
wait_functions: Vec<&'static str>,
}
impl SubprocessDetector {
pub fn new() -> Self {
Self {
exec_functions: vec![
"execl", "execlp", "execle", "execv", "execvp", "execve", "execvpe",
],
wait_functions: vec!["wait", "waitpid", "wait3", "wait4"],
}
}
pub fn detect(&self, func: &HirFunction) -> Vec<ForkExecPattern> {
let mut patterns = Vec::new();
let mut current = ForkExecPattern::default();
self.analyze_statements(func.body(), &mut current);
if current.has_fork || current.has_exec || current.has_wait {
patterns.push(current);
}
patterns
}
fn analyze_statements(&self, stmts: &[HirStatement], pattern: &mut ForkExecPattern) {
for stmt in stmts {
self.analyze_statement(stmt, pattern);
}
}
fn analyze_statement(&self, stmt: &HirStatement, pattern: &mut ForkExecPattern) {
match stmt {
HirStatement::VariableDeclaration { name, initializer: Some(init), .. } => {
if self.is_fork_call(init) {
pattern.has_fork = true;
pattern.pid_var = Some(name.clone());
}
self.analyze_expression(init, pattern);
}
HirStatement::Expression(expr) => {
self.analyze_expression(expr, pattern);
}
HirStatement::If { then_block, else_block, .. } => {
self.analyze_statements(then_block, pattern);
if let Some(else_stmts) = else_block {
self.analyze_statements(else_stmts, pattern);
}
}
HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
self.analyze_statements(body, pattern);
}
_ => {}
}
}
fn analyze_expression(&self, expr: &HirExpression, pattern: &mut ForkExecPattern) {
if let HirExpression::FunctionCall { function, arguments } = expr {
if function == "fork" {
pattern.has_fork = true;
} else if self.exec_functions.contains(&function.as_str()) {
pattern.has_exec = true;
self.extract_exec_args(arguments, pattern);
} else if self.wait_functions.contains(&function.as_str()) {
pattern.has_wait = true;
}
}
}
fn is_fork_call(&self, expr: &HirExpression) -> bool {
matches!(
expr,
HirExpression::FunctionCall { function, .. } if function == "fork"
)
}
fn extract_exec_args(&self, args: &[HirExpression], pattern: &mut ForkExecPattern) {
for (i, arg) in args.iter().enumerate() {
if let HirExpression::StringLiteral(s) = arg {
if i == 0 {
pattern.command = Some(s.clone());
} else {
pattern.args.push(s.clone());
}
}
}
}
}
impl Default for SubprocessDetector {
fn default() -> Self {
Self::new()
}
}