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::HighLevelEmitter;
use super::braces::find_matching_brace;

fn stmts(lines: &[&str]) -> Vec<String> {
    lines.iter().map(|s| s.to_string()).collect()
}

#[test]
fn collapses_unchecked_int32_add() {
    let mut s = stmts(&[
        "let t0 = a + b;",
        "let t1 = t0;",
        "let t2 = -2147483648;",
        "if t1 < t2 {",
        "goto label_0x001A;",
        "let t3 = t0;",
        "let t4 = 2147483647;",
        "if t3 > t4 {",
        "let t5 = 4294967295;",
        "let t6 = t0 & t5;",
        "}",
        "}",
    ]);
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s[0], "let t0 = a + b;");
    for (i, line) in s.iter().enumerate().take(12).skip(1) {
        assert!(line.is_empty(), "line {i} should be blank: {:?}", line);
    }
}

#[test]
fn collapses_checked_int32_add() {
    let mut s = stmts(&[
        "let t0 = a + b;",
        "let t1 = t0;",
        "let t2 = -2147483648;",
        "if t1 < t2 {",
        "throw(t0);",
        "let t3 = 2147483647;",
        "throw(t3);",
        "return;",
        "}",
    ]);
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s[0], "let t0 = checked(a + b);");
    for (i, line) in s.iter().enumerate().take(9).skip(1) {
        assert!(line.is_empty(), "line {i} should be blank: {:?}", line);
    }
}

#[test]
fn collapses_unsigned_range_check() {
    let mut s = stmts(&[
        "let t0 = a + b;",
        "let t1 = t0;",
        "let t2 = 0;",
        "if t1 < t2 {",
        "goto label_0x0084;",
        "let t3 = t0;",
        "let t4 = 4294967295;",
        "if t3 > t4 {",
        "let t5 = 4294967295;",
        "let t6 = t0 & t5;",
        "return t6;",
        "}",
        "}",
    ]);
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s[0], "let t0 = a + b;");
    for (i, line) in s.iter().enumerate().take(13).skip(1) {
        assert!(line.is_empty(), "line {i} should be blank: {:?}", line);
    }
}

#[test]
fn collapses_int64_range_check() {
    let mut s = stmts(&[
        "let t0 = a + b;",
        "let t1 = t0;",
        "let t2 = -9223372036854775808;",
        "if t1 < t2 {",
        "goto label_0x01AC;",
        "}",
    ]);
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s[0], "let t0 = a + b;");
    for (i, line) in s.iter().enumerate().take(6).skip(1) {
        assert!(line.is_empty(), "line {i} should be blank: {:?}", line);
    }
}

#[test]
fn does_not_match_unrelated_if() {
    let mut s = stmts(&[
        "let t0 = a + b;",
        "let t1 = t0;",
        "let t2 = 42;",
        "if t1 < t2 {",
        "return t0;",
        "}",
    ]);
    let original = s.clone();
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s, original);
}

#[test]
fn handles_negate_equality_check() {
    let mut s = stmts(&[
        "let t0 = a;",
        "let t1 = t0;",
        "let t2 = -2147483648;",
        "if t1 == t2 {",
        "throw(a);",
        "return;",
        "}",
    ]);
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s[0], "let t0 = checked(a);");
    for (i, line) in s.iter().enumerate().take(7).skip(1) {
        assert!(line.is_empty(), "line {i} should be blank: {:?}", line);
    }
}

#[test]
fn skips_interleaved_comments() {
    let mut s = stmts(&[
        "let t5 = t4 + 1;",
        "// 0029: DUP",
        "let t6 = t5; // duplicate top of stack",
        "// 002A: PUSHINT32",
        "let t7 = -2147483648;",
        "// 002F: JMPGE",
        "if t6 < t7 {",
        "}",
        "else {",
        "// 0033: DUP",
        "let t8 = t5; // duplicate top of stack",
        "// 0034: PUSHINT32",
        "let t9 = 2147483647;",
        "// 0039: JMPLE",
        "if t8 > t9 {",
        "}",
        "// 003B: PUSHINT64",
        "let t10 = 4294967295;",
        "// 0044: AND",
        "let t11 = t5 & t10;",
        "// 0045: DUP",
        "let t12 = t11; // duplicate top of stack",
        "// 0046: PUSHINT32",
        "let t13 = 2147483647;",
        "// 004B: JMPLE",
        "if t12 > t13 {",
        "// 004D: PUSHINT64",
        "let t14 = 4294967296;",
        "// 0056: SUB",
        "let t15 = t11 - t14;",
        "}",
        "let t5 = t15;",
        "}",
    ]);
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s[0], "let t5 = t4 + 1;");
    for (i, line) in s.iter().enumerate().skip(1) {
        assert!(line.is_empty(), "line {i} should be blank: {:?}", line);
    }
}

#[test]
fn handles_if_else_without_comments() {
    let mut s = stmts(&[
        "let t0 = a + b;",
        "let t1 = t0;",
        "let t2 = -2147483648;",
        "if t1 < t2 {",
        "}",
        "else {",
        "let t3 = t0;",
        "let t4 = 2147483647;",
        "if t3 > t4 {",
        "}",
        "let t5 = 4294967295;",
        "let t6 = t0 & t5;",
        "}",
    ]);
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s[0], "let t0 = a + b;");
    for (i, line) in s.iter().enumerate().skip(1) {
        assert!(line.is_empty(), "line {i} should be blank: {:?}", line);
    }
}

#[test]
fn preserves_indentation() {
    let mut s = stmts(&[
        "        let t0 = a + b;",
        "        let t1 = t0;",
        "        let t2 = -2147483648;",
        "        if t1 < t2 {",
        "            throw(t0);",
        "        }",
    ]);
    HighLevelEmitter::collapse_overflow_checks(&mut s);
    assert_eq!(s[0], "        let t0 = checked(a + b);");
    for (i, line) in s.iter().enumerate().take(6).skip(1) {
        assert!(line.is_empty(), "line {i} should be blank: {:?}", line);
    }
}

#[test]
fn find_matching_brace_closes_at_combined_else_line() {
    let statements = stmts(&["if cond {", "    a;", "} else {", "    b;", "}"]);
    assert_eq!(find_matching_brace(&statements, 0), Some(2));
    let separate = stmts(&["if cond {", "    a;", "}", "else {", "    b;", "}"]);
    assert_eq!(find_matching_brace(&separate, 0), Some(2));
}