extern crate alloc;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::marker::PhantomData;
use core::ptr::{self, null_mut};
use libffi_sys::ffi_call;
#[cfg(msan)]
use crate::__msan_unpoison;
use crate::FnPtr;
use crate::abi::Abi;
use crate::function::raw::Cif;
use crate::return_buffer::{ReturnBuffer, ffi_type_requires_return_buffer};
use crate::types::{FfiTypeLayout, Type, VariadicType};
pub(crate) mod raw;
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct Arg<'arg>(*mut c_void, PhantomData<&'arg ()>);
impl<'arg> Arg<'arg> {
pub fn new<T>(arg_ref: &'arg T) -> Self
where
T: ?Sized,
{
let arg_ptr = ptr::from_ref(arg_ref)
.cast::<c_void>()
.cast_mut();
Self(arg_ptr, PhantomData)
}
fn into_ptr(self) -> *mut c_void {
self.0
}
}
pub fn arg<T>(arg_ref: &'_ T) -> Arg<'_>
where
T: ?Sized,
{
Arg::new(arg_ref)
}
#[derive(Debug)]
#[repr(transparent)]
pub struct Ret<'ret>(*mut c_void, PhantomData<&'ret mut ()>);
impl<'ret> Ret<'ret> {
pub fn new<T>(ret_ref: &'ret mut T) -> Self
where
T: ?Sized,
{
let ret_ptr = ptr::from_mut(ret_ref).cast::<c_void>();
Self(ret_ptr, PhantomData)
}
pub fn void() -> Self {
Self(null_mut(), PhantomData)
}
fn into_ptr(self) -> *mut c_void {
self.0
}
}
pub fn ret<T>(ret_ref: &'_ mut T) -> Ret<'_>
where
T: ?Sized,
{
Ret::new(ret_ref)
}
#[derive(Clone, Debug)]
pub struct Function {
cif: Cif,
fn_ptr: FnPtr,
}
impl Function {
pub fn new<'args, I>(fn_ptr: FnPtr, argument_types: I, return_type: Option<&Type>) -> Self
where
I: IntoIterator<Item = &'args Type>,
{
Self::with_abi(fn_ptr, argument_types, return_type, Abi::default())
}
pub fn variadic<'fixed_args, 'variadic_args, I1, I2>(
fn_ptr: FnPtr,
fixed_argument_types: I1,
variadic_argument_types: I2,
return_type: Option<&Type>,
) -> Self
where
I1: IntoIterator<Item = &'fixed_args Type>,
I2: IntoIterator<Item = &'variadic_args VariadicType>,
{
Self::variadic_with_abi(
fn_ptr,
fixed_argument_types,
variadic_argument_types,
return_type,
Abi::default(),
)
}
pub fn with_abi<'args, I>(
fn_ptr: FnPtr,
argument_types: I,
return_type: Option<&Type>,
abi: Abi,
) -> Self
where
I: IntoIterator<Item = &'args Type>,
{
let cif = Cif::new(abi, argument_types, return_type);
Self { cif, fn_ptr }
}
pub fn variadic_with_abi<'fixed_args, 'variadic_args, I1, I2>(
fn_ptr: FnPtr,
fixed_argument_types: I1,
variadic_argument_types: I2,
return_type: Option<&Type>,
abi: Abi,
) -> Self
where
I1: IntoIterator<Item = &'fixed_args Type>,
I2: IntoIterator<Item = &'variadic_args VariadicType>,
{
let cif = Cif::variadic(
abi,
fixed_argument_types,
variadic_argument_types,
return_type,
);
Self { cif, fn_ptr }
}
pub fn builder() -> FunctionBuilder<FnPtrNotSet> {
FunctionBuilder {
fn_ptr: FnPtrNotSet,
argument_types: Vec::new(),
return_type: None,
abi: Abi::default(),
}
}
pub fn variadic_builder() -> VariadicFunctionBuilder<FnPtrNotSet> {
VariadicFunctionBuilder {
fn_ptr: FnPtrNotSet,
fixed_argument_types: Vec::new(),
variadic_argument_types: Vec::new(),
return_type: None,
abi: Abi::default(),
}
}
pub unsafe fn call<'arg, I>(&self, args: I, ret: Ret)
where
I: IntoIterator<Item = Arg<'arg>>,
{
let mut args: Vec<*mut c_void> = args.into_iter().map(Arg::into_ptr).collect();
let args_ptr = args.as_mut_ptr();
let fn_ptr = self.fn_ptr.as_libffi_sys_ptr();
let return_type = unsafe { &*(*self.cif.as_ffi_cif_ptr()).rtype };
let ret_ptr = ret.into_ptr();
if ffi_type_requires_return_buffer(return_type) {
let mut return_buffer = ReturnBuffer::new();
unsafe {
ffi_call(
self.cif.as_ffi_cif_ptr(),
fn_ptr,
return_buffer.as_mut_ptr(),
args_ptr,
);
#[cfg(msan)]
__msan_unpoison(return_buffer.as_mut_ptr(), size_of::<ReturnBuffer>());
}
unsafe {
return_buffer.write_result(ret_ptr, return_type.size);
}
} else {
unsafe {
ffi_call(self.cif.as_ffi_cif_ptr(), fn_ptr, ret_ptr, args_ptr);
#[cfg(msan)]
__msan_unpoison(ret_ptr, return_type.size);
}
}
}
pub fn argument_layouts(&self) -> Vec<FfiTypeLayout> {
self.cif.argument_layouts()
}
pub fn return_layout(&self) -> FfiTypeLayout {
self.cif.return_layout()
}
}
unsafe impl Send for Function {}
unsafe impl Sync for Function {}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct FnPtrNotSet;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct FnPtrSet(FnPtr);
#[derive(Clone, Debug)]
pub struct FunctionBuilder<State> {
fn_ptr: State,
argument_types: Vec<Type>,
return_type: Option<Type>,
abi: Abi,
}
impl<State> FunctionBuilder<State> {
#[must_use]
pub fn fn_ptr(self, fn_ptr: FnPtr) -> FunctionBuilder<FnPtrSet> {
FunctionBuilder {
fn_ptr: FnPtrSet(fn_ptr),
argument_types: self.argument_types,
return_type: self.return_type,
abi: self.abi,
}
}
#[must_use]
pub fn abi(mut self, abi: Abi) -> Self {
self.abi = abi;
self
}
#[must_use]
pub fn ret(mut self, return_type: Option<Type>) -> Self {
self.return_type = return_type;
self
}
#[must_use]
pub fn arg(mut self, ty: Type) -> Self {
self.argument_types.push(ty);
self
}
#[must_use]
pub fn args<I>(mut self, types: I) -> Self
where
I: IntoIterator<Item = Type>,
{
self.argument_types.extend(types);
self
}
}
impl FunctionBuilder<FnPtrSet> {
pub fn build(self) -> Function {
Function::with_abi(
self.fn_ptr.0,
&self.argument_types,
self.return_type.as_ref(),
self.abi,
)
}
}
#[derive(Clone, Debug)]
pub struct VariadicFunctionBuilder<State> {
fn_ptr: State,
fixed_argument_types: Vec<Type>,
variadic_argument_types: Vec<VariadicType>,
return_type: Option<Type>,
abi: Abi,
}
impl<State> VariadicFunctionBuilder<State> {
#[must_use]
pub fn fn_ptr(self, fn_ptr: FnPtr) -> VariadicFunctionBuilder<FnPtrSet> {
VariadicFunctionBuilder {
fn_ptr: FnPtrSet(fn_ptr),
fixed_argument_types: self.fixed_argument_types,
variadic_argument_types: self.variadic_argument_types,
return_type: self.return_type,
abi: self.abi,
}
}
#[must_use]
pub fn abi(mut self, abi: Abi) -> Self {
self.abi = abi;
self
}
#[must_use]
pub fn ret(mut self, return_type: Option<Type>) -> Self {
self.return_type = return_type;
self
}
#[must_use]
pub fn fixed_arg(mut self, ty: Type) -> Self {
self.fixed_argument_types.push(ty);
self
}
#[must_use]
pub fn fixed_args<I>(mut self, types: I) -> Self
where
I: IntoIterator<Item = Type>,
{
self.fixed_argument_types.extend(types);
self
}
#[must_use]
pub fn variadic_arg(mut self, ty: VariadicType) -> Self {
self.variadic_argument_types.push(ty);
self
}
#[must_use]
pub fn variadic_args<I>(mut self, types: I) -> Self
where
I: IntoIterator<Item = VariadicType>,
{
self.variadic_argument_types.extend(types);
self
}
}
impl VariadicFunctionBuilder<FnPtrSet> {
pub fn build(self) -> Function {
Function::variadic_with_abi(
self.fn_ptr.0,
&self.fixed_argument_types,
&self.variadic_argument_types,
self.return_type.as_ref(),
self.abi,
)
}
}
#[cfg(test)]
mod tests {
use core::cell::UnsafeCell;
use core::ffi::CStr;
use core::mem::MaybeUninit;
use test_callbacks::*;
use super::*;
use crate::fn_ptrize;
use crate::function::raw::CifKind;
use crate::test_utils::{
F32_ARG, F64_ARG, I8_ARG, I16_ARG, I32_ARG, I64_ARG, ISIZE_ARG, PTR_ARG, SNPRINTF_ARG_1,
SNPRINTF_ARG_2, SNPRINTF_ARG_3, SNPRINTF_ARG_4, SNPRINTF_ARG_5, SNPRINTF_ARG_6,
SNPRINTF_EXPECTED_OUTPUT, SNPRINTF_EXPECTED_RETURN_VALUE, SNPRINTF_FORMAT, STRUCT_ARG,
TestStruct, U8_ARG, U16_ARG, U32_ARG, U64_ARG, USIZE_ARG, snprintf,
};
use crate::types::FfiType;
macro_rules! test_identity_function {
($ty:ty, $identity_fn:ident, $val:expr) => {{
let ffi_type = <$ty as FfiType>::ffi_type();
let function = Function::new(
fn_ptrize!($identity_fn),
core::slice::from_ref(&ffi_type),
Some(&ffi_type),
);
let mut return_buffer = MaybeUninit::<$ty>::uninit();
let fn_args = [arg(&$val)];
let fn_ret = ret(&mut return_buffer);
let return_value = unsafe {
function.call(fn_args, fn_ret);
return_buffer.assume_init()
};
assert_eq!(
return_value,
$val,
"Unexpected return while calling identity function {}.",
stringify!($identity_fn)
);
}};
}
#[test]
#[cfg_attr(miri, ignore)]
fn call_void_fn() {
let function = Function::new(fn_ptrize!(void_fn), &[], None);
unsafe {
function.call([], Ret::void());
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_identity_functions() {
test_identity_function!(i8, i8_identity, I8_ARG);
test_identity_function!(i16, i16_identity, I16_ARG);
test_identity_function!(i32, i32_identity, I32_ARG);
test_identity_function!(i64, i64_identity, I64_ARG);
test_identity_function!(isize, isize_identity, ISIZE_ARG);
test_identity_function!(u8, u8_identity, U8_ARG);
test_identity_function!(u16, u16_identity, U16_ARG);
test_identity_function!(u32, u32_identity, U32_ARG);
test_identity_function!(u64, u64_identity, U64_ARG);
test_identity_function!(usize, usize_identity, USIZE_ARG);
test_identity_function!(f32, f32_identity, F32_ARG);
test_identity_function!(f64, f64_identity, F64_ARG);
test_identity_function!(*const c_void, ptr_identity, PTR_ARG.0);
test_identity_function!(TestStruct, test_struct_identity, STRUCT_ARG);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_function_does_not_modify_args() {
let i8_arg = UnsafeCell::new(I8_ARG);
let i16_arg = UnsafeCell::new(I16_ARG);
let i32_arg = UnsafeCell::new(I32_ARG);
let i64_arg = UnsafeCell::new(I64_ARG);
let isize_arg = UnsafeCell::new(ISIZE_ARG);
let u8_arg = UnsafeCell::new(U8_ARG);
let u16_arg = UnsafeCell::new(U16_ARG);
let u32_arg = UnsafeCell::new(U32_ARG);
let u64_arg = UnsafeCell::new(U64_ARG);
let usize_arg = UnsafeCell::new(USIZE_ARG);
let f32_arg = UnsafeCell::new(F32_ARG);
let f64_arg = UnsafeCell::new(F64_ARG);
let struct_arg = UnsafeCell::new(STRUCT_ARG);
let ptr_arg = UnsafeCell::new(PTR_ARG);
#[rustfmt::skip]
let function = Function::new(
fn_ptrize!(modifying_fn),
&[
Type::I8, Type::I16, Type::I32, Type::I64, Type::Isize, Type::U8, Type::U16,
Type::U32, Type::U64, Type::Usize, Type::F32, Type::F64,
<TestStruct as FfiType>::ffi_type(), Type::Pointer,
],
None,
);
#[rustfmt::skip]
let arg_array = [
arg(&i8_arg), arg(&i16_arg), arg(&i32_arg), arg(&i64_arg), arg(&isize_arg),
arg(&u8_arg), arg(&u16_arg), arg(&u32_arg), arg(&u64_arg), arg(&usize_arg),
arg(&f32_arg), arg(&f64_arg), arg(&struct_arg), arg(&ptr_arg),
];
unsafe {
function.call(arg_array, Ret::void());
}
assert_eq!(i8_arg.into_inner(), I8_ARG);
assert_eq!(i16_arg.into_inner(), I16_ARG);
assert_eq!(i32_arg.into_inner(), I32_ARG);
assert_eq!(i64_arg.into_inner(), I64_ARG);
assert_eq!(isize_arg.into_inner(), ISIZE_ARG);
assert_eq!(u8_arg.into_inner(), U8_ARG);
assert_eq!(u16_arg.into_inner(), U16_ARG);
assert_eq!(u32_arg.into_inner(), U32_ARG);
assert_eq!(u64_arg.into_inner(), U64_ARG);
assert_eq!(usize_arg.into_inner(), USIZE_ARG);
assert_eq!(f32_arg.into_inner(), F32_ARG);
assert_eq!(f64_arg.into_inner(), F64_ARG);
assert_eq!(struct_arg.into_inner(), STRUCT_ARG);
assert_eq!(ptr_arg.into_inner(), PTR_ARG);
}
#[test]
fn test_type_layout_fns() {
#[rustfmt::skip]
let modifying_fn_arg_types = [
Type::I8, Type::I16, Type::I32, Type::I64, Type::Isize, Type::U8, Type::U16, Type::U32,
Type::U64, Type::Usize, Type::F32, Type::F64, <TestStruct as FfiType>::ffi_type(),
Type::Pointer,
];
let expected_layouts: Vec<FfiTypeLayout> =
modifying_fn_arg_types.iter().map(Type::layout).collect();
let function = Function::new(fn_ptrize!(modifying_fn), &modifying_fn_arg_types, None);
assert_eq!(expected_layouts, function.argument_layouts());
for ty in &modifying_fn_arg_types {
let function = Function::new(fn_ptrize!(void_fn), core::slice::from_ref(ty), Some(ty));
assert_eq!(function.argument_layouts(), vec![ty.layout()],);
assert_eq!(function.return_layout(), ty.layout());
}
}
#[test]
fn function_new_with_abi_works_with_all_abis() {
for abi in Abi::ABIS {
Function::with_abi(fn_ptrize!(void_fn), &[], None, abi);
}
}
#[test]
fn variadic_function_supports_all_variadic_types() {
for abi in Abi::ABIS {
let variadic_fn = Function::variadic_with_abi(
fn_ptrize!(void_fn),
&[Type::Pointer],
&[
VariadicType::I32,
VariadicType::U32,
VariadicType::I64,
VariadicType::U64,
VariadicType::Isize,
VariadicType::Usize,
VariadicType::F64,
VariadicType::Pointer,
VariadicType::create_struct(vec![Type::I8]).unwrap(),
],
None,
abi,
);
assert!(matches!(variadic_fn.cif.kind, CifKind::Variadic { .. }));
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_variadic_snprintf() {
let snprintf_function = Function::variadic(
fn_ptrize!(snprintf),
&[Type::Pointer, Type::Usize, Type::Pointer],
&[
VariadicType::I32,
VariadicType::U32,
VariadicType::I64,
VariadicType::U64,
VariadicType::Pointer,
VariadicType::F64,
],
Some(&Type::I32),
);
let mut buffer = [0u8; 128];
let buffer_ptr = buffer.as_mut_ptr();
let buffer_size = buffer.len();
let mut return_value = 0i32;
unsafe {
snprintf_function.call(
[
arg(&buffer_ptr),
arg(&buffer_size),
arg(&SNPRINTF_FORMAT),
arg(&SNPRINTF_ARG_1),
arg(&SNPRINTF_ARG_2),
arg(&SNPRINTF_ARG_3),
arg(&SNPRINTF_ARG_4),
arg(&SNPRINTF_ARG_5),
arg(&SNPRINTF_ARG_6),
],
ret(&mut return_value),
);
}
let output_str = CStr::from_bytes_until_nul(&buffer).unwrap();
assert_eq!(
return_value, SNPRINTF_EXPECTED_RETURN_VALUE,
"`snprintf` did not write the expected number of bytes."
);
assert_eq!(
output_str, SNPRINTF_EXPECTED_OUTPUT,
"Output from `snprintf` was not as expected."
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn builder_builds_and_calls_void() {
let function = Function::builder().fn_ptr(fn_ptrize!(void_fn)).build();
unsafe {
function.call([], Ret::void());
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn builder_with_args_and_multiple_fn_ptr_calls() {
let builder = Function::builder()
.arg(Type::I32)
.ret(Some(Type::I32))
.fn_ptr(fn_ptrize!(f64_identity))
.fn_ptr(fn_ptrize!(i32_identity));
let function = builder.build();
let mut return_buffer = MaybeUninit::<i32>::uninit();
let arg_val: i32 = I32_ARG;
unsafe {
function.call([arg(&arg_val)], ret(&mut return_buffer));
}
let return_value = unsafe { return_buffer.assume_init() };
assert_eq!(return_value, arg_val);
}
#[test]
#[cfg_attr(miri, ignore)]
fn variadic_builder_snprintf_works() {
let snprintf_function = Function::variadic_builder()
.fixed_args(vec![Type::Pointer, Type::Usize, Type::Pointer])
.variadic_args(vec![
VariadicType::I32,
VariadicType::U32,
VariadicType::I64,
VariadicType::U64,
VariadicType::Pointer,
VariadicType::F64,
])
.ret(Some(Type::I32))
.fn_ptr(fn_ptrize!(snprintf))
.build();
let mut buffer = [0u8; 128];
let buffer_ptr = buffer.as_mut_ptr();
let buffer_size = buffer.len();
let mut return_value = 0i32;
unsafe {
snprintf_function.call(
[
arg(&buffer_ptr),
arg(&buffer_size),
arg(&SNPRINTF_FORMAT),
arg(&SNPRINTF_ARG_1),
arg(&SNPRINTF_ARG_2),
arg(&SNPRINTF_ARG_3),
arg(&SNPRINTF_ARG_4),
arg(&SNPRINTF_ARG_5),
arg(&SNPRINTF_ARG_6),
],
ret(&mut return_value),
);
}
let output_str = CStr::from_bytes_until_nul(&buffer).unwrap();
assert_eq!(
return_value, SNPRINTF_EXPECTED_RETURN_VALUE,
"`snprintf` did not write the expected number of bytes."
);
assert_eq!(
output_str, SNPRINTF_EXPECTED_OUTPUT,
"Output from `snprintf` was not as expected."
);
}
}
#[cfg(test)]
#[rustfmt::skip]
pub(crate) mod test_callbacks {
use core::ffi::c_void;
use core::hint::black_box;
use crate::test_utils::TestStruct;
pub extern "C" fn void_fn() {}
pub extern "C" fn i8_identity(arg: i8) -> i8 { arg }
pub extern "C" fn i16_identity(arg: i16) -> i16 { arg }
pub extern "C" fn i32_identity(arg: i32) -> i32 { arg }
pub extern "C" fn i64_identity(arg: i64) -> i64 { arg }
pub extern "C" fn isize_identity(arg: isize) -> isize { arg }
pub extern "C" fn u8_identity(arg: u8) -> u8 { arg }
pub extern "C" fn u16_identity(arg: u16) -> u16 { arg }
pub extern "C" fn u32_identity(arg: u32) -> u32 { arg }
pub extern "C" fn u64_identity(arg: u64) -> u64 { arg }
pub extern "C" fn usize_identity(arg: usize) -> usize { arg }
pub extern "C" fn f32_identity(arg: f32) -> f32 { arg }
pub extern "C" fn f64_identity(arg: f64) -> f64 { arg }
pub extern "C" fn test_struct_identity(arg: TestStruct) -> TestStruct { arg }
pub extern "C" fn ptr_identity(arg: *const c_void) -> *const c_void { arg }
pub extern "C" fn modifying_fn(
mut i8_arg: i8, mut i16_arg: i16, mut i32_arg: i32, mut i64_arg: i64, mut isize_arg: isize,
mut u8_arg: u8, mut u16_arg: u16, mut u32_arg: u32, mut u64_arg: u64, mut usize_arg: usize,
mut f32_arg: f32, mut f64_arg: f64, mut struct_arg: TestStruct, mut ptr_arg: *const c_void,
) {
i8_arg += 1; i16_arg += 1; i32_arg += 1; i64_arg += 1; isize_arg += 1; u8_arg += 1;
u16_arg += 1; u32_arg += 1; u64_arg += 1; usize_arg += 1; f32_arg += 1.; f64_arg += 1.0;
struct_arg.0 += 1; struct_arg.1 += 1; struct_arg.2 += 1; struct_arg.3 += 1;
ptr_arg = unsafe { ptr_arg.byte_add(1) };
black_box((
i8_arg, i16_arg, i32_arg, i64_arg, isize_arg, u8_arg, u16_arg, u32_arg, u64_arg,
usize_arg, f32_arg, f64_arg, struct_arg, ptr_arg
));
}
}