use std::fmt;
use crate::metadata::token::Token;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct InstructionPointer {
method: Token,
offset: u32,
current_size: u32,
}
impl InstructionPointer {
#[must_use]
pub fn new(method: Token) -> Self {
InstructionPointer {
method,
offset: 0,
current_size: 0,
}
}
#[must_use]
pub fn at_offset(method: Token, offset: u32) -> Self {
InstructionPointer {
method,
offset,
current_size: 0,
}
}
#[must_use]
pub fn method(&self) -> Token {
self.method
}
#[must_use]
pub fn offset(&self) -> u32 {
self.offset
}
pub fn set_current_size(&mut self, size: u32) {
self.current_size = size;
}
#[must_use]
pub fn current_size(&self) -> u32 {
self.current_size
}
#[must_use]
pub fn next_offset(&self) -> u32 {
self.offset + self.current_size
}
pub fn advance(&mut self, instruction_size: u32) {
self.offset += instruction_size;
self.current_size = 0;
}
pub fn advance_current(&mut self) {
self.offset += self.current_size;
self.current_size = 0;
}
pub fn branch_to(&mut self, target_offset: u32) {
self.offset = target_offset;
self.current_size = 0;
}
#[allow(clippy::cast_sign_loss)] pub fn branch_relative(&mut self, relative_offset: i32) {
let base = self.next_offset();
if relative_offset >= 0 {
self.offset = base.wrapping_add(relative_offset as u32);
} else {
self.offset = base.wrapping_sub(relative_offset.unsigned_abs());
}
self.current_size = 0;
}
#[must_use]
pub fn enter_method(&mut self, new_method: Token) -> InstructionPointer {
let saved = *self;
self.method = new_method;
self.offset = 0;
self.current_size = 0;
saved
}
pub fn restore(&mut self, saved: InstructionPointer) {
*self = saved;
}
#[must_use]
pub fn is_at_start(&self) -> bool {
self.offset == 0
}
}
impl fmt::Debug for InstructionPointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"IP {{ method: 0x{:08X}, offset: 0x{:04X} }}",
self.method.value(),
self.offset
)
}
}
impl fmt::Display for InstructionPointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{:08X}:0x{:04X}", self.method.value(), self.offset)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_instruction_pointer_creation() {
let method = Token::new(0x06000001);
let ip = InstructionPointer::new(method);
assert_eq!(ip.method().value(), 0x06000001);
assert_eq!(ip.offset(), 0);
assert!(ip.is_at_start());
}
#[test]
fn test_instruction_pointer_at_offset() {
let method = Token::new(0x06000001);
let ip = InstructionPointer::at_offset(method, 100);
assert_eq!(ip.offset(), 100);
assert!(!ip.is_at_start());
}
#[test]
fn test_advance() {
let method = Token::new(0x06000001);
let mut ip = InstructionPointer::new(method);
ip.advance(5);
assert_eq!(ip.offset(), 5);
ip.advance(3);
assert_eq!(ip.offset(), 8);
}
#[test]
fn test_advance_current() {
let method = Token::new(0x06000001);
let mut ip = InstructionPointer::new(method);
ip.set_current_size(5);
assert_eq!(ip.next_offset(), 5);
ip.advance_current();
assert_eq!(ip.offset(), 5);
assert_eq!(ip.current_size(), 0);
}
#[test]
fn test_branch_to() {
let method = Token::new(0x06000001);
let mut ip = InstructionPointer::new(method);
ip.advance(10);
ip.branch_to(50);
assert_eq!(ip.offset(), 50);
}
#[test]
fn test_branch_relative_forward() {
let method = Token::new(0x06000001);
let mut ip = InstructionPointer::at_offset(method, 10);
ip.set_current_size(2);
ip.branch_relative(10);
assert_eq!(ip.offset(), 22);
}
#[test]
fn test_branch_relative_backward() {
let method = Token::new(0x06000001);
let mut ip = InstructionPointer::at_offset(method, 50);
ip.set_current_size(2);
ip.branch_relative(-10);
assert_eq!(ip.offset(), 42);
}
#[test]
fn test_enter_method() {
let method1 = Token::new(0x06000001);
let method2 = Token::new(0x06000002);
let mut ip = InstructionPointer::at_offset(method1, 25);
let saved = ip.enter_method(method2);
assert_eq!(ip.method().value(), 0x06000002);
assert_eq!(ip.offset(), 0);
assert_eq!(saved.method().value(), 0x06000001);
assert_eq!(saved.offset(), 25);
}
#[test]
fn test_restore() {
let method1 = Token::new(0x06000001);
let method2 = Token::new(0x06000002);
let mut ip = InstructionPointer::at_offset(method1, 25);
let saved = ip.enter_method(method2);
ip.advance(10);
ip.restore(saved);
assert_eq!(ip.method().value(), 0x06000001);
assert_eq!(ip.offset(), 25);
}
#[test]
fn test_debug_display() {
let method = Token::new(0x06000001);
let ip = InstructionPointer::at_offset(method, 0x100);
let debug = format!("{ip:?}");
assert!(debug.contains("0x06000001"));
assert!(debug.contains("0x0100"));
let display = format!("{ip}");
assert!(display.contains("0x06000001"));
assert!(display.contains("0x0100"));
}
#[test]
fn test_equality() {
let method = Token::new(0x06000001);
let ip1 = InstructionPointer::at_offset(method, 50);
let ip2 = InstructionPointer::at_offset(method, 50);
let ip3 = InstructionPointer::at_offset(method, 100);
assert_eq!(ip1, ip2);
assert_ne!(ip1, ip3);
}
#[test]
fn test_copy() {
let method = Token::new(0x06000001);
let ip1 = InstructionPointer::at_offset(method, 50);
let ip2 = ip1;
assert_eq!(ip1, ip2);
}
}