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 super::super::super::*;

#[test]
fn rewrite_switch_statements_flattens_else_blocks_with_nested_chains() {
    let mut statements = vec![
        "if loc0 == 0 {".to_string(),
        "    do0;".to_string(),
        "}".to_string(),
        "else {".to_string(),
        "    if loc0 == 1 {".to_string(),
        "        do1;".to_string(),
        "    }".to_string(),
        "    else {".to_string(),
        "        do2;".to_string(),
        "    }".to_string(),
        "}".to_string(),
    ];

    HighLevelEmitter::rewrite_switch_statements(&mut statements);

    assert_eq!(statements[0], "switch loc0 {");
    assert!(statements.iter().any(|line| line.trim() == "case 0 {"));
    assert!(statements.iter().any(|line| line.trim() == "case 1 {"));
    assert!(statements.iter().any(|line| line.trim() == "default {"));
}

#[test]
fn rewrite_switch_statements_skips_duplicate_cases() {
    let mut statements = vec![
        "if loc0 == 0 {".to_string(),
        "    do0;".to_string(),
        "}".to_string(),
        "else if loc0 == 0 {".to_string(),
        "    do1;".to_string(),
        "}".to_string(),
        "else {".to_string(),
        "    do2;".to_string(),
        "}".to_string(),
    ];

    HighLevelEmitter::rewrite_switch_statements(&mut statements);

    assert_eq!(statements[0], "if loc0 == 0 {");
    assert!(!statements
        .iter()
        .any(|line| line.trim_start().starts_with("switch ")));
}

#[test]
fn rewrite_switch_statements_skips_non_literal_cases() {
    let mut statements = vec![
        "if loc0 == loc1 {".to_string(),
        "    do0;".to_string(),
        "}".to_string(),
        "else if loc0 == 1 {".to_string(),
        "    do1;".to_string(),
        "}".to_string(),
        "else {".to_string(),
        "    do2;".to_string(),
        "}".to_string(),
    ];

    HighLevelEmitter::rewrite_switch_statements(&mut statements);

    assert_eq!(statements[0], "if loc0 == loc1 {");
    assert!(!statements
        .iter()
        .any(|line| line.trim_start().starts_with("switch ")));
}

#[test]
fn rewrite_switch_statements_collapses_consecutive_standalone_ifs() {
    let mut statements = vec![
        "if loc0 == 0 {".to_string(),
        "    do0;".to_string(),
        "}".to_string(),
        "if loc0 == 1 {".to_string(),
        "    do1;".to_string(),
        "}".to_string(),
        "if loc0 == 2 {".to_string(),
        "    do2;".to_string(),
        "}".to_string(),
    ];

    HighLevelEmitter::rewrite_switch_statements(&mut statements);

    assert_eq!(statements[0], "switch loc0 {");
    assert!(statements.iter().any(|line| line.trim() == "case 0 {"));
    assert!(statements.iter().any(|line| line.trim() == "case 1 {"));
    assert!(statements.iter().any(|line| line.trim() == "case 2 {"));
    assert!(statements.iter().any(|line| line.trim() == "do0;"));
    assert!(statements.iter().any(|line| line.trim() == "do1;"));
    assert!(statements.iter().any(|line| line.trim() == "do2;"));
}

#[test]
fn rewrite_switch_statements_skips_two_consecutive_standalone_ifs() {
    let mut statements = vec![
        "if loc0 == 0 {".to_string(),
        "    do0;".to_string(),
        "}".to_string(),
        "if loc0 == 1 {".to_string(),
        "    do1;".to_string(),
        "}".to_string(),
    ];

    HighLevelEmitter::rewrite_switch_statements(&mut statements);

    // Only 2 cases - below the 3-case threshold for standalone ifs.
    assert_eq!(statements[0], "if loc0 == 0 {");
    assert!(!statements
        .iter()
        .any(|line| line.trim_start().starts_with("switch ")));
}

#[test]
fn rewrite_switch_statements_skips_consecutive_ifs_with_different_scrutinee() {
    let mut statements = vec![
        "if loc0 == 0 {".to_string(),
        "    do0;".to_string(),
        "}".to_string(),
        "if loc1 == 1 {".to_string(),
        "    do1;".to_string(),
        "}".to_string(),
        "if loc0 == 2 {".to_string(),
        "    do2;".to_string(),
        "}".to_string(),
    ];

    HighLevelEmitter::rewrite_switch_statements(&mut statements);

    assert_eq!(statements[0], "if loc0 == 0 {");
    assert!(!statements
        .iter()
        .any(|line| line.trim_start().starts_with("switch ")));
}

#[test]
fn rewrite_switch_keeps_if_chain_when_case_body_reassigns_scrutinee() {
    // Three standalone `if loc0 == k { ... }` blocks where the first body
    // reassigns `loc0`. In the bytecode the later comparisons can still
    // fire after the mutation, so folding into a `switch` (which asserts
    // exactly one case runs) would change semantics - leave it alone.
    let mut statements = vec![
        "if loc0 == 0 {".to_string(),
        "    loc0 = 1;".to_string(),
        "    do0;".to_string(),
        "}".to_string(),
        "if loc0 == 1 {".to_string(),
        "    do1;".to_string(),
        "}".to_string(),
        "if loc0 == 2 {".to_string(),
        "    do2;".to_string(),
        "}".to_string(),
    ];
    let original = statements.clone();

    HighLevelEmitter::rewrite_switch_statements(&mut statements);

    assert_eq!(
        statements, original,
        "scrutinee-mutating if-chain must not be rewritten to a switch"
    );
}

#[test]
fn rewrite_switch_folds_standalone_ifs_when_bodies_terminate() {
    // The same shape but every case body ends in a terminator, so exactly
    // one can run regardless of any mutation - safe to fold.
    let mut statements = vec![
        "if loc0 == 0 {".to_string(),
        "    return 0;".to_string(),
        "}".to_string(),
        "if loc0 == 1 {".to_string(),
        "    return 1;".to_string(),
        "}".to_string(),
        "if loc0 == 2 {".to_string(),
        "    return 2;".to_string(),
        "}".to_string(),
    ];

    HighLevelEmitter::rewrite_switch_statements(&mut statements);

    assert!(
        statements.iter().any(|line| line.trim() == "switch loc0 {"),
        "terminator-ending standalone ifs should fold into a switch: {statements:?}"
    );
}