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                self.inline_single_use_temps,
128            );
129            for warning in render.warnings {
130                push_warning(warning);
131            }
132            render.text
133        });
134        let csharp = output_format.wants_csharp().then(|| {
135            let render = csharp::render_csharp(&nef, &instructions, manifest.as_ref());
136            for warning in render.warnings {
137                push_warning(warning);
138            }
139            render.source
140        });
141
142        Ok(Decompilation {
143            nef,
144            manifest,
145            warnings,
146            instructions,
147            cfg,
148            call_graph,
149            xrefs,
150            types,
151            pseudocode,
152            high_level,
153            csharp,
154            ssa: None,
155        })
156    }
157
158    /// Decompile a NEF file from disk.
159    ///
160    /// # Errors
161    ///
162    /// Returns an error if the file cannot be read, the NEF container is
163    /// malformed, or disassembly fails.
164    pub fn decompile_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Decompilation> {
165        self.io_decompile_file(path)
166    }
167
168    /// Disassemble a NEF file from disk.
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if the file cannot be read, the NEF container is
173    /// malformed, or disassembly fails.
174    pub fn disassemble_file<P: AsRef<std::path::Path>>(
175        &self,
176        path: P,
177    ) -> Result<DisassemblyOutput> {
178        self.io_disassemble_file(path)
179    }
180
181    /// Decompile a NEF file alongside an optional manifest file.
182    ///
183    /// # Errors
184    ///
185    /// Returns an error if either file cannot be read, the NEF container is
186    /// malformed, the manifest JSON is invalid, or disassembly fails.
187    pub fn decompile_file_with_manifest<P, Q>(
188        &self,
189        nef_path: P,
190        manifest_path: Option<Q>,
191        output_format: OutputFormat,
192    ) -> Result<Decompilation>
193    where
194        P: AsRef<std::path::Path>,
195        Q: AsRef<std::path::Path>,
196    {
197        self.io_decompile_file_with_manifest(nef_path, manifest_path, output_format)
198    }
199}