use rulefire::vm::bytecode::{Instruction, Opcode, Program, BYTECODE_MAGIC};
fn bytes_of(program: &[Instruction]) -> Vec<u8> {
Program {
instructions: program.to_vec(),
}
.to_bytes()
}
fn serialized_roundtrip(program: &[Instruction]) -> Program {
let bytes = bytes_of(program);
Program::from_bytes(&bytes).expect("roundtrip")
}
macro_rules! bytecode_case {
($name:ident, $source:expr, $expected:expr) => {
#[test]
fn $name() {
let actual = $source;
let expected: Option<Vec<u8>> = $expected;
if let Some(expected) = expected {
assert_eq!(bytes_of(&actual), expected);
} else {
let round_trip = serialized_roundtrip(&actual);
assert_eq!(round_trip.instructions, actual);
}
}
};
}
macro_rules! bytecode_roundtrip_case {
($name:ident, $opcode:expr, $operand:expr) => {
#[test]
fn $name() {
let program = serialized_roundtrip(&[
Instruction::new($opcode, $operand),
Instruction::new(Opcode::Halt, 0),
]);
assert_eq!(program.instructions.len(), 2);
assert_eq!(program.instructions[0], Instruction::new($opcode, $operand));
}
};
}
#[test]
fn bytecode_to_bytes_has_magic_and_count() {
let program = serialized_roundtrip(&[
Instruction::new(Opcode::PushImmediate, 0xAA),
Instruction::new(Opcode::Halt, 0),
]);
let bytes = program.to_bytes();
assert_eq!(bytes[..4], BYTECODE_MAGIC);
assert_eq!(&bytes[4..8], &(2u32).to_le_bytes());
assert_eq!(bytes.len(), 24);
}
bytecode_case!(bytecode_roundtrip_context_bundle, vec![
Instruction::new(Opcode::PushFileSize, 0),
Instruction::new(Opcode::PushEntropy, 0),
Instruction::new(Opcode::PushIsPe, 0),
Instruction::new(Opcode::PushIsDll, 0),
Instruction::new(Opcode::PushNumSections, 0),
Instruction::new(Opcode::PushNumImports, 0),
Instruction::new(Opcode::PushEntryPoint, 0),
Instruction::new(Opcode::PushHasSignature, 0),
Instruction::new(Opcode::PushMagicU32, 0),
Instruction::new(Opcode::PushIs64bit, 0),
Instruction::new(Opcode::PushNumStrings, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_string_bundle, vec![
Instruction::new(Opcode::PushStringMatched, 0),
Instruction::new(Opcode::PushStringCount, 1),
Instruction::new(Opcode::PushStringOffset, 2),
Instruction::new(Opcode::PushStringLength, 3),
Instruction::new(Opcode::And, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_logic_bundle, vec![
Instruction::new(Opcode::PushImmediate, 1),
Instruction::new(Opcode::PushImmediate, 0),
Instruction::new(Opcode::Eq, 0),
Instruction::new(Opcode::Not, 0),
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::Or, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_arith_bundle, vec![
Instruction::new(Opcode::PushImmediate, 2),
Instruction::new(Opcode::PushImmediate, 4),
Instruction::new(Opcode::Add, 0),
Instruction::new(Opcode::PushImmediate, 2),
Instruction::new(Opcode::Sub, 0),
Instruction::new(Opcode::PushImmediate, 3),
Instruction::new(Opcode::Mul, 0),
Instruction::new(Opcode::PushImmediate, 2),
Instruction::new(Opcode::Div, 0),
Instruction::new(Opcode::PushImmediate, 1),
Instruction::new(Opcode::Mod, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_bit_bundle, vec![
Instruction::new(Opcode::PushImmediate, 0xF0),
Instruction::new(Opcode::PushImmediate, 0x0F),
Instruction::new(Opcode::BitAnd, 0),
Instruction::new(Opcode::PushImmediate, 0x33),
Instruction::new(Opcode::BitOr, 0),
Instruction::new(Opcode::PushImmediate, 0x0F),
Instruction::new(Opcode::BitXor, 0),
Instruction::new(Opcode::PushImmediate, 1),
Instruction::new(Opcode::Shl, 0),
Instruction::new(Opcode::PushImmediate, 1),
Instruction::new(Opcode::Shr, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_comparisons, vec![
Instruction::new(Opcode::PushImmediate, 1),
Instruction::new(Opcode::PushImmediate, 2),
Instruction::new(Opcode::Lt, 0),
Instruction::new(Opcode::PushImmediate, 4),
Instruction::new(Opcode::PushImmediate, 3),
Instruction::new(Opcode::Gt, 0),
Instruction::new(Opcode::PushImmediate, 5),
Instruction::new(Opcode::PushImmediate, 5),
Instruction::new(Opcode::Lte, 0),
Instruction::new(Opcode::PushImmediate, 5),
Instruction::new(Opcode::PushImmediate, 5),
Instruction::new(Opcode::Gte, 0),
Instruction::new(Opcode::PushImmediate, 7),
Instruction::new(Opcode::PushImmediate, 8),
Instruction::new(Opcode::Neq, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_reduction_bundle, vec![
Instruction::new(Opcode::PushStringCount, 0),
Instruction::new(Opcode::PushStringCount, 1),
Instruction::new(Opcode::PushStringCount, 2),
Instruction::new(Opcode::CountOf, (2 << 16) | 3),
Instruction::new(Opcode::PushStringCount, 0),
Instruction::new(Opcode::PushStringCount, 1),
Instruction::new(Opcode::AllOf, 2),
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::AnyOf, 1),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_for_any_loop, vec![
Instruction::new(Opcode::PushImmediate, 0),
Instruction::new(Opcode::PushImmediate, 2),
Instruction::new(Opcode::ForAny, (2 << 16) | 2),
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::EndFor, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_for_all_loop, vec![
Instruction::new(Opcode::PushImmediate, 0),
Instruction::new(Opcode::PushImmediate, 1),
Instruction::new(Opcode::ForAll, (1 << 16) | 2),
Instruction::new(Opcode::PushFalse, 0),
Instruction::new(Opcode::EndFor, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_for_n_loop, vec![
Instruction::new(Opcode::PushImmediate, 1),
Instruction::new(Opcode::PushImmediate, 3),
Instruction::new(Opcode::ForN, (1 << 16) | 2),
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::EndFor, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_string_queries, vec![
Instruction::new(Opcode::PushImmediate, 5),
Instruction::new(Opcode::StringAt, 0),
Instruction::new(Opcode::PushImmediate, 5),
Instruction::new(Opcode::PushImmediate, 15),
Instruction::new(Opcode::StringIn, 0),
Instruction::new(Opcode::Halt, 0),
], None);
bytecode_case!(bytecode_roundtrip_read_int_at, vec![
Instruction::new(Opcode::PushImmediate, 0),
Instruction::new(Opcode::ReadIntAt, 2),
Instruction::new(Opcode::PushImmediate, 0x5A4D),
Instruction::new(Opcode::Eq, 0),
Instruction::new(Opcode::Halt, 0),
], None);
#[test]
fn bytecode_rejects_empty_program() {
let err = Program::from_bytes(&bytes_of(&[])).expect_err("expected error");
assert!(err.to_string().contains("program is empty"));
}
#[test]
fn bytecode_rejects_missing_magic() {
let mut bytes = bytes_of(&[
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::Halt, 0),
]);
bytes[0..4].copy_from_slice(&[0, 0, 0, 0]);
let err = Program::from_bytes(&bytes).expect_err("expected error");
assert!(err.to_string().contains("missing bytecode magic header"));
}
#[test]
fn bytecode_rejects_halt_not_last() {
let program = Program {
instructions: vec![
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::Halt, 0),
Instruction::new(Opcode::PushFalse, 0),
],
};
let err = program.validate().expect_err("invalid program");
assert!(err.to_string().contains("HALT must terminate"));
}
#[test]
fn bytecode_rejects_program_without_halt() {
let program = Program {
instructions: vec![Instruction::new(Opcode::PushTrue, 0)],
};
let err = program.validate().expect_err("invalid program");
assert!(err.to_string().contains("program must end with HALT"));
}
#[test]
fn bytecode_rejects_endfor_without_open_loop() {
let program = Program {
instructions: vec![
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::EndFor, 0),
Instruction::new(Opcode::Halt, 0),
],
};
let err = program.validate().expect_err("invalid program");
assert!(err.to_string().contains("END_FOR"));
}
#[test]
fn bytecode_rejects_unterminated_forany() {
let program = Program {
instructions: vec![
Instruction::new(Opcode::PushImmediate, 0),
Instruction::new(Opcode::PushImmediate, 1),
Instruction::new(Opcode::ForAny, (2 << 16) | 1),
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::Halt, 0),
],
};
let err = program.validate().expect_err("invalid program");
assert!(err.to_string().contains("unterminated FOR loop"));
}
#[test]
fn bytecode_rejects_truncated_instruction_count() {
let mut bytes = bytes_of(&[
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::Halt, 0),
]);
bytes.truncate(20);
let err = Program::from_bytes(&bytes).expect_err("expected error");
assert!(err.to_string().contains("bytecode size mismatch"));
}
#[test]
fn bytecode_rejects_extra_payload_bytes() {
let mut bytes = bytes_of(&[Instruction::new(Opcode::PushTrue, 0), Instruction::new(Opcode::Halt, 0)]);
bytes.extend_from_slice(&[0x12, 0x34, 0x56, 0x78]);
let err = Program::from_bytes(&bytes).expect_err("expected error");
assert!(err.to_string().contains("bytecode size mismatch"));
}
#[test]
fn bytecode_rejects_truncated_magic_count_header() {
let bytes = vec![0x59, 0x42, 0x43];
let err = Program::from_bytes(&bytes).expect_err("expected error");
assert!(err.to_string().contains("missing bytecode magic header"));
}
#[test]
fn bytecode_rejects_zero_match_count() {
let bytes = [
BYTECODE_MAGIC.to_vec(),
(0u32).to_le_bytes().to_vec(),
Vec::<u8>::new(),
]
.concat();
let err = Program::from_bytes(&bytes).expect_err("expected error");
assert!(err.to_string().contains("program is empty"));
}
#[test]
fn bytecode_rejects_unknown_opcode() {
let mut bytes = bytes_of(&[
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::Halt, 0),
]);
bytes[8 + 4..12].copy_from_slice(&99u32.to_le_bytes());
let err = Program::from_bytes(&bytes).expect_err("expected error");
assert!(err.to_string().contains("unknown opcode"));
}
#[test]
fn bytecode_rejects_misaligned_payload() {
let mut bytes = bytes_of(&[
Instruction::new(Opcode::PushTrue, 0),
Instruction::new(Opcode::Halt, 0),
]);
bytes.push(0);
let err = Program::from_bytes(&bytes).expect_err("expected error");
assert!(err.to_string().contains("bytecode size mismatch"));
}
bytecode_roundtrip_case!(bytecode_opcode_push_true, Opcode::PushTrue, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_false, Opcode::PushFalse, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_string_matched, Opcode::PushStringMatched, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_string_count, Opcode::PushStringCount, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_string_offset, Opcode::PushStringOffset, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_string_length, Opcode::PushStringLength, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_file_size, Opcode::PushFileSize, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_entry_count, Opcode::PushEntryCount, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_immediate, Opcode::PushImmediate, 0x11223344);
bytecode_roundtrip_case!(bytecode_opcode_push_num_strings, Opcode::PushNumStrings, 0);
bytecode_roundtrip_case!(bytecode_opcode_and, Opcode::And, 0);
bytecode_roundtrip_case!(bytecode_opcode_or, Opcode::Or, 0);
bytecode_roundtrip_case!(bytecode_opcode_not, Opcode::Not, 0);
bytecode_roundtrip_case!(bytecode_opcode_eq, Opcode::Eq, 0);
bytecode_roundtrip_case!(bytecode_opcode_neq, Opcode::Neq, 0);
bytecode_roundtrip_case!(bytecode_opcode_lt, Opcode::Lt, 0);
bytecode_roundtrip_case!(bytecode_opcode_gt, Opcode::Gt, 0);
bytecode_roundtrip_case!(bytecode_opcode_lte, Opcode::Lte, 0);
bytecode_roundtrip_case!(bytecode_opcode_gte, Opcode::Gte, 0);
bytecode_roundtrip_case!(bytecode_opcode_add, Opcode::Add, 0);
bytecode_roundtrip_case!(bytecode_opcode_sub, Opcode::Sub, 0);
bytecode_roundtrip_case!(bytecode_opcode_count_of, Opcode::CountOf, (2 << 16) | 1);
bytecode_roundtrip_case!(bytecode_opcode_all_of, Opcode::AllOf, 2);
bytecode_roundtrip_case!(bytecode_opcode_any_of, Opcode::AnyOf, 3);
bytecode_roundtrip_case!(bytecode_opcode_string_at, Opcode::StringAt, 0);
bytecode_roundtrip_case!(bytecode_opcode_string_in, Opcode::StringIn, 0);
bytecode_roundtrip_case!(bytecode_opcode_for_any, Opcode::ForAny, (1 << 16) | 2);
bytecode_roundtrip_case!(bytecode_opcode_for_all, Opcode::ForAll, (1 << 16) | 2);
bytecode_roundtrip_case!(bytecode_opcode_end_for, Opcode::EndFor, 0);
bytecode_roundtrip_case!(bytecode_opcode_halt, Opcode::Halt, 0);
bytecode_roundtrip_case!(bytecode_opcode_mul, Opcode::Mul, 0);
bytecode_roundtrip_case!(bytecode_opcode_div, Opcode::Div, 0);
bytecode_roundtrip_case!(bytecode_opcode_mod, Opcode::Mod, 0);
bytecode_roundtrip_case!(bytecode_opcode_bit_and, Opcode::BitAnd, 0);
bytecode_roundtrip_case!(bytecode_opcode_bit_or, Opcode::BitOr, 0);
bytecode_roundtrip_case!(bytecode_opcode_bit_xor, Opcode::BitXor, 0);
bytecode_roundtrip_case!(bytecode_opcode_shl, Opcode::Shl, 0);
bytecode_roundtrip_case!(bytecode_opcode_shr, Opcode::Shr, 0);
bytecode_roundtrip_case!(bytecode_opcode_read_int_at, Opcode::ReadIntAt, 2);
bytecode_roundtrip_case!(bytecode_opcode_for_n, Opcode::ForN, (2 << 16) | 1);
bytecode_roundtrip_case!(bytecode_opcode_push_entropy, Opcode::PushEntropy, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_is_pe, Opcode::PushIsPe, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_is_dll, Opcode::PushIsDll, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_num_sections, Opcode::PushNumSections, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_num_imports, Opcode::PushNumImports, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_entry_point, Opcode::PushEntryPoint, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_has_signature, Opcode::PushHasSignature, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_magic_u32, Opcode::PushMagicU32, 0);
bytecode_roundtrip_case!(bytecode_opcode_push_is_64bit, Opcode::PushIs64bit, 0);