decy_analyzer/
subprocess_analysis.rs1use decy_hir::{HirExpression, HirFunction, HirStatement};
7
8#[derive(Debug, Clone, Default)]
10pub struct ForkExecPattern {
11 pub has_fork: bool,
13 pub has_exec: bool,
15 pub has_wait: bool,
17 pub command: Option<String>,
19 pub args: Vec<String>,
21 pub pid_var: Option<String>,
23}
24
25pub struct SubprocessDetector {
27 exec_functions: Vec<&'static str>,
29 wait_functions: Vec<&'static str>,
31}
32
33impl SubprocessDetector {
34 pub fn new() -> Self {
36 Self {
37 exec_functions: vec![
38 "execl", "execlp", "execle", "execv", "execvp", "execve", "execvpe",
39 ],
40 wait_functions: vec!["wait", "waitpid", "wait3", "wait4"],
41 }
42 }
43
44 pub fn detect(&self, func: &HirFunction) -> Vec<ForkExecPattern> {
46 let mut patterns = Vec::new();
47 let mut current = ForkExecPattern::default();
48
49 self.analyze_statements(func.body(), &mut current);
50
51 if current.has_fork || current.has_exec || current.has_wait {
52 patterns.push(current);
53 }
54
55 patterns
56 }
57
58 fn analyze_statements(&self, stmts: &[HirStatement], pattern: &mut ForkExecPattern) {
59 for stmt in stmts {
60 self.analyze_statement(stmt, pattern);
61 }
62 }
63
64 fn analyze_statement(&self, stmt: &HirStatement, pattern: &mut ForkExecPattern) {
65 match stmt {
66 HirStatement::VariableDeclaration { name, initializer: Some(init), .. } => {
67 if self.is_fork_call(init) {
68 pattern.has_fork = true;
69 pattern.pid_var = Some(name.clone());
70 }
71 self.analyze_expression(init, pattern);
72 }
73 HirStatement::Expression(expr) => {
74 self.analyze_expression(expr, pattern);
75 }
76 HirStatement::If { then_block, else_block, .. } => {
77 self.analyze_statements(then_block, pattern);
78 if let Some(else_stmts) = else_block {
79 self.analyze_statements(else_stmts, pattern);
80 }
81 }
82 HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
83 self.analyze_statements(body, pattern);
84 }
85 _ => {}
86 }
87 }
88
89 fn analyze_expression(&self, expr: &HirExpression, pattern: &mut ForkExecPattern) {
90 if let HirExpression::FunctionCall { function, arguments } = expr {
91 if function == "fork" {
92 pattern.has_fork = true;
93 } else if self.exec_functions.contains(&function.as_str()) {
94 pattern.has_exec = true;
95 self.extract_exec_args(arguments, pattern);
96 } else if self.wait_functions.contains(&function.as_str()) {
97 pattern.has_wait = true;
98 }
99 }
100 }
101
102 fn is_fork_call(&self, expr: &HirExpression) -> bool {
103 matches!(
104 expr,
105 HirExpression::FunctionCall { function, .. } if function == "fork"
106 )
107 }
108
109 fn extract_exec_args(&self, args: &[HirExpression], pattern: &mut ForkExecPattern) {
110 for (i, arg) in args.iter().enumerate() {
111 if let HirExpression::StringLiteral(s) = arg {
112 if i == 0 {
113 pattern.command = Some(s.clone());
114 } else {
115 pattern.args.push(s.clone());
116 }
117 }
118 }
119 }
120}
121
122impl Default for SubprocessDetector {
123 fn default() -> Self {
124 Self::new()
125 }
126}