gbf_core/decompiler/structure_analysis/
tail_region_reducer.rs1#![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
15pub struct TailRegionReducer;
17
18impl TailRegionReducer {
19 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 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 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 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 if analysis.get_region_type(region_id)? != RegionType::ControlFlow {
95 return Ok(false);
96 }
97
98 let jump_expr = Self::extract_jump_expr(analysis, region_id)?;
100
101 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 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 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 if analysis.get_region_type(branch_region_id)? == RegionType::Tail && branch_single_pred {
167 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 if analysis.get_region_type(fallthrough_region_id)? == RegionType::Tail
196 && fallthrough_single_pred
197 {
198 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 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 assert_eq!(region.get_region_type(), RegionType::Tail);
269
270 Ok(())
271 }
272}