neo_decompiler/decompiler/pipeline.rs
1use std::collections::HashSet;
2
3use crate::disassembler::{Disassembler, DisassemblyOutput, UnknownHandling};
4use crate::error::Result;
5use crate::manifest::ContractManifest;
6use crate::nef::NefParser;
7
8use super::cfg::CfgBuilder;
9use super::decompilation::Decompilation;
10use super::output_format::OutputFormat;
11use super::{analysis, csharp, high_level, pseudocode};
12
13mod io;
14
15/// Main entry point used by the CLI and tests.
16#[derive(Debug, Default)]
17pub struct Decompiler {
18 parser: NefParser,
19 disassembler: Disassembler,
20 inline_single_use_temps: bool,
21 emit_trace_comments: bool,
22}
23
24impl Decompiler {
25 /// Create a new decompiler that permits unknown opcodes during disassembly.
26 ///
27 /// This is equivalent to `Decompiler::with_unknown_handling(UnknownHandling::Permit)`.
28 #[must_use]
29 pub fn new() -> Self {
30 Self::with_unknown_handling(UnknownHandling::Permit)
31 }
32
33 /// Create a new decompiler configured with the desired unknown-opcode policy.
34 ///
35 /// Unknown opcodes can appear when disassembling corrupted inputs or when
36 /// targeting a newer VM revision. Use [`crate::UnknownHandling::Error`] to
37 /// fail fast, or [`crate::UnknownHandling::Permit`] to emit `Unknown`
38 /// instructions and continue.
39 ///
40 /// # Examples
41 /// ```
42 /// use neo_decompiler::{Decompiler, UnknownHandling};
43 ///
44 /// let decompiler = Decompiler::with_unknown_handling(UnknownHandling::Error);
45 /// let _ = decompiler;
46 /// ```
47 #[must_use]
48 pub fn with_unknown_handling(handling: UnknownHandling) -> Self {
49 Self {
50 parser: NefParser::new(),
51 disassembler: Disassembler::with_unknown_handling(handling),
52 inline_single_use_temps: false,
53 // Emit `// XXXX: OPCODE` trace comments next to each lifted
54 // statement. Defaults to true to preserve existing rendering
55 // (golden tests assert their presence). Disable via
56 // `with_trace_comments(false)` for clean human-readable output.
57 emit_trace_comments: true,
58 }
59 }
60
61 /// Enable experimental inlining of single-use temporary variables in the high-level view.
62 ///
63 /// This can reduce noise in lifted code by replacing temps like `t0` with their RHS
64 /// at the first use site, but it may reduce readability for larger expressions.
65 #[must_use]
66 pub fn with_inline_single_use_temps(mut self, enabled: bool) -> Self {
67 self.inline_single_use_temps = enabled;
68 self
69 }
70
71 /// Toggle inline `// XXXX: OPCODE` trace comments in the high-level view.
72 ///
73 /// Defaults to `true` (preserved historical behaviour). Set to `false` for
74 /// clean output suitable for end users — the lifted statements alone, with
75 /// no per-instruction trace markers. Untranslated instructions still emit
76 /// their `// not yet translated` notes regardless of this setting.
77 #[must_use]
78 pub fn with_trace_comments(mut self, enabled: bool) -> Self {
79 self.emit_trace_comments = enabled;
80 self
81 }
82
83 /// Decompile a NEF blob already loaded in memory.
84 ///
85 /// # Errors
86 ///
87 /// Returns an error if the NEF container is malformed or disassembly fails.
88 pub fn decompile_bytes(&self, bytes: &[u8]) -> Result<Decompilation> {
89 self.decompile_bytes_with_manifest(bytes, None, OutputFormat::All)
90 }
91
92 /// Disassemble a NEF blob already loaded in memory.
93 ///
94 /// This fast path parses the NEF container and decodes instructions only;
95 /// it skips CFG construction, analysis passes, and renderers.
96 ///
97 /// # Errors
98 ///
99 /// Returns an error if the NEF container is malformed or disassembly fails.
100 pub fn disassemble_bytes(&self, bytes: &[u8]) -> Result<DisassemblyOutput> {
101 let nef = self.parser.parse(bytes)?;
102 self.disassembler.disassemble_with_warnings(&nef.script)
103 }
104
105 /// Decompile a NEF blob using an optional manifest.
106 ///
107 /// # Errors
108 ///
109 /// Returns an error if the NEF container is malformed or disassembly fails.
110 pub fn decompile_bytes_with_manifest(
111 &self,
112 bytes: &[u8],
113 manifest: Option<ContractManifest>,
114 output_format: OutputFormat,
115 ) -> Result<Decompilation> {
116 let nef = self.parser.parse(bytes)?;
117 let disassembly = self.disassembler.disassemble_with_warnings(&nef.script)?;
118 let instructions = disassembly.instructions;
119
120 let mut warnings = Vec::new();
121 let mut seen_warnings = HashSet::new();
122 let mut push_warning = |warning: String| {
123 if seen_warnings.insert(warning.clone()) {
124 warnings.push(warning);
125 }
126 };
127 for warning in disassembly.warnings {
128 push_warning(warning.to_string());
129 }
130
131 let cfg = CfgBuilder::new(&instructions).build();
132 let call_graph =
133 analysis::call_graph::build_call_graph(&nef, &instructions, manifest.as_ref());
134 let xrefs = analysis::xrefs::build_xrefs(&instructions, manifest.as_ref());
135 let types = analysis::types::infer_types(&instructions, manifest.as_ref());
136
137 let pseudocode = output_format
138 .wants_pseudocode()
139 .then(|| pseudocode::render(&instructions));
140 let high_level = output_format.wants_high_level().then(|| {
141 let render = high_level::render_high_level(
142 &nef,
143 &instructions,
144 manifest.as_ref(),
145 &call_graph,
146 self.inline_single_use_temps,
147 self.emit_trace_comments,
148 );
149 for warning in render.warnings {
150 push_warning(warning);
151 }
152 render.text
153 });
154 let csharp = output_format.wants_csharp().then(|| {
155 let render = csharp::render_csharp(
156 &nef,
157 &instructions,
158 manifest.as_ref(),
159 &call_graph,
160 self.inline_single_use_temps,
161 self.emit_trace_comments,
162 );
163 for warning in render.warnings {
164 push_warning(warning);
165 }
166 render.source
167 });
168
169 Ok(Decompilation {
170 nef,
171 manifest,
172 warnings,
173 instructions,
174 cfg,
175 call_graph,
176 xrefs,
177 types,
178 pseudocode,
179 high_level,
180 csharp,
181 ssa: None,
182 })
183 }
184
185 /// Decompile a NEF file from disk.
186 ///
187 /// # Errors
188 ///
189 /// Returns an error if the file cannot be read, the NEF container is
190 /// malformed, or disassembly fails.
191 pub fn decompile_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Decompilation> {
192 self.io_decompile_file(path)
193 }
194
195 /// Disassemble a NEF file from disk.
196 ///
197 /// # Errors
198 ///
199 /// Returns an error if the file cannot be read, the NEF container is
200 /// malformed, or disassembly fails.
201 pub fn disassemble_file<P: AsRef<std::path::Path>>(
202 &self,
203 path: P,
204 ) -> Result<DisassemblyOutput> {
205 self.io_disassemble_file(path)
206 }
207
208 /// Decompile a NEF file alongside an optional manifest file.
209 ///
210 /// # Errors
211 ///
212 /// Returns an error if either file cannot be read, the NEF container is
213 /// malformed, the manifest JSON is invalid, or disassembly fails.
214 pub fn decompile_file_with_manifest<P, Q>(
215 &self,
216 nef_path: P,
217 manifest_path: Option<Q>,
218 output_format: OutputFormat,
219 ) -> Result<Decompilation>
220 where
221 P: AsRef<std::path::Path>,
222 Q: AsRef<std::path::Path>,
223 {
224 self.io_decompile_file_with_manifest(nef_path, manifest_path, output_format)
225 }
226}