neo-decompiler 0.11.0

Neo N3 NEF decompiler: parse, disassemble, lift bytecode to high-level pseudocode and C# skeletons, with a CLI, JSON reports, and optional WebAssembly bindings.
Documentation
use crate::disassembler::{Disassembler, DisassemblyOutput, UnknownHandling};
use crate::error::Result;
use crate::manifest::ContractManifest;
use crate::nef::NefParser;

use super::decompilation::Decompilation;
use super::output_format::OutputFormat;

mod bytes;
mod io;

/// Main entry point used by the CLI and tests.
#[derive(Debug, Default)]
pub struct Decompiler {
    parser: NefParser,
    disassembler: Disassembler,
    inline_single_use_temps: bool,
    emit_trace_comments: bool,
    typed_declarations: bool,
}

impl Decompiler {
    /// Create a new decompiler that permits unknown opcodes during disassembly.
    ///
    /// This is equivalent to `Decompiler::with_unknown_handling(UnknownHandling::Permit)`.
    #[must_use]
    pub fn new() -> Self {
        Self::with_unknown_handling(UnknownHandling::Permit)
    }

    /// Create a new decompiler configured with the desired unknown-opcode policy.
    ///
    /// Unknown opcodes can appear when disassembling corrupted inputs or when
    /// targeting a newer VM revision. Use [`crate::UnknownHandling::Error`] to
    /// fail fast, or [`crate::UnknownHandling::Permit`] to emit `Unknown`
    /// instructions and continue.
    ///
    /// # Examples
    /// ```
    /// use neo_decompiler::{Decompiler, UnknownHandling};
    ///
    /// let decompiler = Decompiler::with_unknown_handling(UnknownHandling::Error);
    /// let _ = decompiler;
    /// ```
    #[must_use]
    pub fn with_unknown_handling(handling: UnknownHandling) -> Self {
        Self {
            parser: NefParser::new(),
            disassembler: Disassembler::with_unknown_handling(handling),
            inline_single_use_temps: false,
            // Emit `// XXXX: OPCODE` trace comments next to each lifted
            // statement. Defaults to true to preserve existing rendering
            // (golden tests assert their presence). Disable via
            // `with_trace_comments(false)` for clean human-readable output.
            emit_trace_comments: true,
            // Annotate lifted signatures and slot declarations with inferred
            // types. Defaults to false to preserve historical output; enable
            // for richer, more strongly-typed pseudo/C# output.
            typed_declarations: false,
        }
    }

    /// Enable experimental inlining of single-use temporary variables in the high-level view.
    ///
    /// This can reduce noise in lifted code by replacing temps like `t0` with their RHS
    /// at the first use site, but it may reduce readability for larger expressions.
    #[must_use]
    pub fn with_inline_single_use_temps(mut self, enabled: bool) -> Self {
        self.inline_single_use_temps = enabled;
        self
    }

    /// Toggle inline `// XXXX: OPCODE` trace comments in the high-level view.
    ///
    /// Defaults to `true` (preserved historical behaviour). Set to `false` for
    /// clean output suitable for end users — the lifted statements alone, with
    /// no per-instruction trace markers. Untranslated instructions still emit
    /// their `// not yet translated` notes regardless of this setting.
    #[must_use]
    pub fn with_trace_comments(mut self, enabled: bool) -> Self {
        self.emit_trace_comments = enabled;
        self
    }

    /// Toggle inferred-type annotations on generated signatures and slot declarations.
    ///
    /// When enabled, renderers emit concrete inferred types for arguments,
    /// locals, and static fields (`fn script_entry(arg0: int)` and `int loc0 =
    /// ...;` in high-level output, `BigInteger arg0` / `BigInteger loc0 = ...;`
    /// in C#). Slots with no inferable type still fall back to the historical
    /// declaration form. Defaults to `false`; off has no effect, so existing
    /// tests stay byte-identical.
    #[must_use]
    pub fn with_typed_declarations(mut self, enabled: bool) -> Self {
        self.typed_declarations = enabled;
        self
    }

    /// Decompile a NEF blob already loaded in memory.
    ///
    /// # Errors
    ///
    /// Returns an error if the NEF container is malformed or disassembly fails.
    pub fn decompile_bytes(&self, bytes: &[u8]) -> Result<Decompilation> {
        self.decompile_bytes_with_manifest(bytes, None, OutputFormat::All)
    }

    /// Disassemble a NEF blob already loaded in memory.
    ///
    /// This fast path parses the NEF container and decodes instructions only;
    /// it skips CFG construction, analysis passes, and renderers.
    ///
    /// # Errors
    ///
    /// Returns an error if the NEF container is malformed or disassembly fails.
    pub fn disassemble_bytes(&self, bytes: &[u8]) -> Result<DisassemblyOutput> {
        self.disassemble_bytes_inner(bytes)
    }

    /// Decompile a NEF blob using an optional manifest.
    ///
    /// # Errors
    ///
    /// Returns an error if the NEF container is malformed or disassembly fails.
    pub fn decompile_bytes_with_manifest(
        &self,
        bytes: &[u8],
        manifest: Option<ContractManifest>,
        output_format: OutputFormat,
    ) -> Result<Decompilation> {
        self.decompile_bytes_with_manifest_inner(bytes, manifest, output_format)
    }

    /// Decompile a NEF file from disk.
    ///
    /// # Errors
    ///
    /// Returns an error if the file cannot be read, the NEF container is
    /// malformed, or disassembly fails.
    pub fn decompile_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<Decompilation> {
        self.io_decompile_file(path)
    }

    /// Disassemble a NEF file from disk.
    ///
    /// # Errors
    ///
    /// Returns an error if the file cannot be read, the NEF container is
    /// malformed, or disassembly fails.
    pub fn disassemble_file<P: AsRef<std::path::Path>>(
        &self,
        path: P,
    ) -> Result<DisassemblyOutput> {
        self.io_disassemble_file(path)
    }

    /// Decompile a NEF file alongside an optional manifest file.
    ///
    /// # Errors
    ///
    /// Returns an error if either file cannot be read, the NEF container is
    /// malformed, the manifest JSON is invalid, or disassembly fails.
    pub fn decompile_file_with_manifest<P, Q>(
        &self,
        nef_path: P,
        manifest_path: Option<Q>,
        output_format: OutputFormat,
    ) -> Result<Decompilation>
    where
        P: AsRef<std::path::Path>,
        Q: AsRef<std::path::Path>,
    {
        self.io_decompile_file_with_manifest(nef_path, manifest_path, output_format)
    }
}