gbf_core/decompiler/structure_analysis/
tail_region_reducer.rs

1#![deny(missing_docs)]
2
3use std::backtrace::Backtrace;
4
5use crate::decompiler::ast::{
6    AstKind, control_flow::ControlFlowNode, expr::ExprKind, new_acylic_condition, new_else, new_if,
7    ptr::P,
8};
9
10use super::{
11    ControlFlowEdgeType, RegionReducer, StructureAnalysis, StructureAnalysisError,
12    region::{RegionId, RegionType},
13};
14
15/// Reduces a tail region.
16pub struct TailRegionReducer;
17
18impl TailRegionReducer {
19    /// Extracts the jump expression from a region, if available.
20    fn extract_jump_expr(
21        analysis: &mut StructureAnalysis,
22        region_id: RegionId,
23    ) -> Result<ExprKind, StructureAnalysisError> {
24        let region = analysis.regions.get_mut(region_id.index).ok_or(
25            StructureAnalysisError::RegionNotFound {
26                region_id,
27                backtrace: Backtrace::capture(),
28            },
29        )?;
30        region
31            .get_jump_expr()
32            .ok_or(StructureAnalysisError::ExpectedConditionNotFound {
33                backtrace: Backtrace::capture(),
34            })
35            .cloned()
36    }
37
38    /// Remove the given node and its adjacent edges from the region graph.
39    fn cleanup_region(
40        analysis: &mut StructureAnalysis,
41        remove_node: RegionId,
42        start_node: RegionId,
43    ) -> Result<(), StructureAnalysisError> {
44        analysis.remove_edge(start_node, remove_node)?;
45        analysis.remove_node(remove_node)?;
46        Ok(())
47    }
48
49    /// Handles merging the tail structure into the original region.
50    fn merge_tail(
51        analysis: &mut StructureAnalysis,
52        region_id: RegionId,
53        tail: Vec<P<ControlFlowNode>>,
54        is_tail: bool,
55    ) -> Result<(), StructureAnalysisError> {
56        let region = analysis.regions.get_mut(region_id.index).ok_or(
57            StructureAnalysisError::RegionNotFound {
58                region_id,
59                backtrace: Backtrace::capture(),
60            },
61        )?;
62        region.push_nodes(tail.into_iter().map(AstKind::ControlFlow).collect());
63        if is_tail {
64            region.set_region_type(RegionType::Tail);
65        } else {
66            region.set_region_type(RegionType::Linear);
67        }
68        region.remove_jump_expr();
69        Ok(())
70    }
71
72    /// Extracts the nodes of a given region.
73    fn get_region_nodes(
74        analysis: &StructureAnalysis,
75        region_id: RegionId,
76    ) -> Result<Vec<AstKind>, StructureAnalysisError> {
77        let region = analysis.regions.get(region_id.index).ok_or(
78            StructureAnalysisError::RegionNotFound {
79                region_id,
80                backtrace: Backtrace::capture(),
81            },
82        )?;
83        Ok(region.get_nodes().to_vec())
84    }
85}
86
87impl RegionReducer for TailRegionReducer {
88    fn reduce_region(
89        &mut self,
90        analysis: &mut StructureAnalysis,
91        region_id: RegionId,
92    ) -> Result<bool, StructureAnalysisError> {
93        // This logic applies to control flow regions only
94        if analysis.get_region_type(region_id)? != RegionType::ControlFlow {
95            return Ok(false);
96        }
97
98        // Step 1: Extract the jump expression
99        let jump_expr = Self::extract_jump_expr(analysis, region_id)?;
100
101        // Step 2: Get successors
102        let successors = analysis.get_successors(region_id)?;
103        if successors.len() != 2 {
104            return Err(StructureAnalysisError::Other {
105                message: "Tail reduction requires exactly two successors".to_string(),
106                backtrace: Backtrace::capture(),
107            });
108        }
109
110        let branch_region_id = successors
111            .iter()
112            .find(|(_, edge_type)| *edge_type == ControlFlowEdgeType::Branch)
113            .map(|(id, _)| *id)
114            .ok_or(StructureAnalysisError::Other {
115                message: "Control flow region must have a branch successor".to_string(),
116                backtrace: Backtrace::capture(),
117            })?;
118
119        let fallthrough_region_id = successors
120            .iter()
121            .find(|(_, edge_type)| *edge_type == ControlFlowEdgeType::Fallthrough)
122            .map(|(id, _)| *id)
123            .ok_or(StructureAnalysisError::Other {
124                message: "Control flow region must have a fallthrough successor".to_string(),
125                backtrace: Backtrace::capture(),
126            })?;
127
128        let branch_single_pred = analysis.has_single_predecessor(branch_region_id)?;
129        let fallthrough_single_pred = analysis.has_single_predecessor(fallthrough_region_id)?;
130
131        // Step 3: Merge if both successors are tail regions with a single predecessor
132        if analysis.get_region_type(branch_region_id)? == RegionType::Tail
133            && branch_single_pred
134            && analysis.get_region_type(fallthrough_region_id)? == RegionType::Tail
135            && fallthrough_single_pred
136        {
137            // Ensure that only the region is a predecessor of the branch and fallthrough regions
138            if !analysis.has_single_predecessor(branch_region_id)?
139                || !analysis.has_single_predecessor(fallthrough_region_id)?
140            {
141                return Ok(false);
142            }
143
144            analysis.before_reduce(region_id);
145
146            let branch_statements = Self::get_region_nodes(analysis, branch_region_id)?;
147            let fallthrough_statements = Self::get_region_nodes(analysis, fallthrough_region_id)?;
148
149            let mut if_else: P<ControlFlowNode> = new_if(jump_expr, fallthrough_statements).into();
150            let mut else_stmt: P<ControlFlowNode> = new_else(branch_statements).into();
151
152            if_else
153                .metadata_mut()
154                .add_comment(fallthrough_region_id.to_string());
155            else_stmt
156                .metadata_mut()
157                .add_comment(branch_region_id.to_string());
158
159            Self::merge_tail(analysis, region_id, vec![if_else, else_stmt], true)?;
160            Self::cleanup_region(analysis, branch_region_id, region_id)?;
161            Self::cleanup_region(analysis, fallthrough_region_id, region_id)?;
162            return Ok(true);
163        }
164
165        // Step 4: Merge if the first successor is a tail region with a single predecessor
166        if analysis.get_region_type(branch_region_id)? == RegionType::Tail && branch_single_pred {
167            // Ensure that only the region is a predecessor of the branch region
168            if !analysis.has_single_predecessor(branch_region_id)? {
169                return Ok(false);
170            }
171            analysis.before_reduce(region_id);
172
173            let branch_statements = Self::get_region_nodes(analysis, branch_region_id)?;
174            let mut if_stmt: P<ControlFlowNode> = new_acylic_condition(
175                jump_expr,
176                branch_statements,
177                analysis.get_branch_opcode(region_id)?,
178            )
179            .map_err(|e| StructureAnalysisError::AstNodeError {
180                source: Box::new(e),
181                backtrace: Backtrace::capture(),
182            })?
183            .into();
184
185            if_stmt
186                .metadata_mut()
187                .add_comment(branch_region_id.to_string());
188
189            Self::merge_tail(analysis, region_id, vec![if_stmt], false)?;
190            Self::cleanup_region(analysis, branch_region_id, region_id)?;
191            return Ok(true);
192        }
193
194        // Step 5: Merge if the second successor is a tail region with a single predecessor
195        if analysis.get_region_type(fallthrough_region_id)? == RegionType::Tail
196            && fallthrough_single_pred
197        {
198            // Ensure that only the region is a predecessor of the fallthrough region
199            if !analysis.has_single_predecessor(fallthrough_region_id)? {
200                return Ok(false);
201            }
202            analysis.before_reduce(region_id);
203
204            let fallthrough_statements = Self::get_region_nodes(analysis, fallthrough_region_id)?;
205            let mut if_stmt: P<ControlFlowNode> = new_acylic_condition(
206                jump_expr,
207                fallthrough_statements,
208                analysis.get_branch_opcode(region_id)?,
209            )
210            .map_err(|e| StructureAnalysisError::AstNodeError {
211                source: Box::new(e),
212                backtrace: Backtrace::capture(),
213            })?
214            .into();
215
216            if_stmt
217                .metadata_mut()
218                .add_comment(fallthrough_region_id.to_string());
219
220            Self::merge_tail(analysis, region_id, vec![if_stmt], false)?;
221            Self::cleanup_region(analysis, fallthrough_region_id, region_id)?;
222            return Ok(true);
223        }
224
225        Ok(false)
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use crate::decompiler::ast::{new_assignment, new_id};
232
233    use super::*;
234
235    #[test]
236    fn test_tail_reduce() -> Result<(), StructureAnalysisError> {
237        let mut structure_analysis = StructureAnalysis::new(false, 100);
238
239        let entry_region = structure_analysis.add_region(RegionType::ControlFlow);
240        let region_1 = structure_analysis.add_region(RegionType::Tail);
241        let region_2 = structure_analysis.add_region(RegionType::Tail);
242
243        // Push nodes to regions
244        structure_analysis
245            .push_to_region(entry_region, new_assignment(new_id("foo"), new_id("bar")));
246        structure_analysis
247            .get_region_mut(entry_region)?
248            .set_jump_expr(Some(new_id("foo").into()));
249        structure_analysis.push_to_region(region_1, new_assignment(new_id("x"), new_id("y")));
250        structure_analysis.push_to_region(region_2, new_assignment(new_id("a"), new_id("b")));
251
252        structure_analysis.connect_regions(entry_region, region_1, ControlFlowEdgeType::Branch)?;
253        structure_analysis.connect_regions(
254            entry_region,
255            region_2,
256            ControlFlowEdgeType::Fallthrough,
257        )?;
258
259        structure_analysis.execute()?;
260        assert_eq!(structure_analysis.region_graph.node_count(), 1);
261
262        let region = structure_analysis.get_entry_region();
263        let region = structure_analysis.get_region(region)?;
264
265        assert_eq!(region.get_nodes().len(), 3);
266
267        // Ensure that the final region is a tail region
268        assert_eq!(region.get_region_type(), RegionType::Tail);
269
270        Ok(())
271    }
272}