use super::*;
use crate::instruction::OpCode;
#[test]
fn high_level_lifts_simple_if_block() {
let script = [
OpCode::Push1.byte(),
OpCode::Jmpifnot.byte(),
0x05,
OpCode::Push2.byte(),
OpCode::Ret.byte(),
OpCode::Push3.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(high_level.contains("if t0 {"));
assert!(high_level.contains("// 0003: PUSH2"));
assert!(high_level.contains("}\n // 0006: RET"));
}
#[test]
fn high_level_closes_if_at_end() {
let script = [
OpCode::Push1.byte(),
OpCode::Jmpifnot.byte(),
0x04,
OpCode::Push2.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(high_level.contains("if t0 {"));
assert!(high_level.contains(" }\n }\n}"));
}
#[test]
fn high_level_lifts_if_else_block() {
let script = [
OpCode::Push1.byte(),
OpCode::Jmpifnot.byte(),
0x05,
OpCode::Push2.byte(),
OpCode::Jmp.byte(),
0x04,
OpCode::Push3.byte(),
OpCode::Ret.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(high_level.contains("if t0 {"));
assert!(high_level.contains("else {"));
assert!(
high_level.contains("let t1 = 2;") || high_level.contains("return 2;"),
"if-branch value: {high_level}"
);
assert!(
high_level.contains("let t2 = 3;") || high_level.contains("return 3;"),
"else-branch value: {high_level}"
);
}
#[test]
fn high_level_lifts_jmpeq_forward_branch() {
let script = [
OpCode::Push1.byte(),
OpCode::Push1.byte(),
OpCode::JmpEq.byte(),
0x04,
OpCode::Push2.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
high_level.contains("if t0 != t1 {"),
"JMPEQ fall-through body should use negated condition: {high_level}"
);
assert!(
!high_level.contains("jump-if-eq ->"),
"JMPEQ should no longer emit a raw jump warning: {high_level}"
);
}
#[test]
fn high_level_lifts_jmpif_forward_branch() {
let script = [
OpCode::Push1.byte(),
OpCode::Jmpif.byte(),
0x04,
OpCode::Push2.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
high_level.contains("if !t0 {"),
"JMPIF forward branch should be lifted as negated if-condition: {high_level}"
);
assert!(
!high_level.contains("jump-if ->"),
"JMPIF should no longer emit raw jump-if warnings: {high_level}"
);
}
#[test]
fn high_level_lifts_jmpif_l_forward_branch() {
let script = [
OpCode::Push1.byte(),
OpCode::Jmpif_L.byte(),
0x06,
0x00,
0x00,
0x00,
OpCode::Push2.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
high_level.contains("if !t0 {"),
"JMPIF_L forward branch should be lifted as negated if-condition: {high_level}"
);
assert!(
!high_level.contains("jump-if ->"),
"JMPIF_L should no longer emit raw jump-if warnings: {high_level}"
);
}
#[test]
fn high_level_else_branch_restores_pre_branch_stack_snapshot() {
let script = [
OpCode::Push1.byte(),
OpCode::Push2.byte(),
OpCode::Push3.byte(),
OpCode::Push1.byte(),
OpCode::Jmpifnot.byte(),
0x06,
OpCode::Drop.byte(),
OpCode::Drop.byte(),
OpCode::Jmp.byte(),
0x03,
OpCode::Reverse3.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
!high_level.contains("insufficient values on stack for REVERSE3"),
"else entry should restore the pre-branch stack snapshot before REVERSE3: {high_level}"
);
}
#[test]
fn crossing_comparison_branch_does_not_emit_malformed_double_else() {
let script = [
OpCode::Push1.byte(),
OpCode::Push2.byte(),
OpCode::JmpLt.byte(),
0x08,
OpCode::Push3.byte(),
OpCode::Push4.byte(),
OpCode::JmpLt.byte(),
0x06,
OpCode::Push5.byte(),
OpCode::Ret.byte(),
OpCode::Push6.byte(),
OpCode::Ret.byte(),
OpCode::Push7.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
let balance: i32 = high_level
.chars()
.map(|c| match c {
'{' => 1,
'}' => -1,
_ => 0,
})
.sum();
assert_eq!(balance, 0, "output must be brace-balanced: {high_level}");
assert!(
high_level.matches("else {").count() <= 1,
"crossing comparison branch must not produce a second else: {high_level}"
);
}
#[test]
fn crossing_unary_branch_does_not_emit_malformed_double_else() {
let script = [
OpCode::Push1.byte(),
OpCode::Jmpif.byte(),
0x09,
OpCode::Push2.byte(),
OpCode::Jmpif.byte(),
0x04,
OpCode::Push7.byte(),
OpCode::Ret.byte(),
OpCode::Push8.byte(),
OpCode::Ret.byte(),
OpCode::Push9.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
let balance: i32 = high_level
.chars()
.map(|c| match c {
'{' => 1,
'}' => -1,
_ => 0,
})
.sum();
assert_eq!(balance, 0, "output must be brace-balanced: {high_level}");
assert!(
high_level.matches("else {").count() <= 1,
"crossing unary branch must not produce a second else: {high_level}"
);
}