neo-decompiler 0.10.2

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};

use super::super::super::{HighLevelEmitter, LoopContext};

impl HighLevelEmitter {
    pub(in super::super::super) fn emit_comparison_if_block(
        &mut self,
        instruction: &Instruction,
        symbol: &str,
    ) {
        let delta = match instruction.operand {
            Some(Operand::Jump(value)) => value as isize,
            Some(Operand::Jump32(value)) => value as isize,
            _ => {
                self.emit_relative(instruction, &format!("jump-if-{symbol}"));
                return;
            }
        };
        let target = instruction.offset as isize + delta;
        if target <= instruction.offset as isize {
            self.emit_relative(instruction, &format!("jump-if-{symbol}"));
            return;
        }

        if self.stack.len() < 2 {
            self.push_comment(instruction);
            self.stack_underflow(instruction, 2);
            return;
        }

        let (Some(right), Some(left)) = (self.stack.pop(), self.stack.pop()) else {
            return;
        };
        let condition = format!("{left} {symbol} {right}");

        let false_target = target as usize;
        if self.has_crossing_closer(false_target)
            || self.has_internal_crossing_branch(instruction.offset, false_target)
        {
            self.emit_conditional_goto(instruction, &format!("!({condition})"), false_target);
            return;
        }

        self.begin_branch_body(instruction, &format!("if {condition} {{"), false_target);
        self.register_else_for_branch(instruction.offset, false_target);
    }

    pub(in super::super::super) fn emit_if_block(&mut self, instruction: &Instruction) {
        self.emit_unary_if_block(instruction, false, "jump-ifnot");
    }

    pub(in super::super::super) fn emit_jmpif_block(&mut self, instruction: &Instruction) {
        self.emit_unary_if_block(instruction, true, "jump-if");
    }

    fn emit_unary_if_block(
        &mut self,
        instruction: &Instruction,
        negate_condition: bool,
        fallback_label: &str,
    ) {
        let delta = match instruction.operand {
            Some(Operand::Jump(value)) => value as isize,
            Some(Operand::Jump32(value)) => value as isize,
            _ => {
                self.emit_relative(instruction, fallback_label);
                return;
            }
        };
        let target = instruction.offset as isize + delta;
        if target <= instruction.offset as isize {
            self.emit_relative(instruction, fallback_label);
            return;
        }

        let raw_condition = match self.stack.pop() {
            Some(value) => value,
            None => {
                self.push_comment(instruction);
                self.stack_underflow(instruction, 1);
                return;
            }
        };
        let condition = if negate_condition {
            format!("!{raw_condition}")
        } else {
            raw_condition.clone()
        };

        let false_target = target as usize;
        if self.has_crossing_closer(false_target)
            || self.has_internal_crossing_branch(instruction.offset, false_target)
        {
            let jump_condition = if negate_condition {
                raw_condition
            } else {
                format!("!{raw_condition}")
            };
            self.emit_conditional_goto(instruction, &jump_condition, false_target);
            return;
        }

        let loop_jump = self.detect_loop_back(false_target, instruction.offset);
        if let Some(loop_jump) = loop_jump.as_ref() {
            self.begin_branch_body(instruction, &format!("while {condition} {{"), false_target);
            self.skip_jumps.insert(loop_jump.jump_offset);
            self.loop_stack.push(LoopContext {
                break_offset: false_target,
                continue_offset: loop_jump.target,
            });
        } else {
            self.begin_branch_body(instruction, &format!("if {condition} {{"), false_target);
            self.register_else_for_branch(instruction.offset, false_target);
        }
    }

    fn begin_branch_body(&mut self, instruction: &Instruction, header: &str, false_target: usize) {
        self.push_comment(instruction);
        self.statements.push(header.to_string());
        self.branch_saved_stacks
            .entry(false_target)
            .or_insert_with(|| self.stack.clone());
        let closer_entry = self.pending_closers.entry(false_target).or_insert(0);
        *closer_entry += 1;
    }

    fn emit_conditional_goto(&mut self, instruction: &Instruction, condition: &str, target: usize) {
        self.push_comment(instruction);
        if self.index_by_offset.contains_key(&target) {
            self.transfer_labels.insert(target);
        }
        self.statements.push(format!(
            "if {condition} {{ goto {}; }}",
            Self::transfer_label_name(target)
        ));
    }

    fn register_else_for_branch(&mut self, branch_offset: usize, false_target: usize) {
        if let Some((jump_offset, jump_target)) = self.detect_else(branch_offset, false_target) {
            if !self.is_loop_control_target(jump_target)
                && !self.else_targets.contains_key(&false_target)
            {
                self.skip_jumps.insert(jump_offset);
                let else_entry = self.else_targets.entry(false_target).or_insert(0);
                *else_entry += 1;
                let closer = self.pending_closers.entry(jump_target).or_insert(0);
                *closer += 1;
                self.pre_branch_stack_depth
                    .entry(jump_target)
                    .or_insert(self.stack.len());
            }
        } else if let Some(else_end) = self.detect_implicit_else(branch_offset, false_target) {
            if !self.else_targets.contains_key(&false_target) {
                let else_entry = self.else_targets.entry(false_target).or_insert(0);
                *else_entry += 1;
                let closer = self.pending_closers.entry(else_end).or_insert(0);
                *closer += 1;
            }
        }
    }
}