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::decompiler::analysis::{MethodRef, MethodTable};
use crate::decompiler::cfg::{BasicBlock, BlockId, Cfg, EdgeKind, Terminator};
use crate::instruction::{Instruction, OpCode};
use crate::manifest::ContractManifest;

use super::extract::extract_method_cfgs;
use super::render::render_method_body;
use super::MethodView;

fn ins(offset: usize, op: OpCode) -> Instruction {
    Instruction::new(offset, op, None)
}

fn two_method_whole_cfg() -> (Cfg, Vec<Instruction>) {
    let mut cfg = Cfg::new();
    cfg.add_block(BasicBlock::new(
        BlockId(0),
        0,
        2,
        0..2,
        Terminator::Jump {
            target: BlockId(10),
        },
    ));
    cfg.add_block(BasicBlock::new(
        BlockId(2),
        2,
        2,
        2..2,
        Terminator::Jump {
            target: BlockId(10),
        },
    ));
    cfg.add_edge(BlockId(0), BlockId(2), EdgeKind::Unconditional);
    cfg.add_edge(BlockId(2), BlockId(10), EdgeKind::Unconditional);
    cfg.add_block(BasicBlock::new(
        BlockId(10),
        10,
        12,
        10..12,
        Terminator::Return,
    ));
    let instructions = vec![
        ins(0, OpCode::Push1),
        ins(1, OpCode::Ret),
        ins(10, OpCode::Push0),
        ins(11, OpCode::Ret),
    ];
    (cfg, instructions)
}

#[test]
fn extract_produces_two_sub_cfgs_with_cross_range_jump_rewritten() {
    let (cfg, instructions) = two_method_whole_cfg();
    let manifest_json = r#"{"name":"C","abi":{"methods":[
            {"name":"main","parameters":[],"returntype":"Integer","offset":0},
            {"name":"helper","parameters":[],"returntype":"Integer","offset":10}
        ]}}"#;
    let manifest: ContractManifest = serde_json::from_str(manifest_json).unwrap();
    let table = MethodTable::new(&instructions, Some(&manifest));
    let views = extract_method_cfgs(&cfg, &table, &instructions);
    assert_eq!(views.len(), 2, "expected two method spans");
    let a = &views[0];
    let a0 = a.cfg.block(BlockId(0)).expect("block 0 in A");
    assert!(matches!(a0.terminator, Terminator::Return));
    let b = &views[1];
    let b10 = b.cfg.block(BlockId(10)).expect("block 10 in B");
    assert!(matches!(b10.terminator, Terminator::Return));
    assert!(a.cfg.block(BlockId(0)).is_some());
}

#[test]
fn render_method_body_emits_fn_with_return_type() {
    // A trivial method: PUSH1 ; RET -> `fn main() -> Integer { ... return ... }`.
    let instructions = vec![
        Instruction::new(0, OpCode::Push1, None),
        Instruction::new(1, OpCode::Ret, None),
    ];
    let mut cfg = Cfg::new();
    cfg.add_block(BasicBlock::new(BlockId(0), 0, 2, 0..2, Terminator::Return));
    let view = MethodView {
        method: MethodRef {
            offset: 0,
            name: "main".to_string(),
        },
        cfg,
        instructions,
    };
    let manifest_json = r#"{"name":"C","abi":{"methods":[
            {"name":"main","parameters":[],"returntype":"Integer"}
        ]}}"#;
    let manifest: ContractManifest = serde_json::from_str(manifest_json).unwrap();
    let out = render_method_body(&view, Some(&manifest));
    assert!(out.contains("fn main() -> int"), "got:\n{out}");
    assert!(out.contains("return"), "got:\n{out}");
}