#[inline]
pub unsafe fn subobject_ptr(obj: *mut u8, offset: isize) -> Option<*mut u8> {
if obj.is_null() {
return None;
}
Some(unsafe { obj.offset(offset) })
}
#[inline]
pub unsafe fn vtable_slot(subobject: *mut u8, slot: usize) -> Option<usize> {
if subobject.is_null() {
return None;
}
#[allow(clippy::cast_ptr_alignment)]
let vtable = unsafe { *(subobject as *const *const usize) };
if vtable.is_null() {
return None;
}
let f_ptr = unsafe { *vtable.add(slot) };
if f_ptr == 0 {
return None;
}
Some(f_ptr)
}
#[inline]
pub unsafe fn secondary_call_target(
obj: *mut u8,
offset: isize,
slot: usize,
) -> Option<(*mut u8, usize)> {
let this = unsafe { subobject_ptr(obj, offset)? };
let f_ptr = unsafe { vtable_slot(this, slot)? };
Some((this, f_ptr))
}
#[cfg(test)]
mod tests {
use super::*;
static MOCK_VTABLE: std::sync::OnceLock<[usize; 8]> = std::sync::OnceLock::new();
fn mock_vtable() -> &'static [usize; 8] {
MOCK_VTABLE.get_or_init(|| {
[
0xDEAD_0000,
0xDEAD_0001,
0xDEAD_0002,
0xDEAD_0003,
0xDEAD_0004,
0xDEAD_0005,
0xDEAD_0006,
0xDEAD_0007,
]
})
}
fn make_obj_with_secondary_vtable(byte_offset: isize) -> [usize; 32] {
let mut buf = [0usize; 32];
let vptr = mock_vtable().as_ptr() as usize;
let idx = usize::try_from(byte_offset).expect("byte_offset must be >= 0")
/ std::mem::size_of::<usize>();
buf[idx] = vptr;
buf
}
#[test]
fn subobject_ptr_returns_none_for_null() {
assert!(unsafe { subobject_ptr(std::ptr::null_mut(), 56) }.is_none());
}
#[test]
fn subobject_ptr_adds_offset_correctly() {
let base = 0x1000 as *mut u8;
let sub = unsafe { subobject_ptr(base, 56) }.unwrap();
assert_eq!(sub as usize, 0x1000 + 56);
}
#[test]
fn vtable_slot_returns_none_for_null_subobject() {
assert!(unsafe { vtable_slot(std::ptr::null_mut(), 0) }.is_none());
}
#[test]
fn vtable_slot_returns_zero_check() {
let zero_table: [usize; 3] = [0xDEAD, 0xDEAD, 0];
let mut buf = [0usize; 8];
buf[0] = zero_table.as_ptr() as usize;
let buf_u8 = buf.as_mut_ptr().cast::<u8>();
assert!(unsafe { vtable_slot(buf_u8, 2) }.is_none());
assert_eq!(unsafe { vtable_slot(buf_u8, 0) }, Some(0xDEAD));
}
#[test]
fn secondary_call_target_combines_both() {
let mut buf = make_obj_with_secondary_vtable(56);
let buf_u8 = buf.as_mut_ptr().cast::<u8>();
let (this, f_ptr) = unsafe { secondary_call_target(buf_u8, 56, 3).unwrap() };
assert_eq!(this as usize, buf_u8 as usize + 56);
assert_eq!(f_ptr, 0xDEAD_0003);
}
#[test]
fn secondary_call_target_null_obj_returns_none() {
assert!(unsafe { secondary_call_target(std::ptr::null_mut(), 56, 0) }.is_none());
}
#[test]
fn secondary_call_target_zero_slot_returns_none() {
let zero_table: [usize; 1] = [0];
let mut buf = [0usize; 16];
buf[2] = zero_table.as_ptr() as usize;
let buf_u8 = buf.as_mut_ptr().cast::<u8>();
assert!(unsafe { secondary_call_target(buf_u8, 8, 0) }.is_none());
}
}