Skip to main content

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}