use super::super::*;
use crate::instruction::OpCode;
#[test]
fn high_level_packs_literal_arrays() {
let script = [
OpCode::Push1.byte(),
OpCode::Push2.byte(),
OpCode::Push2.byte(),
OpCode::Pack.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("pack 2 element"),
"expected literal PACK to be lifted: {high_level}"
);
assert!(
!high_level.contains("pack_dynamic"),
"literal PACK should not fall back to dynamic form: {high_level}"
);
}
#[test]
fn high_level_rewrites_pickitem_as_indexing() {
let script = [
OpCode::Newarray0.byte(),
OpCode::Push0.byte(),
OpCode::Pickitem.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("t0[t1]"),
"expected PICKITEM to be rewritten as indexing: {high_level}"
);
assert!(
!high_level.contains(" get "),
"infix get should not appear after rewrite: {high_level}"
);
}
#[test]
fn high_level_rewrites_setitem_as_index_assignment() {
let script = [
OpCode::Newmap.byte(),
OpCode::Push0.byte(),
OpCode::Push1.byte(),
OpCode::Setitem.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("t0[t1] = t2;"),
"expected SETITEM to be rewritten as indexing assignment: {high_level}"
);
assert!(
!high_level.contains("set_item("),
"set_item helper should not appear after rewrite: {high_level}"
);
}
#[test]
fn high_level_rewrites_haskey_as_function_call() {
let script = [
OpCode::Newmap.byte(),
OpCode::Push0.byte(),
OpCode::Haskey.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.with_inline_single_use_temps(true)
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
high_level.contains("has_key("),
"expected HASKEY to be rewritten as function call: {high_level}"
);
assert!(
!high_level.contains(" has_key "),
"infix has_key should not appear after rewrite: {high_level}"
);
assert!(
!high_level.contains("has_key(return"),
"return must not be nested inside the has_key call: {high_level}"
);
assert!(
high_level.contains("return has_key("),
"HASKEY result should flow into the return: {high_level}"
);
}
#[test]
fn high_level_pickitem_inside_call_keeps_brackets_balanced() {
let script = [
OpCode::Push1.byte(),
OpCode::Push1.byte(),
OpCode::Pack.byte(),
OpCode::Push0.byte(),
OpCode::Pickitem.byte(),
OpCode::Size.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let high_level = Decompiler::new()
.with_inline_single_use_temps(true)
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds")
.high_level
.expect("high-level output");
assert!(
!high_level.contains("[0)]") && !high_level.contains(" get "),
"index access inside a call must not interleave `)` and `]`: {high_level}"
);
assert!(
high_level.contains("len(") && high_level.contains("[0])"),
"PICKITEM result inside len() should render as len(arr[0]): {high_level}"
);
}
#[test]
fn high_level_istype_respects_operand_tag() {
let script = [
OpCode::Push1.byte(),
OpCode::Istype.byte(),
0x40, 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("is_type_array("),
"ISTYPE should map to a helper named for the operand tag: {high_level}"
);
assert!(
!high_level.contains("is_type("),
"High-level output should not use the two-argument shorthand: {high_level}"
);
}
#[test]
fn pack_literal_underflow_renders_elision_marker_not_synthetic_temps() {
let script = [
OpCode::Push1.byte(),
OpCode::Push1.byte(),
OpCode::Push1.byte(),
OpCode::Push5.byte(),
OpCode::Pack.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("/* 2 more elements */"),
"underflowing PACK should render an elision marker: {high_level}"
);
assert!(
!high_level.contains("missing_pack_item"),
"underflowing PACK should not synthesize missing_pack_item temps: {high_level}"
);
}