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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
use crate::instruction::Instruction;
use crate::manifest::ContractManifest;
use crate::nef::NefFile;
use super::analysis::call_graph::CallGraph;
use super::analysis::types::TypeInfo;
use super::analysis::xrefs::Xrefs;
use super::cfg::ssa::{SsaBuilder, SsaForm};
use super::cfg::Cfg;
/// Result of a successful decompilation run.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Decompilation {
/// Parsed NEF container.
pub nef: NefFile,
/// Optional parsed contract manifest.
pub manifest: Option<ContractManifest>,
/// Non-fatal warnings emitted during disassembly or rendering.
pub warnings: Vec<String>,
/// Disassembled instruction stream from the NEF script.
pub instructions: Vec<Instruction>,
/// Control flow graph built from the instruction stream.
pub cfg: Cfg,
/// Best-effort call graph extracted from the instruction stream.
pub call_graph: CallGraph,
/// Best-effort cross-reference information for locals/args/statics.
pub xrefs: Xrefs,
/// Best-effort primitive/collection type inference.
pub types: TypeInfo,
/// Optional rendered pseudocode output.
pub pseudocode: Option<String>,
/// Optional rendered high-level output.
pub high_level: Option<String>,
/// Optional rendered C# output.
pub csharp: Option<String>,
/// SSA form of the control flow graph (computed lazily).
pub(crate) ssa: Option<SsaForm>,
}
impl Decompilation {
/// Get the control flow graph as DOT format for visualization.
///
/// The DOT output can be rendered using Graphviz or similar tools.
/// The graph carries a `label` attribute combining the contract
/// name (when a manifest is provided), the script hash, and the
/// instruction count, so a multi-CFG dump stays self-identifying.
///
/// # Example
/// ```ignore
/// let decompilation = decompiler.decompile_bytes(&nef_bytes)?;
/// let dot = decompilation.cfg_to_dot();
/// std::fs::write("cfg.dot", dot)?;
/// // Then run: dot -Tpng cfg.dot -o cfg.png
/// ```
#[must_use]
pub fn cfg_to_dot(&self) -> String {
let title = self.cfg_dot_title();
let mut dot = self.cfg.to_dot();
// Splice the graph-level label between the `digraph CFG {`
// header and the existing `node [shape=box];` line. Keeping
// this layered above `cfg::to_dot` (rather than threading a
// title argument through) means the lower-level graph
// emitter remains agnostic of contract identity.
if let Some(rest) = dot.strip_prefix("digraph CFG {\n") {
let mut header = String::from("digraph CFG {\n");
header.push_str(&format!(" label=\"{title}\";\n"));
header.push_str(" labelloc=\"t\";\n");
header.push_str(rest);
dot = header;
}
dot
}
fn cfg_dot_title(&self) -> String {
let script_hash = crate::util::format_hash(&self.nef.script_hash());
let name = self
.manifest
.as_ref()
.map(|m| m.name.trim())
.filter(|n| !n.is_empty());
let count = self.instructions.len();
match name {
Some(name) => format!("{name} ({script_hash}, {count} instr)"),
None => format!("{script_hash} ({count} instr)"),
}
}
/// Get the cached SSA form for this decompilation, if available.
///
/// Call [`Self::compute_ssa`] first to populate the cached SSA value.
///
/// # Returns
///
/// `Option<&SsaForm>` - The SSA form, or `None` if CFG has no blocks.
///
/// # Examples
///
/// ```ignore
/// let mut decompilation = decompiler.decompile_bytes(&nef_bytes)?;
/// decompilation.compute_ssa();
/// if let Some(ssa) = decompilation.ssa() {
/// println!("SSA Stats: {}", ssa.stats());
/// println!("{}", ssa.render());
/// }
/// ```
#[must_use]
pub fn ssa(&self) -> Option<&SsaForm> {
self.ssa.as_ref()
}
/// Compute SSA form if not already computed.
///
/// Builds a real stack-effect SSA (def/use chains + φ nodes at control-flow
/// joins) from the instructions and CFG, with precomputed dominance
/// information. See [`SsaBuilder`](crate::decompiler::cfg::ssa::SsaBuilder).
pub fn compute_ssa(&mut self) {
if self.ssa.is_none() && self.cfg.block_count() > 0 {
let builder = SsaBuilder::new(&self.cfg, &self.instructions);
self.ssa = Some(builder.build());
}
}
/// Compute (if needed) and optimize the SSA form in place.
///
/// Runs constant folding/propagation, copy propagation, trivial-φ
/// elimination, and dead-code elimination to a fixed point. Returns the
/// number of optimization rounds applied (`0` if the form was already
/// optimal). Computes the SSA first if it has not been built yet.
pub fn optimize_ssa(&mut self) -> usize {
self.compute_ssa();
match &mut self.ssa {
Some(ssa) => crate::decompiler::cfg::ssa::optimize_ssa(ssa),
None => 0,
}
}
/// Render the optimized SSA form as readable pseudo-code.
///
/// Computes and optimizes the SSA (if not already done), then lowers it to
/// the typed IR and renders it. This surfaces the Phase 2/3 data-flow work
/// (φ resolution, constant folding, copy propagation, DCE) as analysis-facing
/// text — the scaffolding for the full IR-spine renderer.
#[must_use]
pub fn render_optimized_ssa(&mut self) -> String {
self.optimize_ssa();
match &self.ssa {
Some(ssa) => crate::decompiler::cfg::ssa::render_ssa_form(ssa),
None => String::new(),
}
}
/// Render a structured IR view by recovering control flow from the CFG.
///
/// Computes + optimizes the SSA, then runs the CFG structurer
/// (`cfg::structure`) to recover `if` / `if-else` constructs as typed
/// `ir::ControlFlow` nodes — the structural alternative to the legacy
/// string-pattern postprocess. Straight-line bodies carry the optimized
/// SSA data-flow. This is the Phase-4 IR-spine path (additive; the legacy
/// high-level view remains the default).
#[must_use]
pub fn render_structured_ir(&mut self) -> String {
// Per-method + envelope render. Fall back to the single-CFG render if
// extraction fails or yields no methods, so the view never regresses.
let table = crate::decompiler::analysis::MethodTable::new(
&self.instructions,
self.manifest.as_ref(),
);
let views = crate::decompiler::cfg::method_view::extract_method_cfgs(
&self.cfg,
&table,
&self.instructions,
);
if !views.is_empty() {
return crate::decompiler::cfg::method_view::render_envelope(
&self.nef,
self.manifest.as_ref(),
&views,
);
}
self.render_structured_ir_single_cfg()
}
/// Fallback: render the whole-script CFG as a single structured block.
/// Used when per-method extraction yields no methods.
fn render_structured_ir_single_cfg(&mut self) -> String {
self.optimize_ssa();
match &self.ssa {
Some(ssa) => {
let block = crate::decompiler::cfg::structure_cfg(ssa);
crate::decompiler::ir::render_block(&block, 0)
}
None => String::new(),
}
}
/// Get SSA statistics if SSA form is available.
///
/// # Returns
///
/// `Option<String>` - Formatted statistics string, or `None` if SSA not computed.
#[must_use]
pub fn ssa_stats(&self) -> Option<String> {
self.ssa.as_ref().map(|ssa| format!("{}", ssa.stats()))
}
/// Render SSA form if available.
///
/// # Returns
///
/// `Option<String>` - Rendered SSA code, or `None` if SSA not computed.
#[must_use]
pub fn render_ssa(&self) -> Option<String> {
self.ssa.as_ref().map(SsaForm::render)
}
}