gbf_core/decompiler/structure_analysis/
if_region_reducer.rs

1#![deny(missing_docs)]
2
3use std::backtrace::Backtrace;
4
5use crate::decompiler::ast::{
6    AstKind, control_flow::ControlFlowNode, emit, expr::ExprKind, new_acylic_condition, new_else,
7    new_if, ptr::P,
8};
9
10use super::{
11    ControlFlowEdgeType, RegionReducer, StructureAnalysis, StructureAnalysisError,
12    region::{RegionId, RegionType},
13};
14
15/// Reduces an if region.
16pub struct IfRegionReducer;
17
18impl IfRegionReducer {
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.
39    fn cleanup_region(
40        analysis: &mut StructureAnalysis,
41        remove_node: RegionId,
42        start_node: RegionId,
43        final_node: RegionId,
44    ) -> Result<(), StructureAnalysisError> {
45        analysis.remove_edge(start_node, remove_node)?;
46        analysis.remove_edge(remove_node, final_node)?;
47        analysis.remove_node(remove_node)?;
48        Ok(())
49    }
50
51    /// Handles merging the conditional structure into the original region.
52    fn merge_conditional(
53        analysis: &mut StructureAnalysis,
54        region_id: RegionId,
55        cond: Vec<P<ControlFlowNode>>,
56    ) -> Result<(), StructureAnalysisError> {
57        let region = analysis.regions.get_mut(region_id.index).ok_or(
58            StructureAnalysisError::RegionNotFound {
59                region_id,
60                backtrace: Backtrace::capture(),
61            },
62        )?;
63        region.push_nodes(cond.into_iter().map(|node| node.into()).collect());
64        region.set_region_type(RegionType::Linear);
65        region.remove_jump_expr();
66        Ok(())
67    }
68
69    /// Extracts the nodes of a given region.
70    fn get_region_nodes(
71        analysis: &StructureAnalysis,
72        region_id: RegionId,
73    ) -> Result<Vec<AstKind>, StructureAnalysisError> {
74        let region = analysis.regions.get(region_id.index).ok_or(
75            StructureAnalysisError::RegionNotFound {
76                region_id,
77                backtrace: Backtrace::capture(),
78            },
79        )?;
80        Ok(region.get_nodes().to_vec())
81    }
82
83    /// Extracts the unresolved nodes of a given region.
84    fn get_unresolved_nodes(
85        analysis: &StructureAnalysis,
86        region_id: RegionId,
87    ) -> Result<Vec<AstKind>, StructureAnalysisError> {
88        let region = analysis.regions.get(region_id.index).ok_or(
89            StructureAnalysisError::RegionNotFound {
90                region_id,
91                backtrace: Backtrace::capture(),
92            },
93        )?;
94        Ok(region.get_unresolved_nodes().to_vec())
95    }
96
97    /// Add region comments to P<ControlFlowNode>
98    fn add_region_comments(
99        analysis: &StructureAnalysis,
100        node: &mut P<ControlFlowNode>,
101        region_id: RegionId,
102    ) {
103        node.metadata_mut().add_comment(region_id.to_string());
104
105        let unresolved = IfRegionReducer::get_unresolved_nodes(analysis, region_id).unwrap();
106        if !unresolved.is_empty() {
107            node.metadata_mut()
108                .add_comment("Unresolved nodes:".to_string());
109        }
110        for (idx, n) in unresolved.iter().enumerate() {
111            node.metadata_mut()
112                .add_comment(format!("idx={}: {}", idx, emit(n.clone())));
113        }
114    }
115}
116
117impl RegionReducer for IfRegionReducer {
118    fn reduce_region(
119        &mut self,
120        analysis: &mut StructureAnalysis,
121        region_id: RegionId,
122    ) -> Result<bool, StructureAnalysisError> {
123        // Step 1: Extract the jump expression
124        let jump_expr = Self::extract_jump_expr(analysis, region_id)?;
125
126        // Step 2: Get successors and classify them
127        let successors = analysis.get_successors(region_id)?;
128        if successors.len() != 2 {
129            return Err(StructureAnalysisError::Other {
130                message: format!(
131                    "Control flow region must have exactly two successors, found {}",
132                    successors.len()
133                ),
134                backtrace: Backtrace::capture(),
135            });
136        }
137
138        let branch_region_id = successors
139            .iter()
140            .find(|(_, edge_type)| *edge_type == ControlFlowEdgeType::Branch)
141            .map(|(id, _)| *id)
142            .ok_or(StructureAnalysisError::Other {
143                message: "Control flow region must have a branch successor".to_string(),
144                backtrace: Backtrace::capture(),
145            })?;
146
147        let fallthrough_region_id = successors
148            .iter()
149            .find(|(_, edge_type)| *edge_type == ControlFlowEdgeType::Fallthrough)
150            .map(|(id, _)| *id)
151            .ok_or(StructureAnalysisError::Other {
152                message: "Control flow region must have a fallthrough successor".to_string(),
153                backtrace: Backtrace::capture(),
154            })?;
155
156        // Step 3: Determine linear successors
157        let branch_linear_successor = analysis.get_single_linear_successor(branch_region_id)?;
158        let fallthrough_linear_successor =
159            analysis.get_single_linear_successor(fallthrough_region_id)?;
160        // Step 4: Handle the different cases
161        if let Some(successor) = branch_linear_successor {
162            if successor == fallthrough_region_id {
163                // Ensure that only the region is a predecessor of the branch region
164                if !analysis.has_single_predecessor(branch_region_id)? {
165                    return Ok(false);
166                }
167
168                // Call the before_reduce hook
169                analysis.before_reduce(region_id);
170
171                // Branch linear successor aligns with fallthrough region
172                let branch_statements =
173                    IfRegionReducer::get_region_nodes(analysis, branch_region_id)?;
174                let mut cond: 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                IfRegionReducer::add_region_comments(analysis, &mut cond, region_id);
186                IfRegionReducer::add_region_comments(analysis, &mut cond, branch_region_id);
187
188                Self::merge_conditional(analysis, region_id, vec![cond])?;
189                Self::cleanup_region(analysis, branch_region_id, region_id, successor)?;
190                return Ok(true);
191            }
192        }
193
194        if let Some(successor) = fallthrough_linear_successor {
195            if successor == branch_region_id {
196                // Ensure that only the region is a predecessor of the fallthrough region
197                if !analysis.has_single_predecessor(fallthrough_region_id)? {
198                    return Ok(false);
199                }
200                // Call the before_reduce hook
201                analysis.before_reduce(region_id);
202
203                // Fallthrough linear successor aligns with branch region
204                let fallthrough_statements =
205                    IfRegionReducer::get_region_nodes(analysis, fallthrough_region_id)?;
206                let mut cond: P<ControlFlowNode> = new_acylic_condition(
207                    jump_expr,
208                    fallthrough_statements,
209                    analysis.get_branch_opcode(region_id)?,
210                )
211                .map_err(|e| StructureAnalysisError::AstNodeError {
212                    source: Box::new(e),
213                    backtrace: Backtrace::capture(),
214                })?
215                .into();
216
217                IfRegionReducer::add_region_comments(analysis, &mut cond, region_id);
218                IfRegionReducer::add_region_comments(analysis, &mut cond, fallthrough_region_id);
219
220                Self::merge_conditional(analysis, region_id, vec![cond])?;
221                Self::cleanup_region(analysis, fallthrough_region_id, region_id, successor)?;
222                return Ok(true);
223            }
224        }
225
226        if let (Some(branch_successor), Some(fallthrough_successor)) =
227            (branch_linear_successor, fallthrough_linear_successor)
228        {
229            // Create if / else statement
230            if branch_successor == fallthrough_successor {
231                // Ensure that only the region is a predecessor of the branch and fallthrough regions
232                if !analysis.has_single_predecessor(branch_region_id)?
233                    || !analysis.has_single_predecessor(fallthrough_region_id)?
234                {
235                    return Ok(false);
236                }
237                // Call the before_reduce hook
238                analysis.before_reduce(region_id);
239
240                // Both linear successors are the same
241                let branch_statements =
242                    IfRegionReducer::get_region_nodes(analysis, branch_region_id)?;
243                let fallthrough_statements =
244                    IfRegionReducer::get_region_nodes(analysis, fallthrough_region_id)?;
245                let mut if_stmnt: P<ControlFlowNode> =
246                    new_if(jump_expr, fallthrough_statements).into();
247                let mut else_stmt: P<ControlFlowNode> = new_else(branch_statements).into();
248
249                IfRegionReducer::add_region_comments(analysis, &mut if_stmnt, region_id);
250                IfRegionReducer::add_region_comments(
251                    analysis,
252                    &mut if_stmnt,
253                    fallthrough_region_id,
254                );
255                IfRegionReducer::add_region_comments(analysis, &mut else_stmt, branch_region_id);
256
257                Self::merge_conditional(analysis, region_id, vec![if_stmnt, else_stmt])?;
258                Self::cleanup_region(analysis, branch_region_id, region_id, branch_successor)?;
259                Self::cleanup_region(
260                    analysis,
261                    fallthrough_region_id,
262                    region_id,
263                    fallthrough_successor,
264                )?;
265
266                // Finally, add the edge between the original region and the common successor
267                analysis.connect_regions(
268                    region_id,
269                    branch_successor,
270                    ControlFlowEdgeType::Branch,
271                )?;
272                return Ok(true);
273            }
274        }
275
276        Ok(false)
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use crate::decompiler::ast::{new_assignment, new_id};
283
284    use super::*;
285
286    #[test]
287    fn test_if_reduce() -> Result<(), StructureAnalysisError> {
288        let mut structure_analysis = StructureAnalysis::new(false, 100);
289
290        let entry_region = structure_analysis.add_region(RegionType::ControlFlow);
291        let region_1 = structure_analysis.add_region(RegionType::Linear);
292        let region_2 = structure_analysis.add_region(RegionType::Tail);
293
294        // push nodes to the regions
295        structure_analysis
296            .push_to_region(entry_region, new_assignment(new_id("foo"), new_id("bar")));
297        // set condition for the region
298        structure_analysis
299            .get_region_mut(entry_region)?
300            .set_jump_expr(Some(new_id("foo").into()));
301        structure_analysis.push_to_region(region_1, new_assignment(new_id("foo2"), new_id("bar2")));
302        structure_analysis.push_to_region(region_1, new_assignment(new_id("foo3"), new_id("bar3")));
303        structure_analysis.push_to_region(region_2, new_assignment(new_id("foo4"), new_id("bar4")));
304        structure_analysis.push_to_region(region_2, new_assignment(new_id("foo5"), new_id("bar5")));
305        structure_analysis.connect_regions(
306            entry_region,
307            region_1,
308            ControlFlowEdgeType::Fallthrough,
309        )?;
310        structure_analysis.connect_regions(entry_region, region_2, ControlFlowEdgeType::Branch)?;
311        structure_analysis.connect_regions(region_1, region_2, ControlFlowEdgeType::Fallthrough)?;
312        structure_analysis.execute()?;
313
314        assert_eq!(structure_analysis.region_graph.node_count(), 1);
315
316        let region = structure_analysis.get_entry_region();
317        let region = structure_analysis.get_region(region)?;
318        assert_eq!(region.get_nodes().len(), 4);
319
320        // ensure that the final region is a tail region
321        assert_eq!(region.get_region_type(), RegionType::Tail);
322
323        Ok(())
324    }
325
326    // TODO: Bring back test case once this is working again
327    // #[test]
328    // fn test_if_reduce_single_condition_two_ret() -> Result<(), StructureAnalysisError> {
329    //     let mut structure_analysis = StructureAnalysis::new();
330
331    //     let entry_region = structure_analysis.add_region(RegionType::ControlFlow);
332    //     let region_1 = structure_analysis.add_region(RegionType::Tail);
333    //     let region_2 = structure_analysis.add_region(RegionType::Tail);
334
335    //     // push nodes to the regions
336    //     structure_analysis
337    //         .push_to_region(entry_region, new_assignment(new_id("foo"), new_id("bar")));
338    //     // set condition for the region
339    //     structure_analysis
340    //         .get_region_mut(entry_region)?
341    //         .set_jump_expr(Some(new_id("foo").into()));
342    //     structure_analysis.push_to_region(region_1, new_assignment(new_id("foo2"), new_id("bar2")));
343    //     structure_analysis.push_to_region(region_2, new_assignment(new_id("foo3"), new_id("bar3")));
344    //     structure_analysis.connect_regions(entry_region, region_1, ControlFlowEdgeType::Branch)?;
345    //     structure_analysis.connect_regions(
346    //         entry_region,
347    //         region_2,
348    //         ControlFlowEdgeType::Fallthrough,
349    //     )?;
350    //     structure_analysis.execute()?;
351    //     assert_eq!(structure_analysis.region_graph.node_count(), 1);
352    //     Ok(())
353    // }
354
355    #[test]
356    fn test_if_else_case() -> Result<(), StructureAnalysisError> {
357        let mut structure_analysis = StructureAnalysis::new(false, 100);
358
359        let entry_region = structure_analysis.add_region(RegionType::ControlFlow);
360        let region_1 = structure_analysis.add_region(RegionType::Linear);
361        let region_2 = structure_analysis.add_region(RegionType::Linear);
362        let region_3 = structure_analysis.add_region(RegionType::Tail);
363
364        // push nodes to the regions
365        structure_analysis
366            .push_to_region(entry_region, new_assignment(new_id("foo"), new_id("bar")));
367        // set condition for the region
368        structure_analysis
369            .get_region_mut(entry_region)?
370            .set_jump_expr(Some(new_id("foo").into()));
371
372        structure_analysis.push_to_region(region_1, new_assignment(new_id("foo2"), new_id("bar2")));
373        structure_analysis.push_to_region(region_1, new_assignment(new_id("foo3"), new_id("bar3")));
374        structure_analysis.push_to_region(region_2, new_assignment(new_id("foo4"), new_id("bar4")));
375        structure_analysis.push_to_region(region_2, new_assignment(new_id("foo5"), new_id("bar5")));
376        structure_analysis.push_to_region(region_3, new_assignment(new_id("foo6"), new_id("bar6")));
377
378        structure_analysis.connect_regions(entry_region, region_1, ControlFlowEdgeType::Branch)?;
379        structure_analysis.connect_regions(
380            entry_region,
381            region_2,
382            ControlFlowEdgeType::Fallthrough,
383        )?;
384        structure_analysis.connect_regions(region_1, region_3, ControlFlowEdgeType::Branch)?;
385        structure_analysis.connect_regions(region_2, region_3, ControlFlowEdgeType::Branch)?;
386        structure_analysis.execute()?;
387        assert_eq!(structure_analysis.region_graph.node_count(), 1);
388
389        Ok(())
390    }
391}