neo-decompiler 0.10.1

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;

use super::super::super::HighLevelEmitter;

impl HighLevelEmitter {
    pub(in super::super::super) fn emit_relative(
        &mut self,
        instruction: &Instruction,
        label: &str,
    ) {
        if self.skip_jumps.remove(&instruction.offset) {
            return;
        }
        if let Some(target) = self.jump_target(instruction) {
            self.warn(
                instruction,
                &format!("{label} -> 0x{target:04X} (control flow not yet lifted)"),
            );
        } else {
            self.warn(
                instruction,
                &format!("{label} with unsupported operand (skipping)"),
            );
        }
    }

    pub(in super::super::super) fn emit_jump(&mut self, instruction: &Instruction) {
        if self.skip_jumps.remove(&instruction.offset) {
            // jump consumed by structured if/else handling
            return;
        }
        match self.jump_target(instruction) {
            Some(target) => self.emit_known_jump_target(instruction, target),
            None => self.warn(instruction, "jump with unsupported operand (skipping)"),
        }
    }

    pub(in super::super::super) fn emit_endtry(&mut self, instruction: &Instruction) {
        if self.skip_jumps.remove(&instruction.offset) {
            return;
        }

        self.push_comment(instruction);
        match self.jump_target(instruction) {
            Some(target) => self.emit_known_endtry_target(target),
            None => self.warn(instruction, "end-try with unsupported operand (skipping)"),
        }
    }

    fn emit_known_jump_target(&mut self, instruction: &Instruction, target: usize) {
        if self.try_emit_loop_jump(instruction, target) {
            return;
        }
        // Tail-call: JMP to a known method entry point.
        if self.method_labels_by_offset.contains_key(&target) {
            self.emit_internal_call(instruction, target);
            let result = self.stack.pop().unwrap_or_default();
            if result.is_empty() {
                self.statements.push("return;".into());
            } else {
                self.statements.push(format!("return {result};"));
            }
            return;
        }
        self.push_comment(instruction);
        if self.index_by_offset.contains_key(&target) {
            self.transfer_labels.insert(target);
        }
        self.statements
            .push(format!("goto {};", Self::transfer_label_name(target)));
    }

    fn emit_known_endtry_target(&mut self, target: usize) {
        // When an ENDTRY targets a loop's continue offset, it acts as
        // `continue`: the VM executes finally, then resumes at the condition.
        if self
            .loop_stack
            .iter()
            .rev()
            .any(|ctx| target == ctx.continue_offset)
        {
            self.statements.push("continue;".into());
            return;
        }
        // When an ENDTRY targets at or beyond a loop's break offset, it acts
        // as a `break`, exiting both the try block and the enclosing loop.
        if self
            .loop_stack
            .iter()
            .rev()
            .any(|ctx| target >= ctx.break_offset)
        {
            self.statements.push("break;".into());
            return;
        }
        if self.index_by_offset.contains_key(&target) {
            self.transfer_labels.insert(target);
        }
        self.statements
            .push(format!("leave {};", Self::transfer_label_name(target)));
    }
}