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::instruction::{Instruction, Operand, OperandEncoding};

use super::super::basic_block::BlockId;
use super::CfgBuilder;

impl<'a> CfgBuilder<'a> {
    pub(super) fn offset_to_block_id(&self, offset: usize, leaders: &[usize]) -> BlockId {
        // `leaders` is sorted ascending, so binary-search for the index of the
        // largest leader <= offset instead of a linear reverse scan. The scan
        // was called once per terminator (one per block), making CFG
        // construction O(blocks^2) — a decompiler-hang DoS for a branch-dense
        // in-cap NEF (every jump creates a block).
        let p = leaders.partition_point(|&leader| leader <= offset);
        if p == 0 {
            BlockId::ENTRY
        } else {
            BlockId::new(p - 1)
        }
    }

    pub(super) fn instruction_end_offset(&self, index: usize) -> Option<usize> {
        self.instructions.get(index + 1).map(|ins| ins.offset)
    }

    pub(super) fn instruction_len_fallback(&self, instr: &Instruction) -> usize {
        match instr.opcode.operand_encoding() {
            OperandEncoding::None => 1,
            OperandEncoding::I8 | OperandEncoding::U8 | OperandEncoding::Jump8 => 2,
            OperandEncoding::I16 | OperandEncoding::U16 => 3,
            OperandEncoding::I32
            | OperandEncoding::U32
            | OperandEncoding::Jump32
            | OperandEncoding::Syscall => 5,
            OperandEncoding::I64 => 9,
            OperandEncoding::Bytes(n) => 1 + n,
            OperandEncoding::Data1 => 1 + 1 + Self::bytes_len(instr),
            OperandEncoding::Data2 => 1 + 2 + Self::bytes_len(instr),
            OperandEncoding::Data4 => 1 + 4 + Self::bytes_len(instr),
        }
    }

    fn bytes_len(instr: &Instruction) -> usize {
        match &instr.operand {
            Some(Operand::Bytes(bytes)) => bytes.len(),
            _ => 0,
        }
    }

    pub(super) fn end_offset(&self) -> usize {
        let Some(last) = self.instructions.last() else {
            return 0;
        };
        let last_index = self.instructions.len().saturating_sub(1);
        self.instruction_end_offset(last_index)
            .unwrap_or_else(|| last.offset + self.instruction_len_fallback(last))
    }
}