use super::*;
use crate::decompiler::analysis::call_graph::CallTarget;
use crate::instruction::OpCode;
#[test]
fn call_graph_includes_direct_calla_targets_as_methods() {
let nef_bytes = build_nef(&[
OpCode::PushA.byte(),
0x07,
0x00,
0x00,
0x00, OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, OpCode::Ret.byte(), ]);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
assert!(
decompilation
.call_graph
.methods
.iter()
.any(|method| method.offset == 0x0007),
"resolved CALLA target must be listed as a method: {:?}",
decompilation.call_graph.methods
);
let calla = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "CALLA")
.expect("CALLA edge present");
match &calla.target {
CallTarget::Internal { method } => assert_eq!(method.offset, 0x0007),
other => panic!("CALLA target should be internal, got {other:?}"),
}
let syscall = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "SYSCALL")
.expect("SYSCALL edge present");
assert_eq!(syscall.caller.offset, 0x0007);
}
#[test]
fn call_graph_attributes_helper_syscall_to_inferred_helper_method() {
let script = [
OpCode::Call.byte(),
0x04, OpCode::Ret.byte(), OpCode::Nop.byte(), OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
assert_eq!(decompilation.call_graph.edges.len(), 2);
let helper_edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "SYSCALL")
.expect("helper syscall edge");
assert_eq!(helper_edge.call_offset, 4);
assert_eq!(helper_edge.caller.offset, 4);
assert_eq!(helper_edge.caller.name, "sub_0x0004");
}
#[test]
fn call_graph_attributes_pusha_calla_helper_syscall_to_inferred_helper_method() {
let script = [
OpCode::PushA.byte(),
0x08,
0x00,
0x00,
0x00, OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Nop.byte(), OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let helper_edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "SYSCALL")
.expect("helper syscall edge");
assert_eq!(helper_edge.call_offset, 8);
assert_eq!(helper_edge.caller.offset, 8);
assert_eq!(helper_edge.caller.name, "sub_0x0008");
}
#[test]
fn call_graph_attributes_ldarg_calla_helper_syscall_to_inferred_helper_method() {
let script = [
OpCode::PushA.byte(),
0x0F,
0x00,
0x00,
0x00, OpCode::Call.byte(),
0x04, OpCode::Ret.byte(), OpCode::Nop.byte(), OpCode::Initslot.byte(),
0x00,
0x01, OpCode::Ldarg0.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let helper_edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "SYSCALL")
.expect("helper syscall edge");
assert_eq!(helper_edge.call_offset, 0x000F);
assert_eq!(helper_edge.caller.offset, 0x000F);
assert_eq!(helper_edge.caller.name, "sub_0x000F");
}
#[test]
fn call_graph_attributes_ldloc_from_argument_calla_helper_syscall_to_inferred_helper_method() {
let script = [
OpCode::PushA.byte(),
0x11,
0x00,
0x00,
0x00, OpCode::Call.byte(),
0x04, OpCode::Ret.byte(), OpCode::Nop.byte(), OpCode::Initslot.byte(),
0x01,
0x01, OpCode::Ldarg0.byte(), OpCode::Stloc0.byte(), OpCode::Ldloc0.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let helper_edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "SYSCALL")
.expect("helper syscall edge");
assert_eq!(helper_edge.call_offset, 0x0011);
assert_eq!(helper_edge.caller.offset, 0x0011);
assert_eq!(helper_edge.caller.name, "sub_0x0011");
}
#[test]
fn call_graph_resolves_nested_pusha_argument_through_calla_helper() {
let script = [
OpCode::PushA.byte(),
0x12,
0x00,
0x00,
0x00, OpCode::PushA.byte(),
0x07,
0x00,
0x00,
0x00, OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Initslot.byte(),
0x00,
0x01, OpCode::Ldarg0.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let nested_calla = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "CALLA" && edge.call_offset == 0x0010)
.expect("nested CALLA edge present");
match &nested_calla.target {
CallTarget::Internal { method } => {
assert_eq!(method.offset, 0x0012);
assert_eq!(method.name, "sub_0x0012");
}
other => panic!("expected nested CALLA to resolve helper target, got: {other:?}"),
}
let helper_edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "SYSCALL")
.expect("helper syscall edge");
assert_eq!(helper_edge.call_offset, 0x0012);
assert_eq!(helper_edge.caller.offset, 0x0012);
assert_eq!(helper_edge.caller.name, "sub_0x0012");
}
#[test]
fn call_graph_resolves_nested_pusha_argument_through_calla_helper_without_initslot() {
let script = [
OpCode::PushA.byte(),
0x0B,
0x00,
0x00,
0x00, OpCode::Call.byte(),
0x03, OpCode::Ret.byte(), OpCode::Ldarg0.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let nested_calla = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "CALLA" && edge.call_offset == 0x0009)
.expect("nested CALLA edge present");
match &nested_calla.target {
CallTarget::Internal { method } => {
assert_eq!(method.offset, 0x000B);
assert_eq!(method.name, "sub_0x000B");
}
other => panic!("expected CALLA without INITSLOT helper to resolve target, got: {other:?}"),
}
}
#[test]
fn call_graph_resolves_two_level_nested_calla_argument_chain() {
let script = [
OpCode::PushA.byte(),
0x14,
0x00,
0x00,
0x00, OpCode::PushA.byte(),
0x15,
0x00,
0x00,
0x00, OpCode::Call.byte(),
0x03, OpCode::Ret.byte(), OpCode::Initslot.byte(),
0x00,
0x02, OpCode::Ldarg0.byte(), OpCode::Ldarg1.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Initslot.byte(),
0x00,
0x01, OpCode::Ldarg0.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let nested_calla = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "CALLA" && edge.call_offset == 0x0018)
.expect("second-level CALLA edge present");
match &nested_calla.target {
CallTarget::Internal { method } => {
assert_eq!(method.offset, 0x001A);
assert_eq!(method.name, "sub_0x001A");
}
other => panic!("expected second-level CALLA to resolve helper target, got: {other:?}"),
}
let helper_edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "SYSCALL")
.expect("helper syscall edge");
assert_eq!(helper_edge.call_offset, 0x001A);
assert_eq!(helper_edge.caller.offset, 0x001A);
assert_eq!(helper_edge.caller.name, "sub_0x001A");
}
#[test]
fn inferred_method_starts_tolerate_malformed_tryl_operand() {
use crate::instruction::{Instruction, OpCode, Operand};
let instructions = vec![
Instruction::new(
0,
OpCode::TryL,
Some(Operand::Bytes(vec![0x01, 0x02, 0x03])),
),
Instruction::new(1, OpCode::Ret, None),
];
let inferred = crate::decompiler::helpers::inferred_method_starts(&instructions, None);
assert_eq!(inferred, vec![0]);
}
#[test]
fn decompilation_resolves_pickitem_delegate_array_into_calla_edge() {
let script = [
OpCode::Newarray0.byte(), OpCode::Stloc0.byte(), OpCode::Ldloc0.byte(), OpCode::PushA.byte(),
0x0B,
0x00,
0x00,
0x00, OpCode::Append.byte(), OpCode::Ldloc0.byte(), OpCode::Push0.byte(), OpCode::Pickitem.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Initslot.byte(),
0x00,
0x00, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "CALLA")
.expect("CALLA edge present");
match &edge.target {
CallTarget::Internal { method } => {
assert_eq!(method.offset, 0x000E);
assert_eq!(method.name, "sub_0x000E");
}
other => {
panic!("expected PICKITEM delegate CALLA to resolve helper target, got: {other:?}")
}
}
}
#[test]
fn decompilation_resolves_pickitem_delegate_array_through_local_alias() {
let script = [
OpCode::Newarray0.byte(), OpCode::Stloc0.byte(), OpCode::Ldloc0.byte(), OpCode::Dup.byte(), OpCode::Stloc1.byte(), OpCode::PushA.byte(),
0x0D,
0x00,
0x00,
0x00, OpCode::Append.byte(), OpCode::Ldloc1.byte(), OpCode::Push0.byte(), OpCode::Pickitem.byte(), OpCode::Stloc2.byte(), OpCode::Ldloc2.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Initslot.byte(),
0x00,
0x00, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "CALLA")
.expect("CALLA edge present");
match &edge.target {
CallTarget::Internal { method } => {
assert_eq!(method.offset, 0x0012);
assert_eq!(method.name, "sub_0x0012");
}
other => {
panic!("expected aliased delegate-array CALLA to resolve helper target, got: {other:?}")
}
}
}
#[test]
fn decompilation_resolves_duplicated_pointer_into_calla_edge() {
let script = [
OpCode::PushA.byte(),
0x08,
0x00,
0x00,
0x00, OpCode::Dup.byte(), OpCode::CallA.byte(), OpCode::Ret.byte(), OpCode::Initslot.byte(),
0x00,
0x00, OpCode::Ret.byte(), ];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::Pseudocode)
.expect("decompile succeeds");
let edge = decompilation
.call_graph
.edges
.iter()
.find(|edge| edge.opcode == "CALLA")
.expect("CALLA edge present");
match &edge.target {
CallTarget::Internal { method } => {
assert_eq!(method.offset, 0x0008);
assert_eq!(method.name, "sub_0x0008");
}
other => panic!("expected DUP-fed CALLA target to resolve, got: {other:?}"),
}
}