use super::*;
use crate::instruction::OpCode;
#[test]
fn decompile_lifts_indirect_calls_without_not_yet_translated_warning() {
let nef_bytes = build_nef(&[
OpCode::CallA.byte(),
OpCode::CallT.byte(),
0x01,
0x00,
OpCode::Ret.byte(),
]);
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("calla("),
"CALLA should be lifted to an indirect-call statement: {high_level}"
);
assert!(
high_level.contains("callt(0x0001)"),
"CALLT should be lifted to an indirect-call statement: {high_level}"
);
assert!(
!high_level.contains("not yet translated"),
"indirect calls should no longer emit not-yet-translated placeholders: {high_level}"
);
}
#[test]
fn decompile_uses_method_token_signature_for_callt_arguments_and_returns() {
let nef_bytes = build_nef_with_single_token(
&[
OpCode::Push1.byte(),
OpCode::CallT.byte(),
0x00,
0x00,
OpCode::Ret.byte(),
],
[0u8; 20],
"foo",
1,
false,
0x0F,
);
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("foo(t0);"),
"CALLT should consume declared token argument and emit call expression: {high_level}"
);
assert!(
!high_level.contains("let t1 = foo("),
"CALLT token marked non-returning should not push a synthetic return temp: {high_level}"
);
assert!(
high_level.contains("return;"),
"script entry should end with a bare return after non-returning CALLT: {high_level}"
);
}
#[test]
fn decompile_lifts_relative_calls_without_control_flow_warning() {
let nef_bytes = build_nef(&[
OpCode::Call.byte(),
0x02,
OpCode::Call_L.byte(),
0x05,
0x00,
0x00,
0x00,
OpCode::Ret.byte(),
]);
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("sub_0x0002()"),
"CALL should resolve to inferred method name: {high_level}"
);
assert!(
high_level.contains("sub_0x0007()"),
"CALL_L should resolve to inferred method name: {high_level}"
);
assert!(
!high_level.contains("control flow not yet lifted"),
"relative calls should no longer use control-flow-not-lifted warnings: {high_level}"
);
}
#[test]
fn decompile_resolves_relative_call_target_to_inferred_method_name() {
let nef_bytes = build_nef(&[
OpCode::Call.byte(),
0x05, OpCode::Ret.byte(), OpCode::Nop.byte(),
OpCode::Nop.byte(), OpCode::Initslot.byte(),
0x00,
0x00, OpCode::Ret.byte(), ]);
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("sub_0x0005()"),
"relative CALL should use inferred callee name when target matches method start: {high_level}"
);
}
#[test]
fn decompile_relative_call_passes_known_method_arguments() {
let nef_bytes = build_nef(&[
OpCode::Push1.byte(), OpCode::Call.byte(),
0x07, OpCode::Ret.byte(), OpCode::Nop.byte(),
OpCode::Nop.byte(),
OpCode::Nop.byte(),
OpCode::Nop.byte(), OpCode::Initslot.byte(),
0x00,
0x01, OpCode::Ldarg0.byte(), OpCode::Ret.byte(), ]);
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("sub_0x0008("),
"relative CALL should target inferred method call syntax: {high_level}"
);
assert!(
!high_level.contains("sub_0x0008()"),
"relative CALL into one-arg method should pass argument expression: {high_level}"
);
}
#[test]
fn decompile_calla_with_stack_setup() {
let nef_bytes = build_nef(&[
OpCode::Push1.byte(),
OpCode::Push0.byte(),
OpCode::CallA.byte(),
OpCode::Ret.byte(),
]);
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("calla("),
"CALLA should produce an indirect call expression: {high_level}"
);
}
#[test]
fn decompile_resolves_pusha_calla_to_internal_call_placeholder() {
let nef_bytes = build_nef(&[
OpCode::PushA.byte(),
0x0A,
0x00,
0x00,
0x00, OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Nop.byte(),
OpCode::Nop.byte(),
OpCode::Nop.byte(), OpCode::Initslot.byte(),
0x00,
0x00, OpCode::Ret.byte(), ]);
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("sub_0x000A()"),
"PUSHA+CALLA should resolve to the inferred method name when available: {high_level}"
);
assert!(
!high_level.contains("calla("),
"resolved PUSHA+CALLA should not remain as generic indirect call: {high_level}"
);
}
#[test]
fn decompile_resolves_local_pointer_flow_into_calla() {
let nef_bytes = build_nef(&[
OpCode::PushA.byte(),
0x09,
0x00,
0x00,
0x00, OpCode::Stloc0.byte(), OpCode::Ldloc0.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Initslot.byte(),
0x00,
0x00, OpCode::Ret.byte(), ]);
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("sub_0x0009()"),
"local pointer flow should resolve CALLA to inferred method name: {high_level}"
);
assert!(
!high_level.contains("calla(loc0)"),
"resolved local pointer flow should not remain generic CALLA: {high_level}"
);
}
#[test]
fn decompile_resolves_static_pointer_flow_into_calla() {
let nef_bytes = build_nef(&[
OpCode::PushA.byte(),
0x09,
0x00,
0x00,
0x00, OpCode::Stsfld0.byte(), OpCode::Ldsfld0.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Initslot.byte(),
0x00,
0x00, OpCode::Ret.byte(), ]);
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("sub_0x0009()"),
"static pointer flow should resolve CALLA to inferred method name: {high_level}"
);
assert!(
!high_level.contains("calla(static0)"),
"resolved static pointer flow should not remain generic CALLA: {high_level}"
);
}
#[test]
fn decompile_multiple_sequential_calls() {
let nef_bytes = build_nef(&[
OpCode::Call.byte(),
0x02,
OpCode::Call.byte(),
0x00,
OpCode::Ret.byte(),
OpCode::Ret.byte(),
]);
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("sub_0x") || high_level.contains("call_"),
"sequential CALL instructions should each produce a call expression: {high_level}"
);
}