use super::*;
use crate::instruction::{OpCode, OperandEncoding};
#[test]
fn high_level_dispatch_handles_every_known_opcode_without_generic_placeholder() {
for opcode in OpCode::all_known() {
let script = opcode_matrix_script(opcode);
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.unwrap_or_else(|err| panic!("decompile should accept {opcode}: {err}"));
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
!high_level.contains("not yet translated"),
"{opcode} should be handled by high-level dispatch, got:\n{high_level}"
);
}
}
fn opcode_matrix_script(opcode: OpCode) -> Vec<u8> {
let mut script = Vec::new();
script.extend_from_slice(&opcode_stack_setup(opcode));
script.push(opcode.byte());
script.extend_from_slice(&sample_operand_bytes(opcode.operand_encoding()));
if opcode != OpCode::Ret {
script.push(OpCode::Ret.byte());
}
script
}
fn opcode_stack_setup(opcode: OpCode) -> Vec<u8> {
use OpCode::*;
match opcode {
Assert | Throw | Abortmsg | Newarray | NewarrayT | Newbuffer | Newstruct | Size | Keys
| Values | Isnull | Istype | Convert | Xdrop | Pick | Roll | Reversen => {
vec![Push1.byte()]
}
Assertmsg | Cat | Substr | Left | Right | And | Or | Xor | Equal | Notequal | Add | Sub
| Mul | Div | Mod | Pow | Shl | Shr | Booland | Boolor | Numequal | Numnotequal | Lt
| Le | Gt | Ge | Min | Max | Haskey | Pickitem | Append | Remove | Popitem | Jmpif
| Jmpif_L | Jmpifnot | Jmpifnot_L => vec![Push1.byte(), Push2.byte()],
Modmul | Modpow | Within | Setitem => vec![Push1.byte(), Push2.byte(), Push3.byte()],
Memcpy => vec![
Push1.byte(),
Push2.byte(),
Push3.byte(),
Push4.byte(),
Push5.byte(),
],
Pack | Packmap | Packstruct => vec![Push1.byte(), Push2.byte(), Push2.byte()],
Unpack => vec![Push2.byte(), Push3.byte(), Push2.byte(), Pack.byte()],
Reverse3 => vec![Push1.byte(), Push2.byte(), Push3.byte()],
Reverse4 => vec![Push1.byte(), Push2.byte(), Push3.byte(), Push4.byte()],
Nip | Over | Swap | Tuck => vec![Push1.byte(), Push2.byte()],
Rot => vec![Push1.byte(), Push2.byte(), Push3.byte()],
CallA => vec![PushA.byte(), 0x02, 0x00, 0x00, 0x00],
JmpEq | JmpEq_L | JmpNe | JmpNe_L | JmpGt | JmpGt_L | JmpGe | JmpGe_L | JmpLt | JmpLt_L
| JmpLe | JmpLe_L => vec![Push1.byte(), Push2.byte()],
Stloc0 | Stloc1 | Stloc2 | Stloc3 | Stloc4 | Stloc5 | Stloc6 | Stloc | Starg0 | Starg1
| Starg2 | Starg3 | Starg4 | Starg5 | Starg6 | Starg | Stsfld0 | Stsfld1 | Stsfld2
| Stsfld3 | Stsfld4 | Stsfld5 | Stsfld6 | Stsfld => vec![Push1.byte()],
_ => Vec::new(),
}
}
fn sample_operand_bytes(encoding: OperandEncoding) -> Vec<u8> {
match encoding {
OperandEncoding::None => Vec::new(),
OperandEncoding::I8 => vec![1],
OperandEncoding::I16 => 1i16.to_le_bytes().to_vec(),
OperandEncoding::I32 => 1i32.to_le_bytes().to_vec(),
OperandEncoding::I64 => 1i64.to_le_bytes().to_vec(),
OperandEncoding::Bytes(len) => vec![0; len],
OperandEncoding::Data1 => vec![1, b'a'],
OperandEncoding::Data2 => {
let mut bytes = 1u16.to_le_bytes().to_vec();
bytes.push(b'a');
bytes
}
OperandEncoding::Data4 => {
let mut bytes = 1u32.to_le_bytes().to_vec();
bytes.push(b'a');
bytes
}
OperandEncoding::Jump8 => vec![1],
OperandEncoding::Jump32 => 1i32.to_le_bytes().to_vec(),
OperandEncoding::U8 => vec![0],
OperandEncoding::U16 => 0u16.to_le_bytes().to_vec(),
OperandEncoding::U32 => 1u32.to_le_bytes().to_vec(),
OperandEncoding::Syscall => 0u32.to_le_bytes().to_vec(),
}
}