use super::*;
use crate::instruction::OpCode;
#[test]
fn high_level_lifts_try_finally_blocks() {
let script = [
OpCode::Try.byte(),
0x00,
0x03,
OpCode::Push1.byte(),
OpCode::Endtry.byte(),
0x02,
OpCode::Push2.byte(),
OpCode::Endfinally.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("try {"),
"missing try block: {high_level}"
);
assert!(
high_level.contains("finally {"),
"missing finally block: {high_level}"
);
assert!(
!high_level.contains("end-try ->"),
"structured try should suppress ENDTRY note: {high_level}"
);
assert!(
!high_level.contains("endfinally"),
"structured try should suppress ENDFINALLY note: {high_level}"
);
}
#[test]
fn high_level_lifts_try_catch_blocks() {
let script = [
OpCode::Try.byte(),
0x03,
0x00,
OpCode::Push1.byte(),
OpCode::Endtry.byte(),
0x03,
OpCode::Push2.byte(),
OpCode::Endtry.byte(),
0x00,
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("try {"),
"missing try header: {high_level}"
);
assert!(
high_level.contains("catch {"),
"missing catch header: {high_level}"
);
assert!(
!high_level.contains("end-try ->"),
"structured try should suppress ENDTRY notes: {high_level}"
);
}
#[test]
fn high_level_lifts_try_catch_finally_blocks() {
let script = [
OpCode::Try.byte(),
0x03,
0x06,
OpCode::Push1.byte(),
OpCode::Endtry.byte(),
0x05,
OpCode::Push2.byte(),
OpCode::Endtry.byte(),
0x02,
OpCode::Push3.byte(),
OpCode::Endfinally.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("try {"),
"missing try header: {high_level}"
);
assert!(
high_level.contains("catch {"),
"missing catch header: {high_level}"
);
assert!(
high_level.contains("finally {"),
"missing finally header: {high_level}"
);
assert!(
!high_level.contains("endfinally"),
"structured try should suppress ENDFINALLY note: {high_level}"
);
}
#[test]
fn high_level_lifts_try_finally_with_throw_inside() {
let script = [
OpCode::Try.byte(),
0x00,
0x04, OpCode::Push1.byte(), OpCode::Throw.byte(), OpCode::Endtry.byte(),
0x02, OpCode::Push2.byte(), OpCode::Endfinally.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("try {"),
"missing try block: {high_level}"
);
assert!(
high_level.contains("throw("),
"THROW inside try should be lifted: {high_level}"
);
assert!(
high_level.contains("finally {"),
"missing finally block: {high_level}"
);
}
#[test]
fn high_level_lifts_try_catch_with_abort_in_catch() {
let script = [
OpCode::Try.byte(),
0x03,
0x00, OpCode::Push1.byte(), OpCode::Endtry.byte(),
0x03, OpCode::Abort.byte(), OpCode::Endtry.byte(),
0x00, 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("try {"),
"missing try header: {high_level}"
);
assert!(
high_level.contains("catch {"),
"missing catch header: {high_level}"
);
assert!(
high_level.contains("abort()"),
"ABORT inside catch should be lifted: {high_level}"
);
}
#[test]
fn high_level_models_catch_entry_stack_with_exception_value() {
let script = [
OpCode::Try.byte(),
0x06,
0x00, OpCode::Push1.byte(), OpCode::Endtry.byte(),
0x06, OpCode::Stloc0.byte(), OpCode::Ldloc0.byte(), OpCode::Endtry.byte(),
0x00, 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("catch {"),
"missing catch header: {high_level}"
);
assert!(
high_level.contains("let loc0 = exception;"),
"catch entry should model exception object stack item for STLOC0: {high_level}"
);
assert!(
!high_level.contains("insufficient values on stack"),
"catch entry STLOC0 should not underflow the stack: {high_level}"
);
}
#[test]
fn malformed_try_out_of_bounds_handlers_keep_braces_balanced() {
let script = [OpCode::Try.byte(), 0x7F, 0x7F, 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,
"malformed try output must stay brace-balanced:\n{high_level}"
);
}