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 {
67 name,
68 initializer: Some(init),
69 ..
70 } => {
71 if self.is_fork_call(init) {
72 pattern.has_fork = true;
73 pattern.pid_var = Some(name.clone());
74 }
75 self.analyze_expression(init, pattern);
76 }
77 HirStatement::Expression(expr) => {
78 self.analyze_expression(expr, pattern);
79 }
80 HirStatement::If {
81 then_block,
82 else_block,
83 ..
84 } => {
85 self.analyze_statements(then_block, pattern);
86 if let Some(else_stmts) = else_block {
87 self.analyze_statements(else_stmts, pattern);
88 }
89 }
90 HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
91 self.analyze_statements(body, pattern);
92 }
93 _ => {}
94 }
95 }
96
97 fn analyze_expression(&self, expr: &HirExpression, pattern: &mut ForkExecPattern) {
98 if let HirExpression::FunctionCall {
99 function,
100 arguments,
101 } = expr
102 {
103 if function == "fork" {
104 pattern.has_fork = true;
105 } else if self.exec_functions.contains(&function.as_str()) {
106 pattern.has_exec = true;
107 self.extract_exec_args(arguments, pattern);
108 } else if self.wait_functions.contains(&function.as_str()) {
109 pattern.has_wait = true;
110 }
111 }
112 }
113
114 fn is_fork_call(&self, expr: &HirExpression) -> bool {
115 matches!(
116 expr,
117 HirExpression::FunctionCall { function, .. } if function == "fork"
118 )
119 }
120
121 fn extract_exec_args(&self, args: &[HirExpression], pattern: &mut ForkExecPattern) {
122 for (i, arg) in args.iter().enumerate() {
123 if let HirExpression::StringLiteral(s) = arg {
124 if i == 0 {
125 pattern.command = Some(s.clone());
126 } else {
127 pattern.args.push(s.clone());
128 }
129 }
130 }
131 }
132}
133
134impl Default for SubprocessDetector {
135 fn default() -> Self {
136 Self::new()
137 }
138}