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}
22
23impl Decompiler {
24    /// Create a new decompiler that permits unknown opcodes during disassembly.
25    ///
26    /// This is equivalent to `Decompiler::with_unknown_handling(UnknownHandling::Permit)`.
27    #[must_use]
28    pub fn new() -> Self {
29        Self::with_unknown_handling(UnknownHandling::Permit)
30    }
31
32    /// Create a new decompiler configured with the desired unknown-opcode policy.
33    ///
34    /// Unknown opcodes can appear when disassembling corrupted inputs or when
35    /// targeting a newer VM revision. Use [`crate::UnknownHandling::Error`] to
36    /// fail fast, or [`crate::UnknownHandling::Permit`] to emit `Unknown`
37    /// instructions and continue.
38    ///
39    /// # Examples
40    /// ```
41    /// use neo_decompiler::{Decompiler, UnknownHandling};
42    ///
43    /// let decompiler = Decompiler::with_unknown_handling(UnknownHandling::Error);
44    /// let _ = decompiler;
45    /// ```
46    #[must_use]
47    pub fn with_unknown_handling(handling: UnknownHandling) -> Self {
48        Self {
49            parser: NefParser::new(),
50            disassembler: Disassembler::with_unknown_handling(handling),
51            inline_single_use_temps: false,
52        }
53    }
54
55    /// Enable experimental inlining of single-use temporary variables in the high-level view.
56    ///
57    /// This can reduce noise in lifted code by replacing temps like `t0` with their RHS
58    /// at the first use site, but it may reduce readability for larger expressions.
59    #[must_use]
60    pub fn with_inline_single_use_temps(mut self, enabled: bool) -> Self {
61        self.inline_single_use_temps = enabled;
62        self
63    }
64
65    /// Decompile a NEF blob already loaded in memory.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if the NEF container is malformed or disassembly fails.
70    pub fn decompile_bytes(&self, bytes: &[u8]) -> Result<Decompilation> {
71        self.decompile_bytes_with_manifest(bytes, None, OutputFormat::All)
72    }
73
74    /// Disassemble a NEF blob already loaded in memory.
75    ///
76    /// This fast path parses the NEF container and decodes instructions only;
77    /// it skips CFG construction, analysis passes, and renderers.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if the NEF container is malformed or disassembly fails.
82    pub fn disassemble_bytes(&self, bytes: &[u8]) -> Result<DisassemblyOutput> {
83        let nef = self.parser.parse(bytes)?;
84        self.disassembler.disassemble_with_warnings(&nef.script)
85    }
86
87    /// Decompile a NEF blob using an optional manifest.
88    ///
89    /// # Errors
90    ///
91    /// Returns an error if the NEF container is malformed or disassembly fails.
92    pub fn decompile_bytes_with_manifest(
93        &self,
94        bytes: &[u8],
95        manifest: Option<ContractManifest>,
96        output_format: OutputFormat,
97    ) -> Result<Decompilation> {
98        let nef = self.parser.parse(bytes)?;
99        let disassembly = self.disassembler.disassemble_with_warnings(&nef.script)?;
100        let instructions = disassembly.instructions;
101
102        let mut warnings = Vec::new();
103        let mut seen_warnings = HashSet::new();
104        let mut push_warning = |warning: String| {
105            if seen_warnings.insert(warning.clone()) {
106                warnings.push(warning);
107            }
108        };
109        for warning in disassembly.warnings {
110            push_warning(warning.to_string());
111        }
112
113        let cfg = CfgBuilder::new(&instructions).build();
114        let call_graph =
115            analysis::call_graph::build_call_graph(&nef, &instructions, manifest.as_ref());
116        let xrefs = analysis::xrefs::build_xrefs(&instructions, manifest.as_ref());
117        let types = analysis::types::infer_types(&instructions, manifest.as_ref());
118
119        let pseudocode = output_format
120            .wants_pseudocode()
121            .then(|| pseudocode::render(&instructions));
122        let high_level = output_format.wants_high_level().then(|| {
123            let render = high_level::render_high_level(
124                &nef,
125                &instructions,
126                manifest.as_ref(),
127                &call_graph,
128                self.inline_single_use_temps,
129            );
130            for warning in render.warnings {
131                push_warning(warning);
132            }
133            render.text
134        });
135        let csharp = output_format.wants_csharp().then(|| {
136            let render = csharp::render_csharp(&nef, &instructions, manifest.as_ref(), &call_graph);
137            for warning in render.warnings {
138                push_warning(warning);
139            }
140            render.source
141        });
142
143        Ok(Decompilation {
144            nef,
145            manifest,
146            warnings,
147            instructions,
148            cfg,
149            call_graph,
150            xrefs,
151            types,
152            pseudocode,
153            high_level,
154            csharp,
155            ssa: None,
156        })
157    }
158
159    /// Decompile a NEF file from disk.
160    ///
161    /// # Errors
162    ///
163    /// Returns an error if the file cannot be read, the NEF container is
164    /// malformed, or disassembly fails.
165    pub fn decompile_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Decompilation> {
166        self.io_decompile_file(path)
167    }
168
169    /// Disassemble a NEF file from disk.
170    ///
171    /// # Errors
172    ///
173    /// Returns an error if the file cannot be read, the NEF container is
174    /// malformed, or disassembly fails.
175    pub fn disassemble_file<P: AsRef<std::path::Path>>(
176        &self,
177        path: P,
178    ) -> Result<DisassemblyOutput> {
179        self.io_disassemble_file(path)
180    }
181
182    /// Decompile a NEF file alongside an optional manifest file.
183    ///
184    /// # Errors
185    ///
186    /// Returns an error if either file cannot be read, the NEF container is
187    /// malformed, the manifest JSON is invalid, or disassembly fails.
188    pub fn decompile_file_with_manifest<P, Q>(
189        &self,
190        nef_path: P,
191        manifest_path: Option<Q>,
192        output_format: OutputFormat,
193    ) -> Result<Decompilation>
194    where
195        P: AsRef<std::path::Path>,
196        Q: AsRef<std::path::Path>,
197    {
198        self.io_decompile_file_with_manifest(nef_path, manifest_path, output_format)
199    }
200}