use super::*;
use crate::instruction::{OpCode, Operand};
#[test]
fn decodes_simple_sequence() {
let bytecode = [
OpCode::Push0.byte(),
OpCode::Push1.byte(),
OpCode::Add.byte(),
OpCode::Ret.byte(),
];
let instructions = Disassembler::new()
.disassemble(&bytecode)
.expect("disassembly succeeds");
let mnemonics: Vec<_> = instructions
.iter()
.map(|ins| ins.opcode.mnemonic())
.collect();
assert_eq!(mnemonics, vec!["PUSH0", "PUSH1", "ADD", "RET"]);
}
#[test]
fn errors_on_unknown_opcode() {
let bytecode = [0xFF];
let err = Disassembler::with_unknown_handling(UnknownHandling::Error)
.disassemble(&bytecode)
.unwrap_err();
assert!(matches!(
err,
crate::error::Error::Disassembly(DisassemblyError::UnknownOpcode {
opcode: 0xFF,
offset: 0
})
));
}
#[test]
fn permits_unknown_opcode_when_configured() {
let bytecode = [0xFF, OpCode::Ret.byte()];
let instructions = Disassembler::with_unknown_handling(UnknownHandling::Permit)
.disassemble(&bytecode)
.expect("disassembly succeeds in tolerant mode");
assert_eq!(instructions.len(), 2);
assert!(matches!(instructions[0].opcode, OpCode::Unknown(0xFF)));
assert_eq!(instructions[1].opcode, OpCode::Ret);
}
#[test]
fn reports_warning_for_unknown_opcode_in_tolerant_mode() {
let bytecode = [0xFF, OpCode::Ret.byte()];
let output = Disassembler::with_unknown_handling(UnknownHandling::Permit)
.disassemble_with_warnings(&bytecode)
.expect("disassembly succeeds in tolerant mode");
assert_eq!(output.instructions.len(), 2);
assert_eq!(output.warnings.len(), 1);
assert!(matches!(
output.warnings[0],
DisassemblyWarning::UnknownOpcode {
opcode: 0xFF,
offset: 0
}
));
}
#[test]
fn fails_on_truncated_operand() {
let bytecode = [OpCode::Pushint16.byte(), 0x00];
let err = Disassembler::new().disassemble(&bytecode).unwrap_err();
assert!(matches!(
err,
crate::error::Error::Disassembly(DisassemblyError::UnexpectedEof { offset: 0 })
));
}
#[test]
fn decodes_calla_no_operand() {
let bytecode = [OpCode::CallA.byte()];
let instructions = Disassembler::new()
.disassemble(&bytecode)
.expect("disassembly succeeds");
assert_eq!(instructions.len(), 1);
assert_eq!(instructions[0].opcode, OpCode::CallA);
assert_eq!(instructions[0].operand, None);
}
#[test]
fn decodes_pushdata2() {
let bytecode = [OpCode::Pushdata2.byte(), 0x04, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
let instruction = Disassembler::new().disassemble(&bytecode).expect("success")[0].clone();
assert_eq!(instruction.opcode.mnemonic(), "PUSHDATA2");
assert_eq!(instruction.offset, 0);
assert_eq!(
instruction.operand,
Some(Operand::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]))
);
assert_eq!(instruction.opcode, OpCode::Pushdata2);
}
#[test]
fn decodes_jump_long() {
let bytecode = [OpCode::Jmp_L.byte(), 0x34, 0x12, 0x00, 0x00];
let instruction = Disassembler::new().disassemble(&bytecode).expect("success")[0].clone();
assert_eq!(instruction.opcode, OpCode::Jmp_L);
assert_eq!(instruction.operand, Some(Operand::Jump32(0x1234)));
assert_eq!(instruction.offset, 0);
}
#[test]
fn decodes_pusha_backward_offset_as_signed_i32() {
let bytecode = [OpCode::PushA.byte(), 0xA6, 0xFF, 0xFF, 0xFF];
let instruction = Disassembler::new().disassemble(&bytecode).expect("success")[0].clone();
assert_eq!(instruction.opcode, OpCode::PushA);
assert_eq!(instruction.operand, Some(Operand::I32(-90)));
assert_eq!(instruction.operand.expect("operand").to_string(), "-90");
}
#[test]
fn decodes_pusha_forward_offset_as_signed_i32() {
let bytecode = [
OpCode::PushA.byte(),
0x05,
0x00,
0x00,
0x00,
OpCode::Ret.byte(),
];
let instruction = Disassembler::new().disassemble(&bytecode).expect("success")[0].clone();
assert_eq!(instruction.opcode, OpCode::PushA);
assert_eq!(instruction.operand, Some(Operand::I32(5)));
assert_eq!(instruction.operand.expect("operand").to_string(), "5");
}
#[test]
fn pusha_truncated_operand_returns_unexpected_eof() {
let bytecode = [OpCode::PushA.byte(), 0xA6, 0xFF, 0xFF];
let err = Disassembler::new().disassemble(&bytecode).unwrap_err();
assert!(matches!(
err,
crate::error::Error::Disassembly(DisassemblyError::UnexpectedEof { offset: 0 })
));
}
#[test]
fn pushdata2_truncated_length_prefix_returns_unexpected_eof() {
let bytecode = [OpCode::Pushdata2.byte(), 0x10];
let err = Disassembler::new().disassemble(&bytecode).unwrap_err();
assert!(matches!(
err,
crate::error::Error::Disassembly(DisassemblyError::UnexpectedEof { offset: 0 })
));
}
#[test]
fn pushdata4_truncated_length_prefix_returns_unexpected_eof() {
let bytecode = [OpCode::Pushdata4.byte(), 0xFF, 0xFF, 0xFF];
let err = Disassembler::new().disassemble(&bytecode).unwrap_err();
assert!(matches!(
err,
crate::error::Error::Disassembly(DisassemblyError::UnexpectedEof { offset: 0 })
));
}
#[test]
fn decodes_syscall_operand_with_name() {
let bytecode = [OpCode::Syscall.byte(), 0xB2, 0x79, 0xFC, 0xF6];
let instruction = Disassembler::new().disassemble(&bytecode).expect("success")[0].clone();
assert_eq!(instruction.opcode, OpCode::Syscall);
let operand = instruction.operand.expect("syscall operand");
assert_eq!(operand.to_string(), "System.Runtime.Platform (0xF6FC79B2)");
}
#[test]
fn pushdata4_excessive_length_returns_operand_too_large() {
let bytecode = [OpCode::Pushdata4.byte(), 0x01, 0x00, 0x10, 0x00];
let err = Disassembler::new().disassemble(&bytecode).unwrap_err();
assert!(matches!(
err,
crate::error::Error::Disassembly(DisassemblyError::OperandTooLarge {
offset: 0,
len: 1_048_577
})
));
}
#[test]
fn pushdata4_truncated_payload_returns_unexpected_eof() {
let bytecode = [OpCode::Pushdata4.byte(), 0x04, 0x00, 0x00, 0x00, 0xAA, 0xBB];
let err = Disassembler::new().disassemble(&bytecode).unwrap_err();
assert!(matches!(
err,
crate::error::Error::Disassembly(DisassemblyError::UnexpectedEof { offset: 0 })
));
}