#[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) })
}
#[deprecated(
since = "3.5.0",
note = "returns the address without provenance; use `vtable_slot_ptr`"
)]
#[inline]
pub unsafe fn vtable_slot(subobject: *mut u8, slot: usize) -> Option<usize> {
unsafe { vtable_slot_ptr(subobject, slot) }.map(|f| f.addr())
}
#[inline]
pub unsafe fn vtable_slot_ptr(subobject: *mut u8, slot: usize) -> Option<*const ()> {
if subobject.is_null() {
return None;
}
#[allow(clippy::cast_ptr_alignment)]
let vtable = unsafe { *(subobject as *const *const *const ()) };
if vtable.is_null() {
return None;
}
let f_ptr = unsafe { *vtable.add(slot) };
if f_ptr.is_null() {
return None;
}
Some(f_ptr)
}
#[inline]
pub unsafe fn secondary_call_target_ptr(
obj: *mut u8,
offset: isize,
slot: usize,
) -> Option<(*mut u8, *const ())> {
let this = unsafe { subobject_ptr(obj, offset)? };
let f_ptr = unsafe { vtable_slot_ptr(this, slot)? };
Some((this, f_ptr))
}
#[deprecated(
since = "3.5.0",
note = "returns the address without provenance; use `secondary_call_target_ptr`"
)]
#[inline]
pub unsafe fn secondary_call_target(
obj: *mut u8,
offset: isize,
slot: usize,
) -> Option<(*mut u8, usize)> {
unsafe { secondary_call_target_ptr(obj, offset, slot) }.map(|(this, f)| (this, f.addr()))
}
macro_rules! call_vtable {
(
$ptr:expr, $offset:expr, $slot:expr,
($($arg_ty:ty),* $(,)?) -> $ret:ty,
($($arg:expr),* $(,)?),
$absent:expr
) => {{
#[cfg(not(target_env = "msvc"))]
type VirtualFn = unsafe extern "C" fn(*mut u8 $(, $arg_ty)*) -> $ret;
#[cfg(target_env = "msvc")]
type VirtualFn = unsafe extern "thiscall" fn(*mut u8 $(, $arg_ty)*) -> $ret;
match unsafe { $crate::omp::vtable::secondary_call_target_ptr($ptr, $offset, $slot) } {
Some((this, f_ptr)) => {
let call: VirtualFn = unsafe { std::mem::transmute(f_ptr) };
unsafe { call(this $(, $arg)*) }
}
None => $absent,
}
}};
}
pub(crate) use call_vtable;
#[cfg(test)]
pub(crate) struct MockTable<const N: usize>(pub [*const (); N]);
#[cfg(test)]
unsafe impl<const N: usize> Sync for MockTable<N> {}
#[cfg(test)]
unsafe impl<const N: usize> Send for MockTable<N> {}
#[cfg(test)]
mod tests {
use super::*;
const DUMMY: [u8; 8] = [0; 8];
fn fake(i: usize) -> *const () {
DUMMY.as_ptr().wrapping_add(i).cast()
}
fn make_obj_with_secondary_vtable(byte_offset: isize, table: &[*const ()]) -> [*const (); 32] {
let mut buf = [std::ptr::null::<()>(); 32];
let idx = usize::try_from(byte_offset).expect("byte_offset must be >= 0")
/ std::mem::size_of::<*const ()>();
buf[idx] = table.as_ptr().cast();
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 mut buf = [0u8; 64];
let base = buf.as_mut_ptr();
let sub = unsafe { subobject_ptr(base, 56) }.unwrap();
assert_eq!(sub, base.wrapping_add(56));
}
#[test]
fn vtable_slot_ptr_returns_none_for_null_subobject() {
assert!(unsafe { vtable_slot_ptr(std::ptr::null_mut(), 0) }.is_none());
}
#[test]
fn vtable_slot_ptr_null_slot_returns_none() {
let table = [fake(0), fake(1), std::ptr::null()];
let mut buf = make_obj_with_secondary_vtable(0, &table);
let buf_u8 = buf.as_mut_ptr().cast::<u8>();
assert!(unsafe { vtable_slot_ptr(buf_u8, 2) }.is_none());
assert_eq!(unsafe { vtable_slot_ptr(buf_u8, 0) }, Some(fake(0)));
}
#[test]
fn secondary_call_target_ptr_combines_both() {
let table: Vec<*const ()> = (0..8).map(fake).collect();
let mut buf = make_obj_with_secondary_vtable(56, &table);
let buf_u8 = buf.as_mut_ptr().cast::<u8>();
let (this, f_ptr) = unsafe { secondary_call_target_ptr(buf_u8, 56, 3).unwrap() };
assert_eq!(this, buf_u8.wrapping_add(56));
assert_eq!(f_ptr, fake(3));
}
#[test]
fn secondary_call_target_ptr_null_obj_returns_none() {
assert!(unsafe { secondary_call_target_ptr(std::ptr::null_mut(), 56, 0) }.is_none());
}
#[test]
fn secondary_call_target_ptr_null_slot_returns_none() {
let table = [std::ptr::null::<()>()];
let mut buf = make_obj_with_secondary_vtable(8, &table);
let buf_u8 = buf.as_mut_ptr().cast::<u8>();
assert!(unsafe { secondary_call_target_ptr(buf_u8, 8, 0) }.is_none());
}
#[test]
#[allow(deprecated)]
fn deprecated_wrappers_return_the_same_address() {
let table: Vec<*const ()> = (0..8).map(fake).collect();
let mut buf = make_obj_with_secondary_vtable(56, &table);
let buf_u8 = buf.as_mut_ptr().cast::<u8>();
let (_, f) = unsafe { secondary_call_target(buf_u8, 56, 3).unwrap() };
assert_eq!(f, fake(3).addr());
let sub = buf_u8.wrapping_add(56);
assert_eq!(unsafe { vtable_slot(sub, 5) }, Some(fake(5).addr()));
}
}