decy_analyzer/
subprocess_analysis.rs

1//! Fork/exec subprocess pattern detection for C-to-Rust transpilation.
2//!
3//! Detects C subprocess patterns like fork()+exec*() and transforms them
4//! to Rust's `std::process::Command` API.
5
6use decy_hir::{HirExpression, HirFunction, HirStatement};
7
8/// Detected fork/exec subprocess pattern.
9#[derive(Debug, Clone, Default)]
10pub struct ForkExecPattern {
11    /// Whether fork() was detected
12    pub has_fork: bool,
13    /// Whether an exec*() call was detected
14    pub has_exec: bool,
15    /// Whether wait*() was detected
16    pub has_wait: bool,
17    /// Command path (first arg to exec*)
18    pub command: Option<String>,
19    /// Arguments extracted from exec*()
20    pub args: Vec<String>,
21    /// Variable holding fork() result
22    pub pid_var: Option<String>,
23}
24
25/// Detects fork/exec subprocess patterns in HIR functions.
26pub struct SubprocessDetector {
27    /// Exec function variants to detect
28    exec_functions: Vec<&'static str>,
29    /// Wait function variants to detect
30    wait_functions: Vec<&'static str>,
31}
32
33impl SubprocessDetector {
34    /// Create a new subprocess detector.
35    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    /// Detect fork/exec patterns in a function.
45    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}