forked from AbleOS/holey-bytes
159 lines
5.4 KiB
Rust
159 lines
5.4 KiB
Rust
//! Generate HoleyBytes code validator
|
|
|
|
macro_rules! gen_valider {
|
|
(
|
|
$(
|
|
$ityn:ident
|
|
($($param_i:ident: $param_ty:ident),* $(,)?)
|
|
=> [$($opcode:ident),* $(,)?],
|
|
)*
|
|
) => {
|
|
#[allow(unreachable_code)]
|
|
pub mod valider {
|
|
//! Validate if program is sound to execute
|
|
|
|
/// Program validation error kind
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum ErrorKind {
|
|
/// Unknown opcode
|
|
InvalidInstruction,
|
|
/// VM doesn't implement this valid opcode
|
|
Unimplemented,
|
|
/// Attempted to copy over register boundary
|
|
RegisterArrayOverflow,
|
|
/// Program is not validly terminated
|
|
InvalidEnd,
|
|
}
|
|
|
|
/// Error
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct Error {
|
|
/// Kind
|
|
pub kind: ErrorKind,
|
|
/// Location in bytecode
|
|
pub index: usize,
|
|
}
|
|
|
|
/// Perform bytecode validation. If it passes, the program should be
|
|
/// sound to execute.
|
|
pub fn validate(mut program: &[u8]) -> Result<(), Error> {
|
|
// Program has to end with 12 zeroes, if there is less than
|
|
// 12 bytes, program is invalid.
|
|
if program.len() < 12 {
|
|
return Err(Error {
|
|
kind: ErrorKind::InvalidEnd,
|
|
index: 0,
|
|
});
|
|
}
|
|
|
|
// Verify that program ends with 12 zeroes
|
|
for (index, item) in program.iter().enumerate().skip(program.len() - 12) {
|
|
if *item != 0 {
|
|
return Err(Error {
|
|
kind: ErrorKind::InvalidEnd,
|
|
index,
|
|
});
|
|
}
|
|
}
|
|
|
|
let start = program;
|
|
loop {
|
|
use crate::opcode::*;
|
|
program = match program {
|
|
// End of program
|
|
[] => return Ok(()),
|
|
|
|
// Memory load/store cannot go out-of-bounds register array
|
|
[LD..=ST, reg, _, _, _, _, _, _, _, _, count_0, count_1, ..]
|
|
if usize::from(*reg) * 8
|
|
+ usize::from(u16::from_le_bytes([*count_1, *count_0]))
|
|
> 2048 =>
|
|
{
|
|
return Err(Error {
|
|
kind: ErrorKind::RegisterArrayOverflow,
|
|
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
|
});
|
|
}
|
|
|
|
// Block register copy cannot go out-of-bounds register array
|
|
[BRC, src, dst, count, ..]
|
|
if src.checked_add(*count).is_none()
|
|
|| dst.checked_add(*count).is_none() =>
|
|
{
|
|
return Err(Error {
|
|
kind: ErrorKind::RegisterArrayOverflow,
|
|
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
|
});
|
|
}
|
|
|
|
$(
|
|
$crate::gen_valider::inst_chk!(
|
|
rest, $ityn, $($opcode),*
|
|
)
|
|
)|* => rest,
|
|
|
|
// The plebs
|
|
_ => {
|
|
return Err(Error {
|
|
kind: ErrorKind::InvalidInstruction,
|
|
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Generate instruction check pattern
|
|
macro_rules! inst_chk {
|
|
// Sadly this has hardcoded instruction types,
|
|
// as I cannot generate parts of patterns+
|
|
|
|
($rest:ident, bbbb, $($opcode:ident),*) => {
|
|
// B B B B
|
|
[$($opcode)|*, _, _, _, _, $rest @ ..]
|
|
};
|
|
|
|
($rest:ident, bbb, $($opcode:ident),*) => {
|
|
// B B B
|
|
[$($opcode)|*, _, _, _, $rest @ ..]
|
|
};
|
|
|
|
($rest:ident, bbdh, $($opcode:ident),*) => {
|
|
// B B D1 D2 D3 D4 D5 D6 D7 D8 H1 H2
|
|
[$($opcode)|*, _, _, _, _, _, _, _, _, _, _, _, _, $rest @ ..]
|
|
};
|
|
|
|
($rest:ident, bbd, $($opcode:ident),*) => {
|
|
// B B D1 D2 D3 D4 D5 D6 D7 D8
|
|
[$($opcode)|*, _, _, _, _, _, _, _, _, _, _, $rest @ ..]
|
|
};
|
|
|
|
($rest:ident, bbw, $($opcode:ident),*) => {
|
|
// B B W1 W2 W3 W4
|
|
[$($opcode)|*, _, _, _, _, _, _, $rest @ ..]
|
|
};
|
|
|
|
($rest:ident, bb, $($opcode:ident),*) => {
|
|
// B B
|
|
[$($opcode)|*, _, _, $rest @ ..]
|
|
};
|
|
|
|
($rest:ident, bd, $($opcode:ident),*) => {
|
|
// B D1 D2 D3 D4 D5 D6 D7 D8
|
|
[$($opcode)|*, _, _, _, _, _, _, _, _, _, $rest @ ..]
|
|
};
|
|
|
|
($rest:ident, n, $($opcode:ident),*) => {
|
|
[$($opcode)|*, $rest @ ..]
|
|
};
|
|
|
|
($_0:ident, $($_1:ident),*) => {
|
|
compile_error!("Invalid instruction type");
|
|
}
|
|
}
|
|
|
|
pub(crate) use {gen_valider, inst_chk};
|