neo_decompiler/decompiler/decompilation.rs
1use crate::instruction::Instruction;
2use crate::manifest::ContractManifest;
3use crate::nef::NefFile;
4
5use super::analysis::call_graph::CallGraph;
6use super::analysis::types::TypeInfo;
7use super::analysis::xrefs::Xrefs;
8use super::cfg::ssa::{SsaBuilder, SsaForm};
9use super::cfg::Cfg;
10
11/// Result of a successful decompilation run.
12#[derive(Debug, Clone)]
13#[non_exhaustive]
14pub struct Decompilation {
15 /// Parsed NEF container.
16 pub nef: NefFile,
17 /// Optional parsed contract manifest.
18 pub manifest: Option<ContractManifest>,
19 /// Non-fatal warnings emitted during disassembly or rendering.
20 pub warnings: Vec<String>,
21 /// Disassembled instruction stream from the NEF script.
22 pub instructions: Vec<Instruction>,
23 /// Control flow graph built from the instruction stream.
24 pub cfg: Cfg,
25 /// Best-effort call graph extracted from the instruction stream.
26 pub call_graph: CallGraph,
27 /// Best-effort cross-reference information for locals/args/statics.
28 pub xrefs: Xrefs,
29 /// Best-effort primitive/collection type inference.
30 pub types: TypeInfo,
31 /// Optional rendered pseudocode output.
32 pub pseudocode: Option<String>,
33 /// Optional rendered high-level output.
34 pub high_level: Option<String>,
35 /// Optional rendered C# output.
36 pub csharp: Option<String>,
37 /// SSA form of the control flow graph (computed lazily).
38 pub ssa: Option<SsaForm>,
39}
40
41impl Decompilation {
42 /// Get the control flow graph as DOT format for visualization.
43 ///
44 /// The DOT output can be rendered using Graphviz or similar tools.
45 /// The graph carries a `label` attribute combining the contract
46 /// name (when a manifest is provided), the script hash, and the
47 /// instruction count, so a multi-CFG dump stays self-identifying.
48 ///
49 /// # Example
50 /// ```ignore
51 /// let decompilation = decompiler.decompile_bytes(&nef_bytes)?;
52 /// let dot = decompilation.cfg_to_dot();
53 /// std::fs::write("cfg.dot", dot)?;
54 /// // Then run: dot -Tpng cfg.dot -o cfg.png
55 /// ```
56 #[must_use]
57 pub fn cfg_to_dot(&self) -> String {
58 let title = self.cfg_dot_title();
59 let mut dot = self.cfg.to_dot();
60 // Splice the graph-level label between the `digraph CFG {`
61 // header and the existing `node [shape=box];` line. Keeping
62 // this layered above `cfg::to_dot` (rather than threading a
63 // title argument through) means the lower-level graph
64 // emitter remains agnostic of contract identity.
65 if let Some(rest) = dot.strip_prefix("digraph CFG {\n") {
66 let mut header = String::from("digraph CFG {\n");
67 header.push_str(&format!(" label=\"{title}\";\n"));
68 header.push_str(" labelloc=\"t\";\n");
69 header.push_str(rest);
70 dot = header;
71 }
72 dot
73 }
74
75 fn cfg_dot_title(&self) -> String {
76 let script_hash = crate::util::format_hash(&self.nef.script_hash());
77 let name = self
78 .manifest
79 .as_ref()
80 .map(|m| m.name.trim())
81 .filter(|n| !n.is_empty());
82 let count = self.instructions.len();
83 match name {
84 Some(name) => format!("{name} ({script_hash}, {count} instr)"),
85 None => format!("{script_hash} ({count} instr)"),
86 }
87 }
88
89 /// Get the cached SSA form for this decompilation, if available.
90 ///
91 /// Call [`Self::compute_ssa`] first to populate the cached SSA value.
92 ///
93 /// # Returns
94 ///
95 /// `Option<&SsaForm>` - The SSA form, or `None` if CFG has no blocks.
96 ///
97 /// # Examples
98 ///
99 /// ```ignore
100 /// let mut decompilation = decompiler.decompile_bytes(&nef_bytes)?;
101 /// decompilation.compute_ssa();
102 /// if let Some(ssa) = decompilation.ssa() {
103 /// println!("SSA Stats: {}", ssa.stats());
104 /// println!("{}", ssa.render());
105 /// }
106 /// ```
107 #[must_use]
108 pub fn ssa(&self) -> Option<&SsaForm> {
109 self.ssa.as_ref()
110 }
111
112 /// Compute SSA form if not already computed.
113 ///
114 /// This is a convenience method for computing SSA form lazily.
115 /// After calling this, `ssa()` will return `Some(...)`.
116 pub fn compute_ssa(&mut self) {
117 if self.ssa.is_none() && self.cfg.block_count() > 0 {
118 // Build the structural SSA skeleton (dominance info + versioned PUSH
119 // assignments) from the instructions and CFG. See `SsaBuilder`.
120 let builder = SsaBuilder::new(&self.cfg, &self.instructions);
121 self.ssa = Some(builder.build());
122 }
123 }
124
125 /// Get SSA statistics if SSA form is available.
126 ///
127 /// # Returns
128 ///
129 /// `Option<String>` - Formatted statistics string, or `None` if SSA not computed.
130 #[must_use]
131 pub fn ssa_stats(&self) -> Option<String> {
132 self.ssa.as_ref().map(|ssa| format!("{}", ssa.stats()))
133 }
134
135 /// Render SSA form if available.
136 ///
137 /// # Returns
138 ///
139 /// `Option<String>` - Rendered SSA code, or `None` if SSA not computed.
140 #[must_use]
141 pub fn render_ssa(&self) -> Option<String> {
142 self.ssa.as_ref().map(SsaForm::render)
143 }
144}