Skip to main content

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 { 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}