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, OpCode, Operand};

use super::state::{StackEffect, MAX_SIMULATED_POPS};

pub(super) fn stack_effect_for_arg_inference(
    instruction: &Instruction,
    simulated_stack: &[Option<usize>],
) -> Option<StackEffect> {
    use OpCode::*;

    let literal_push = |value: usize| StackEffect {
        pops: 0,
        pushes: vec![Some(value)],
    };
    let unknown_push = || StackEffect {
        pops: 0,
        pushes: vec![None],
    };
    let unary_unknown = || StackEffect {
        pops: 1,
        pushes: vec![None],
    };
    let binary_unknown = || StackEffect {
        pops: 2,
        pushes: vec![None],
    };

    match instruction.opcode {
        Push0 => Some(literal_push(0)),
        Push1 => Some(literal_push(1)),
        Push2 => Some(literal_push(2)),
        Push3 => Some(literal_push(3)),
        Push4 => Some(literal_push(4)),
        Push5 => Some(literal_push(5)),
        Push6 => Some(literal_push(6)),
        Push7 => Some(literal_push(7)),
        Push8 => Some(literal_push(8)),
        Push9 => Some(literal_push(9)),
        Push10 => Some(literal_push(10)),
        Push11 => Some(literal_push(11)),
        Push12 => Some(literal_push(12)),
        Push13 => Some(literal_push(13)),
        Push14 => Some(literal_push(14)),
        Push15 => Some(literal_push(15)),
        Push16 => Some(literal_push(16)),
        Pushint8 | Pushint16 | Pushint32 | Pushint64 => match instruction.operand {
            Some(Operand::I8(v)) if v >= 0 => Some(literal_push(v as usize)),
            Some(Operand::I16(v)) if v >= 0 => Some(literal_push(v as usize)),
            Some(Operand::I32(v)) if v >= 0 => Some(literal_push(v as usize)),
            Some(Operand::I64(v)) if v >= 0 => Some(literal_push(v as usize)),
            _ => Some(unknown_push()),
        },
        Pushint128 | Pushint256 | PushT | PushF | PushA | PushNull | Pushdata1 | Pushdata2
        | Pushdata4 | PushM1 | Newarray0 | Newstruct0 | Newmap | Ldsfld0 | Ldsfld1 | Ldsfld2
        | Ldsfld3 | Ldsfld4 | Ldsfld5 | Ldsfld6 | Ldsfld | Ldloc0 | Ldloc1 | Ldloc2 | Ldloc3
        | Ldloc4 | Ldloc5 | Ldloc6 | Ldloc | Ldarg0 | Ldarg1 | Ldarg2 | Ldarg3 | Ldarg4
        | Ldarg5 | Ldarg6 | Ldarg | Depth => Some(unknown_push()),
        Nop | Initsslot | Initslot => Some(StackEffect {
            pops: 0,
            pushes: vec![],
        }),
        Drop | Stsfld0 | Stsfld1 | Stsfld2 | Stsfld3 | Stsfld4 | Stsfld5 | Stsfld6 | Stsfld
        | Stloc0 | Stloc1 | Stloc2 | Stloc3 | Stloc4 | Stloc5 | Stloc6 | Stloc | Starg0
        | Starg1 | Starg2 | Starg3 | Starg4 | Starg5 | Starg6 | Starg | Reverseitems
        | Clearitems => Some(StackEffect {
            pops: 1,
            pushes: vec![],
        }),
        Newbuffer | Isnull | Istype | Convert | Keys | Values | Size | Sign | Abs | Negate
        | Inc | Dec | Not | Nz | Sqrt | Newarray | NewarrayT | Newstruct | Invert => {
            Some(unary_unknown())
        }
        Dup => {
            let top = simulated_stack.last().copied().flatten();
            Some(StackEffect {
                pops: 1,
                pushes: vec![top, top],
            })
        }
        Nip => Some(StackEffect {
            pops: 2,
            pushes: vec![None],
        }),
        Over | Tuck => Some(StackEffect {
            pops: 2,
            pushes: vec![None, None, None],
        }),
        Swap => Some(StackEffect {
            pops: 2,
            pushes: vec![None, None],
        }),
        Rot | Reverse3 => Some(StackEffect {
            pops: 3,
            pushes: vec![None, None, None],
        }),
        Reverse4 => Some(StackEffect {
            pops: 4,
            pushes: vec![None, None, None, None],
        }),
        Cat | Left | Right | And | Or | Xor | Equal | Notequal | Add | Sub | Mul | Div | Mod
        | Pow | Shl | Shr | Booland | Boolor | Numequal | Numnotequal | Lt | Le | Gt | Ge | Min
        | Max | Haskey | Pickitem => Some(binary_unknown()),
        Popitem => Some(unary_unknown()),
        Append | Remove => Some(StackEffect {
            pops: 2,
            pushes: vec![],
        }),
        Substr | Modmul | Modpow | Within => Some(StackEffect {
            pops: 3,
            pushes: vec![None],
        }),
        Setitem => Some(StackEffect {
            pops: 3,
            pushes: vec![],
        }),
        Memcpy => Some(StackEffect {
            pops: 5,
            pushes: vec![],
        }),
        Pack | Packstruct => {
            let count = simulated_stack.last().copied().flatten()?;
            let pops = count.saturating_add(1).min(MAX_SIMULATED_POPS);
            Some(StackEffect {
                pops,
                pushes: vec![None],
            })
        }
        Packmap => {
            let count = simulated_stack.last().copied().flatten()?;
            let pops = count
                .saturating_mul(2)
                .saturating_add(1)
                .min(MAX_SIMULATED_POPS);
            Some(StackEffect {
                pops,
                pushes: vec![None],
            })
        }
        Syscall => match instruction.operand {
            Some(Operand::Syscall(hash)) => {
                let info = crate::syscalls::lookup(hash);
                let pops = info.map_or(0usize, |syscall| syscall.param_count as usize);
                let pushes = if info.is_some_and(|syscall| syscall.returns_value) {
                    vec![None]
                } else {
                    vec![]
                };
                Some(StackEffect { pops, pushes })
            }
            _ => None,
        },
        _ => None,
    }
}