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;
use super::{forget_stack_value, is_simple_literal_or_identifier, replace_dynamic_stack_survivors};

impl HighLevelEmitter {
    pub(in super::super::super) fn emit_pick(&mut self, instruction: &Instruction) {
        self.push_comment(instruction);
        let Some(index_name) = self.stack.pop() else {
            self.stack_underflow(instruction, 1);
            return;
        };

        let index_literal = self.take_usize_literal(&index_name);

        if let Some(index) = index_literal {
            if index < self.stack.len() {
                let source = self.stack[self.stack.len() - 1 - index].clone();
                // Skip the temp for a simple literal/identifier (same
                // optimization as `dup_top`/`over_second`/`emit_tuck`):
                // duplicating a bare expression string is safe and the
                // JS port's `materialiseStackTopForDup` does the same, so
                // this keeps PICK output byte-identical across ports.
                if is_simple_literal_or_identifier(&source) {
                    self.stack.push(source);
                    return;
                }
                let temp = self.next_temp();
                self.statements
                    .push(format!("let {temp} = {source}; // pick stack[{index}]"));
                self.stack.push(temp.clone());
                if let Some(elements) = self.packed_values_by_name.get(&source).cloned() {
                    self.packed_values_by_name.insert(temp.clone(), elements);
                }
                if let Some(literal) = self.literal_values.get(&source).cloned() {
                    self.literal_values.insert(temp.clone(), literal);
                }
                return;
            }
        }

        let temp = self.next_temp();
        self.statements
            .push(format!("let {temp} = pick({index_name});"));
        self.stack.push(temp);
    }

    pub(in super::super::super) fn emit_roll(&mut self, instruction: &Instruction) {
        self.push_comment(instruction);
        if self.stack.len() < 2 {
            self.stack_underflow(instruction, 2);
            return;
        }
        let Some(index_name) = self.stack.pop() else {
            self.stack_underflow(instruction, 1);
            return;
        };

        let index_literal = self.take_usize_literal(&index_name);

        if let Some(index) = index_literal {
            if index < self.stack.len() {
                let pos = self.stack.len() - 1 - index;
                let value = self.stack.remove(pos);
                self.stack.push(value);
                self.statements
                    .push(format!("// roll stack[{index}] to top"));
                return;
            }
        }

        // Dynamic index: we cannot resolve which stack position moves, but
        // ROLL still removes one existing value and puts it on top. Conservedly
        // replace the current top source value so the modeled stack depth stays
        // aligned with the VM.
        let Some(source) = self.stack.pop() else {
            self.stack_underflow(instruction, 2);
            return;
        };
        forget_stack_value(self, &source);
        replace_dynamic_stack_survivors(self, "roll_remaining", &index_name);
        let temp = self.next_temp();
        self.statements
            .push(format!("let {temp} = roll({index_name}); // dynamic roll"));
        self.stack.push(temp);
    }

    pub(in super::super::super) fn emit_xdrop(&mut self, instruction: &Instruction) {
        self.push_comment(instruction);
        if self.stack.len() < 2 {
            self.stack_underflow(instruction, 2);
            return;
        }

        let Some(index_name) = self.stack.pop() else {
            return;
        };
        let index_literal = self.take_usize_literal(&index_name);

        if let Some(index) = index_literal {
            if index < self.stack.len() {
                let pos = self.stack.len() - 1 - index;
                let removed = self.stack.remove(pos);
                forget_stack_value(self, &removed);
                self.statements
                    .push(format!("// xdrop stack[{index}] (removed {removed})"));
                return;
            }
        }

        // Dynamic index: we cannot resolve which stack position is removed,
        // but XDROP still consumes one source value. Conservedly remove the
        // current top source so later stack-depth checks stay aligned with VM
        // semantics.
        let Some(source) = self.stack.pop() else {
            self.stack_underflow(instruction, 2);
            return;
        };
        forget_stack_value(self, &source);
        replace_dynamic_stack_survivors(self, "xdrop_remaining", &index_name);
        self.statements.push(format!(
            "// xdrop stack[{index_name}] (dynamic index, stack may be imprecise)"
        ));
    }
}