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
15pub struct IfRegionReducer;
17
18impl IfRegionReducer {
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 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 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 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 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 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 let jump_expr = Self::extract_jump_expr(analysis, region_id)?;
125
126 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 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 if let Some(successor) = branch_linear_successor {
162 if successor == fallthrough_region_id {
163 if !analysis.has_single_predecessor(branch_region_id)? {
165 return Ok(false);
166 }
167
168 analysis.before_reduce(region_id);
170
171 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 if !analysis.has_single_predecessor(fallthrough_region_id)? {
198 return Ok(false);
199 }
200 analysis.before_reduce(region_id);
202
203 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 if branch_successor == fallthrough_successor {
231 if !analysis.has_single_predecessor(branch_region_id)?
233 || !analysis.has_single_predecessor(fallthrough_region_id)?
234 {
235 return Ok(false);
236 }
237 analysis.before_reduce(region_id);
239
240 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 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 structure_analysis
296 .push_to_region(entry_region, new_assignment(new_id("foo"), new_id("bar")));
297 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 assert_eq!(region.get_region_type(), RegionType::Tail);
322
323 Ok(())
324 }
325
326 #[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 structure_analysis
366 .push_to_region(entry_region, new_assignment(new_id("foo"), new_id("bar")));
367 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}