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 super::*;
use crate::decompiler::cfg::ssa::dominance::DominanceInfo;
use crate::decompiler::cfg::ssa::variable::{PhiNode, SsaVariable};
use crate::decompiler::cfg::{BlockId, Cfg};
use crate::decompiler::ir::{BinOp, Literal, UnaryOp};

#[test]
fn test_ssa_form_creation() {
    let cfg = Cfg::new();
    let dominance = DominanceInfo::new();
    let ssa = SsaForm::new(cfg, dominance);

    assert_eq!(ssa.block_count(), 0);
    assert!(ssa.definitions.is_empty());
    assert!(ssa.uses.is_empty());
}

#[test]
fn test_ssa_block_additions() {
    let mut block = SsaBlock::new();

    let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
    block.add_phi(phi);

    let stmt = SsaStmt::assign(
        SsaVariable::new("y".to_string(), 0),
        SsaExpr::lit(Literal::Int(42)),
    );
    block.add_stmt(stmt);

    assert_eq!(block.phi_count(), 1);
    assert_eq!(block.stmt_count(), 1);
    assert!(!block.is_empty());
}

#[test]
fn test_dominance_info_empty() {
    let info = DominanceInfo::new();

    assert!(info.idom(BlockId(0)).is_none());
    assert!(info.children(BlockId(0)).is_empty());
    assert!(info.dominance_frontier_vec(BlockId(0)).is_empty());
}

#[test]
fn test_ssa_expr_constructors() {
    let var = SsaVariable::initial("x".to_string());
    let expr = SsaExpr::var(var);

    assert!(matches!(expr, SsaExpr::Variable(_)));

    let lit = SsaExpr::lit(Literal::Int(42));
    assert!(matches!(lit, SsaExpr::Literal(_)));

    let binary = SsaExpr::binary(
        BinOp::Add,
        SsaExpr::lit(Literal::Int(1)),
        SsaExpr::lit(Literal::Int(2)),
    );
    assert!(matches!(binary, SsaExpr::Binary { .. }));

    let call = SsaExpr::call("foo".to_string(), vec![]);
    assert!(matches!(call, SsaExpr::Call { .. }));
}

#[test]
fn test_use_site() {
    let site = UseSite::new(BlockId(5), 10);

    assert_eq!(site.block, BlockId(5));
    assert_eq!(site.stmt_index, 10);
}

#[test]
fn test_ssa_expr_display() {
    let var = SsaVariable::initial("x".to_string());
    assert_eq!(format!("{}", SsaExpr::var(var.clone())), "x");

    let lit = SsaExpr::lit(Literal::Int(42));
    assert_eq!(format!("{}", lit), "42");

    let binary = SsaExpr::binary(
        BinOp::Add,
        SsaExpr::var(var.clone()),
        SsaExpr::lit(Literal::Int(10)),
    );
    assert_eq!(format!("{}", binary), "(x + 10)");

    let unary = SsaExpr::unary(UnaryOp::Neg, SsaExpr::var(var));
    assert_eq!(format!("{}", unary), "-(x)");

    let var2 = SsaVariable::initial("x".to_string());
    let call = SsaExpr::call("foo".to_string(), vec![SsaExpr::var(var2)]);
    assert_eq!(format!("{}", call), "foo(x)");
}

#[test]
fn test_ssa_stmt_display() {
    let var = SsaVariable::new("result".to_string(), 0);
    let stmt = SsaStmt::assign(var, SsaExpr::lit(Literal::Int(42)));

    assert_eq!(format!("{}", stmt), "result = 42;");
}

#[test]
fn test_ssa_block_display() {
    let mut block = SsaBlock::new();

    let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
    block.add_phi(phi);

    let stmt = SsaStmt::assign(
        SsaVariable::new("y".to_string(), 0),
        SsaExpr::lit(Literal::Int(42)),
    );
    block.add_stmt(stmt);

    let display = format!("{}", block);
    assert!(display.contains("φ"));
    assert!(display.contains("y = 42;"));
}

#[test]
fn test_ssa_form_render() {
    let cfg = Cfg::new();
    let dominance = DominanceInfo::new();
    let mut ssa = SsaForm::new(cfg, dominance);

    let mut block = SsaBlock::new();
    block.add_stmt(SsaStmt::assign(
        SsaVariable::new("x".to_string(), 0),
        SsaExpr::lit(Literal::Int(42)),
    ));
    ssa.add_block(BlockId::ENTRY, block);

    let rendered = ssa.render();
    assert!(rendered.contains("SSA Form"));
    assert!(rendered.contains("block"));
    assert!(rendered.contains("x = 42;"));
}

#[test]
fn test_ssa_stats() {
    let cfg = Cfg::new();
    let dominance = DominanceInfo::new();
    let mut ssa = SsaForm::new(cfg, dominance);

    let mut block = SsaBlock::new();

    let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
    block.add_phi(phi);

    let stmt = SsaStmt::assign(
        SsaVariable::new("y".to_string(), 0),
        SsaExpr::lit(Literal::Int(42)),
    );
    block.add_stmt(stmt);

    ssa.add_block(BlockId::ENTRY, block);
    ssa.add_definition(SsaVariable::initial("y".to_string()), BlockId::ENTRY);

    let stats = ssa.stats();
    assert_eq!(stats.block_count, 1);
    assert_eq!(stats.total_phi_nodes, 1);
    assert_eq!(stats.total_statements, 1);
    assert_eq!(stats.total_variables, 1);
}

#[test]
fn test_ssa_expr_complex() {
    let arr = SsaExpr::Array(vec![
        SsaExpr::lit(Literal::Int(1)),
        SsaExpr::lit(Literal::Int(2)),
    ]);
    assert_eq!(format!("{}", arr), "[1, 2]");

    let map = SsaExpr::Map(vec![(
        SsaExpr::lit(Literal::String("key".to_string())),
        SsaExpr::lit(Literal::Int(1)),
    )]);
    assert!(format!("{}", map).contains("{"));

    let ternary = SsaExpr::Ternary {
        condition: Box::new(SsaExpr::lit(Literal::Bool(true))),
        then_expr: Box::new(SsaExpr::lit(Literal::Int(1))),
        else_expr: Box::new(SsaExpr::lit(Literal::Int(2))),
    };
    assert_eq!(format!("{}", ternary), "true ? 1 : 2");
}

#[test]
fn phi_placement_diamond_cfg() {
    use super::super::dominance;
    use crate::decompiler::cfg::{BasicBlock, EdgeKind, Terminator};

    let mut cfg = Cfg::new();

    let entry = BasicBlock::new(
        BlockId(0),
        0,
        1,
        0..1,
        Terminator::Branch {
            then_target: BlockId(1),
            else_target: BlockId(2),
        },
    );
    cfg.add_block(entry);

    let left = BasicBlock::new(
        BlockId(1),
        1,
        2,
        1..2,
        Terminator::Jump { target: BlockId(3) },
    );
    cfg.add_block(left);
    cfg.add_edge(BlockId(0), BlockId(1), EdgeKind::ConditionalTrue);

    let right = BasicBlock::new(
        BlockId(2),
        2,
        3,
        2..3,
        Terminator::Jump { target: BlockId(3) },
    );
    cfg.add_block(right);
    cfg.add_edge(BlockId(0), BlockId(2), EdgeKind::ConditionalFalse);

    let exit = BasicBlock::new(BlockId(3), 3, 4, 3..4, Terminator::Return);
    cfg.add_block(exit);
    cfg.add_edge(BlockId(1), BlockId(3), EdgeKind::Unconditional);
    cfg.add_edge(BlockId(2), BlockId(3), EdgeKind::Unconditional);

    let dom = dominance::compute(&cfg);
    assert_eq!(dom.dominance_frontier_vec(BlockId(1)), vec![BlockId(3)],);
    assert_eq!(dom.dominance_frontier_vec(BlockId(2)), vec![BlockId(3)],);

    let mut ssa = SsaForm::new(cfg, dom);

    ssa.add_block(BlockId(0), SsaBlock::new());

    let mut bb1 = SsaBlock::new();
    let x0 = SsaVariable::new("x".to_string(), 0);
    bb1.add_stmt(SsaStmt::assign(x0.clone(), SsaExpr::lit(Literal::Int(1))));
    ssa.add_block(BlockId(1), bb1);
    ssa.add_definition(x0, BlockId(1));

    let mut bb2 = SsaBlock::new();
    let x1 = SsaVariable::new("x".to_string(), 1);
    bb2.add_stmt(SsaStmt::assign(x1.clone(), SsaExpr::lit(Literal::Int(2))));
    ssa.add_block(BlockId(2), bb2);
    ssa.add_definition(x1, BlockId(2));

    let mut bb3 = SsaBlock::new();
    let mut phi = PhiNode::new(SsaVariable::new("x".to_string(), 2));
    phi.operands
        .insert(BlockId(1), SsaVariable::new("x".to_string(), 0));
    phi.operands
        .insert(BlockId(2), SsaVariable::new("x".to_string(), 1));
    bb3.add_phi(phi);
    ssa.add_block(BlockId(3), bb3);

    let merge_block = ssa.block(BlockId(3)).expect("BB3 must exist");
    assert_eq!(merge_block.phi_count(), 1, "BB3 should have 1 phi node");
    assert_eq!(
        merge_block.phi_nodes[0].target.base, "x",
        "phi target should be variable x"
    );
    assert_eq!(
        merge_block.phi_nodes[0].operands.len(),
        2,
        "phi should have 2 operands (one per predecessor)"
    );

    assert_eq!(
        ssa.block(BlockId(0)).unwrap().phi_count(),
        0,
        "entry should have no phi nodes"
    );
    assert_eq!(
        ssa.block(BlockId(1)).unwrap().phi_count(),
        0,
        "BB1 should have no phi nodes"
    );
    assert_eq!(
        ssa.block(BlockId(2)).unwrap().phi_count(),
        0,
        "BB2 should have no phi nodes"
    );
}