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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use crate::instruction::Instruction;
use super::super::super::HighLevelEmitter;
impl HighLevelEmitter {
pub(in super::super::super) fn emit_try_block(&mut self, instruction: &Instruction) {
self.push_comment(instruction);
let Some((body_start, catch_target, finally_target)) =
self.try_handler_targets(instruction)
else {
self.warn(instruction, "try with unsupported operand (skipping)");
return;
};
self.statements.push("try {".into());
let mut handlers = Vec::new();
if let Some(catch) = catch_target {
handlers.push(catch);
}
if let Some(finally) = finally_target {
handlers.push(finally);
}
handlers.sort_unstable();
let try_end = handlers.first().copied();
if let Some(end) = try_end {
let closer_entry = self.pending_closers.entry(end).or_insert(0);
*closer_entry += 1;
}
let mut resume_target = None;
if let Some(end) = try_end {
if let Some((endtry_offset, target)) = self.find_endtry_target(body_start, end) {
self.skip_jumps.insert(endtry_offset);
resume_target = Some(target);
}
}
if let Some(catch) = catch_target {
let catch_entry = self.catch_targets.entry(catch).or_insert(0);
*catch_entry += 1;
// Record where execution resumes after this try-catch so the
// stack state from the try block's normal exit can be saved
// before the catch handler clears it.
if let Some(rt) = resume_target {
self.try_catch_resume.insert(catch, rt);
}
let mut catch_end = finally_target.or(resume_target);
// Search the catch body for its ENDTRY. When catch_end is already
// known (from a finally target or the try-body's ENDTRY), use it as
// the search bound. Otherwise the try body always terminates
// (throw/abort) so there was no ENDTRY — search forward from the
// catch target to find the catch body's own ENDTRY.
let search_bound = catch_end
.unwrap_or_else(|| self.program.last().map(|i| i.offset + 1).unwrap_or(catch));
if let Some((endtry_offset, target)) = self.find_endtry_target(catch, search_bound) {
self.skip_jumps.insert(endtry_offset);
resume_target.get_or_insert(target);
catch_end.get_or_insert(target);
}
// Fallback: when the catch body has no ENDTRY (e.g. it contains
// an inner try-finally that ends with ENDFINALLY and the catch
// itself terminates via throw/abort), use the ENDFINALLY position
// to infer where the catch body ends.
if catch_end.is_none() {
if let Some((_, endfinally_end)) = self.find_endfinally_end(catch) {
catch_end = Some(endfinally_end);
}
}
if let Some(end) = catch_end {
let closer_entry = self.pending_closers.entry(end).or_insert(0);
*closer_entry += 1;
}
}
if let Some(finally) = finally_target {
// Suppress duplicate `finally {` when this finally target falls
// inside an already-registered finally body. The Neo compiler
// sometimes points an outer TRY's finally into the middle of an
// inner TRY's finally body (code sharing / overlapping regions).
let already_covered = self
.finally_body_ranges
.iter()
.any(|&(start, end)| finally > start && finally < end);
if already_covered {
// The code at `finally` is already inside another finally
// block — do not emit a redundant `finally { }` wrapper.
// We still need to remove the closer that was registered for
// the try body at this offset (the `}` before `finally {`),
// since there is no `finally {` to pair with.
//
// Actually, the try body closer at `try_end` (= min handler)
// is correct — it closes the try body `}`. The catch closer
// at `finally` offset closes the catch body `}`. Both are
// needed. We just skip emitting the finally header + closer.
} else {
let finally_entry = self.finally_targets.entry(finally).or_insert(0);
*finally_entry += 1;
let mut endfinally_end = None;
if let Some((endfinally_offset, end)) = self.find_endfinally_end(finally) {
self.skip_jumps.insert(endfinally_offset);
endfinally_end = Some(end);
// Record this finally body range so nested TRY blocks
// whose finally targets overlap can be detected.
self.finally_body_ranges.push((finally, end));
}
// The finally closer belongs at the instruction after ENDFINALLY,
// not at the try/catch resume point. In nested try blocks these
// can differ (resume_target may precede the finally block).
//
// When neither ENDFINALLY nor a resume target exists, the finally
// block terminates unconditionally (RET/THROW/ABORT). Register
// the closer past the last instruction so `finish()` flushes it.
let finally_end = endfinally_end
.or(resume_target.filter(|&rt| rt > finally))
.or_else(|| self.program.last().map(|i| i.offset + 1));
if let Some(end) = finally_end {
// Record the finally body range even when ENDFINALLY is
// absent (the compiler omits it when all paths inside the
// finally block terminate unconditionally via abort/throw).
// This lets detect_implicit_else avoid wrapping sequential
// code inside finally blocks in spurious else branches.
if endfinally_end.is_none() {
self.finally_body_ranges.push((finally, end));
}
let closer_entry = self.pending_closers.entry(end).or_insert(0);
*closer_entry += 1;
}
}
}
}
}