use isa_lang::x86_64::{DecodeError, decode, decode_all, disassemble, encode};
use proptest::prelude::*;
fn check_bytes(bytes: &[u8]) -> Result<usize, DecodeError> {
match decode(bytes) {
Ok(d) => {
assert!(
d.len() >= 1 && d.len() <= bytes.len().min(15),
"{bytes:02x?}"
);
let inst = d.inst();
let _ = inst.to_string();
let _ = inst.display_at(u64::MAX).to_string();
let again = encode(&inst).unwrap_or_else(|e| panic!("{bytes:02x?} -> {inst}: {e}"));
let back = decode(&again).unwrap_or_else(|e| panic!("{inst} -> {again:?}: {e}"));
assert_eq!(back.inst(), inst, "{bytes:02x?} -> {again:?}");
for cut in 0..d.len() {
match decode(&bytes[..cut]) {
Err(DecodeError::Truncated { at }) => assert!(at <= cut, "{bytes:02x?}"),
other => panic!("{:02x?} cut at {cut}: {other:?}", &bytes[..cut]),
}
}
Ok(d.len())
}
Err(e) => {
assert!(e.at() <= bytes.len(), "{bytes:02x?}: {e:?}");
let _ = e.to_string();
Err(e)
}
}
}
const TAILS: [[u8; 12]; 3] = [
[
0x24, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
],
[
0x25, 0x80, 0xFF, 0xFF, 0x7F, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
],
[
0xE5, 0x7F, 0x80, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
],
];
#[test]
fn exhaustive_prefix_opcode_modrm_space() {
let mut prefixes: Vec<Vec<u8>> = vec![vec![], vec![0x66]];
for rex in 0x40..=0x4Fu8 {
prefixes.push(vec![rex]);
prefixes.push(vec![0x66, rex]);
}
let mut accepted = 0usize;
let mut buf = Vec::with_capacity(32);
for prefix in &prefixes {
for escape in [false, true] {
for opcode in 0..=255u8 {
for modrm in 0..=255u8 {
for tail in &TAILS {
buf.clear();
buf.extend_from_slice(prefix);
if escape {
buf.push(0x0F);
}
buf.push(opcode);
buf.push(modrm);
buf.extend_from_slice(tail);
if check_bytes(&buf).is_ok() {
accepted += 1;
}
}
}
}
}
}
assert!(accepted > 300_000, "only {accepted} accepted");
}
#[test]
fn every_one_and_two_byte_input() {
for a in 0..=255u8 {
let _ = check_bytes(&[a]);
for b in 0..=255u8 {
let _ = check_bytes(&[a, b]);
}
}
}
#[test]
fn empty_input_is_truncated_at_zero() {
assert_eq!(decode(&[]), Err(DecodeError::Truncated { at: 0 }));
assert_eq!(decode_all(&[]).count(), 0);
assert_eq!(disassemble(&[], 0).to_string(), "");
}
#[test]
fn long_prefix_runs_are_rejected_at_the_first_bad_byte() {
let mut bytes = vec![0x66; 1000];
bytes.push(0x90);
assert_eq!(decode(&bytes), Err(DecodeError::RepeatedPrefix { at: 1 }));
let rex_run = [0x48; 64];
assert_eq!(decode(&rex_run), Err(DecodeError::MisplacedRex { at: 0 }));
}
proptest! {
#![proptest_config(ProptestConfig { cases: 50_000, ..ProptestConfig::default() })]
#[test]
fn arbitrary_bytes_never_panic(bytes in proptest::collection::vec(any::<u8>(), 0..24)) {
let _ = check_bytes(&bytes);
}
#[test]
fn decode_all_tiles_the_input(bytes in proptest::collection::vec(any::<u8>(), 0..256)) {
let mut next = 0;
for item in decode_all(&bytes) {
match item {
Ok((at, d)) => {
prop_assert_eq!(at, next);
next = at + d.len();
}
Err(e) => {
prop_assert!(e.at() >= next && e.at() <= bytes.len());
next = bytes.len();
}
}
}
prop_assert!(next <= bytes.len());
let listing = disassemble(&bytes, 0x1000).to_string();
prop_assert_eq!(listing.is_empty(), bytes.is_empty());
}
}