1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use std::collections::HashSet;
use super::super::super::HighLevelEmitter;
use super::scan::next_if_line;
impl HighLevelEmitter {
/// Converts `label_X: <setup> if COND { <body> goto label_X; <phi> }` into
/// `<setup> while COND { <body> <phi> <setup> }` — recovering while-loop
/// semantics from backward unconditional JMPs inside if-blocks.
pub(crate) fn rewrite_if_goto_to_while(statements: &mut Vec<String>) {
// The pass can only succeed when a matching `goto label_X;` exists
// (checked at lines below inside the if-block). Pre-collect those once
// so labels with no corresponding goto are skipped in O(1) instead of
// each running an unbounded forward `next_if_line` scan to the tail of
// the vector. Without this guard the pass is O(labels x N): a crafted
// in-cap NEF that emits many `label_X:` lines with no following `if`
// drives it quadratic — a decompiler-hang DoS.
let goto_targets: HashSet<String> = statements
.iter()
.map(|s| s.trim())
.filter(|t| t.starts_with("goto label_") && t.ends_with(';'))
.map(str::to_string)
.collect();
if goto_targets.is_empty() {
return;
}
let mut index = 0;
while index < statements.len() {
let trimmed = statements[index].trim().to_string();
// Match: label_0xXXXX:
let Some(label) = trimmed.strip_suffix(':') else {
index += 1;
continue;
};
if !label.starts_with("label_") {
index += 1;
continue;
}
// Fast-skip: with no `goto label_X;` anywhere, the search below can
// only fail, so skip the unbounded `next_if_line` scan entirely.
if !goto_targets.contains(format!("goto {label};").as_str()) {
index += 1;
continue;
}
// Find next `if ... {` after the label
let Some(if_idx) = next_if_line(statements, index) else {
index += 1;
continue;
};
// Find the matching `}`
let Some(end_idx) = Self::find_block_end(statements, if_idx) else {
index += 1;
continue;
};
if statements[end_idx].trim() != "}" {
index += 1;
continue;
}
// Find `goto label_X;` inside the if-block
let goto_target = format!("goto {label};");
let Some(goto_idx) =
(if_idx + 1..end_idx).find(|&i| statements[i].trim() == goto_target)
else {
index += 1;
continue;
};
// Collect setup lines (non-empty, non-comment) between label and if
let setup_lines: Vec<String> = (index + 1..if_idx)
.filter(|&i| {
let t = statements[i].trim();
!t.is_empty() && !t.starts_with("//")
})
.map(|i| statements[i].clone())
.collect();
// Transform: remove label, change if->while, remove goto,
// append setup copies at end of loop body
statements[index].clear(); // remove label
let if_line = statements[if_idx].trim().to_string();
statements[if_idx] = if_line.replacen("if ", "while ", 1);
statements[goto_idx].clear(); // remove goto
// Insert setup copies before closing `}`
if !setup_lines.is_empty() {
for (j, line) in setup_lines.into_iter().enumerate() {
statements.insert(end_idx + j, line);
}
}
index += 1;
}
}
}