use crate::{
error::Error,
opcode::{Opcode, parse_opcode},
reader::Reader,
ty::Type,
};
#[derive(Clone, Debug, PartialEq)]
pub struct Instruction<'a> {
pub offset: u32,
pub opcode: Opcode<'a>,
pub next_offset: u32,
}
impl Instruction<'_> {
pub fn branch_target(&self) -> Option<u32> {
self.branch_targets().into_iter().flatten().next()
}
pub fn branch_targets(&self) -> [Option<u32>; 4] {
match &self.opcode {
Opcode::Goto { offset }
| Opcode::PopAndGoto { offset }
| Opcode::Pop2AndGoto { offset } => [
relative_i32_target(self.next_offset, *offset),
None,
None,
None,
],
Opcode::CondGoto { offset, .. } | Opcode::CondNotGoto { offset, .. } => [
relative_u32_target(self.next_offset, *offset),
None,
None,
None,
],
Opcode::FlagGoto { target } => [Some(*target), None, None, None],
Opcode::PushExceptionHandler {
finally_offset,
exception_offset,
finally2_offset,
end_of_block,
} => [
Some(*finally_offset),
Some(*exception_offset),
Some(*finally2_offset),
Some(*end_of_block),
],
_ => [None, None, None, None],
}
}
}
fn relative_i32_target(base: u32, offset: i32) -> Option<u32> {
base.checked_add_signed(offset)
}
fn relative_u32_target(base: u32, offset: u32) -> Option<u32> {
base.checked_add(offset)
}
#[derive(Clone, Debug)]
pub struct ProcDisasm<'a> {
pub proc_index: u32,
pub bytecode_offset: u32,
pub instructions: Vec<Instruction<'a>>,
}
pub(crate) fn disassemble_proc<'a>(
blob: &'a [u8],
proc_index: u32,
bytecode_offset: u32,
bytecode_len: u32,
types: &[Type<'a>],
) -> Result<ProcDisasm<'a>, Error> {
let start = bytecode_offset as usize;
let end = (bytecode_offset as u64)
.checked_add(u64::from(bytecode_len))
.ok_or(Error::Overflow {
what: "bytecode end offset",
})?;
let end = usize::try_from(end).map_err(|_| Error::Overflow {
what: "bytecode end offset",
})?;
let body = blob.get(start..end).ok_or(Error::BytecodeOutOfRange {
offset: bytecode_offset,
length: bytecode_len,
})?;
let mut reader = Reader::new(body);
let mut instructions = Vec::new();
while reader.pos() < reader.len() {
let local_offset = reader.pos();
let opcode = parse_opcode(&mut reader, types)?;
let absolute_offset = bytecode_offset
.checked_add(u32::try_from(local_offset).unwrap_or(u32::MAX))
.ok_or(Error::Overflow {
what: "instruction absolute offset",
})?;
let next_offset = bytecode_offset
.checked_add(u32::try_from(reader.pos()).unwrap_or(u32::MAX))
.ok_or(Error::Overflow {
what: "instruction next offset",
})?;
instructions.push(Instruction {
offset: absolute_offset,
opcode,
next_offset,
});
}
Ok(ProcDisasm {
proc_index,
bytecode_offset,
instructions,
})
}