#[allow(unused_imports)]
use closure_ffi::{cc, BareFn, BareFnMut, BareFnOnce, UntypedBareFn};
mod slab_alloc;
use slab_alloc::SLAB;
#[test]
fn test_stateless_fn() {
let bare_closure = BareFn::new_c_in(move |n: usize| 2 * n, &SLAB);
let bare = bare_closure.bare();
assert_eq!(unsafe { bare(5) }, 10);
}
#[test]
fn test_borrow_fn() {
let array = [0, 5, 10, 15, 20];
let bare_closure = BareFn::new_c_in(
|n: usize| {
println!("{n:08x}");
array[n]
},
&SLAB,
);
let bare = bare_closure.bare();
assert_eq!(unsafe { bare(3) }, 15);
}
#[test]
fn test_borrow_fn_mut() {
let mut sum = 0;
let bare_closure = BareFnMut::new_c_in(|n: usize| sum += n, &SLAB);
let bare = bare_closure.bare();
unsafe {
bare(5);
bare(3);
bare(7);
}
drop(bare_closure);
assert_eq!(sum, 15);
}
#[test]
fn test_moved_fn_mut() {
let mut sum = 0;
let bare_closure = BareFnMut::new_c_in(
move |n: usize| {
sum += n;
sum
},
&SLAB,
);
let bare = bare_closure.bare();
unsafe {
assert_eq!(bare(5), 5);
assert_eq!(bare(3), 8);
assert_eq!(bare(4), 12);
}
}
#[cfg(feature = "std")]
#[test]
fn test_print_fn() {
let bare_closure = BareFn::new_c_in(
move |n: usize| {
println!("{n:08x}");
3 * n
},
&SLAB,
);
let bare = bare_closure.bare();
assert_eq!(unsafe { bare(5) }, 15);
}
#[cfg(feature = "std")]
#[test]
fn test_print_fn_once() {
let bare_closure = BareFnOnce::new_c_in(
move |n: usize| {
println!("{n:08x}");
3 * n
},
&SLAB,
);
let bare = bare_closure.leak();
assert_eq!(unsafe { bare(5) }, 15);
}
#[cfg(feature = "std")]
#[test]
fn test_double_free() {
println!("test BareFn (coerced)");
#[derive(Debug)]
#[allow(dead_code)]
struct Val(usize, Box<usize>);
impl Val {
fn new(val: usize) -> Self {
Self(val, Box::new(101 ))
}
}
impl Drop for Val {
fn drop(&mut self) {
println!("Val dropped (val={})", self.0);
}
}
fn f() -> u32 {
println!("bare BareFn call");
let mut v = Val::new(100);
v.0 += 1;
drop(v);
42
}
let f: fn() -> u32 = f as _;
assert_eq!(42, f());
unsafe {
let bare = BareFn::new_c_in(f, &SLAB);
assert_eq!(42, bare.bare()());
}
unsafe {
let wrap = BareFn::new_c_in(f, &SLAB);
let bare = wrap.bare();
assert_eq!(42, bare());
assert_eq!(42, bare());
}
assert_eq!(42, f());
println!("test FnPtr - done");
}
#[cfg(feature = "std")]
#[test]
fn test_unwind_fn() {
let capture = 42usize;
let bare_closure = BareFn::with_cc_in(cc::CUnwind, |arg| assert_eq!(arg, capture), &SLAB);
let bare = bare_closure.bare();
unsafe { bare(42) };
let result = std::panic::catch_unwind(|| unsafe { bare(0) });
assert!(result.is_err())
}
#[cfg(feature = "std")]
#[test]
fn test_untyped_bare_fn() {
use core::cell::Cell;
#[derive(Debug)]
struct SetOnDrop<'a>(&'a Cell<bool>);
impl Drop for SetOnDrop<'_> {
fn drop(&mut self) {
self.0.set(true);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum IntOrStr {
Int(i32),
Str(&'static str),
}
let shared = Cell::new(IntOrStr::Int(0));
let shared_ref = &shared;
let mut bare_closures = Vec::default();
let int_closure_dropped = Cell::new(false);
let int_closure_check = SetOnDrop(&int_closure_dropped);
let int_closure = move |arg: i32| {
let _ = &int_closure_check;
shared_ref.set(IntOrStr::Int(arg));
};
bare_closures.push(BareFn::new_c_in(int_closure, &SLAB).into_untyped());
let str_closure_dropped = Cell::new(false);
let str_closure_check = SetOnDrop(&str_closure_dropped);
let str_closure = move |arg: &'static str| {
println!("{str_closure_check:?}"); shared_ref.set(IntOrStr::Str(arg));
};
bare_closures.push(BareFn::new_c_in(str_closure, &SLAB).into_untyped());
unsafe {
let takes_int: unsafe extern "C" fn(i32) = core::mem::transmute(bare_closures[0].bare());
let takes_str: unsafe extern "C" fn(&'static str) =
core::mem::transmute(bare_closures[1].bare());
takes_str("foo");
assert_eq!(shared.get(), IntOrStr::Str("foo"));
takes_int(42);
assert_eq!(shared.get(), IntOrStr::Int(42));
takes_str("bar");
assert_eq!(shared.get(), IntOrStr::Str("bar"));
}
drop(bare_closures);
assert!(int_closure_dropped.get());
assert!(str_closure_dropped.get());
}
#[test]
fn test_upcast() {
use core::sync::atomic::{AtomicBool, Ordering::SeqCst};
use closure_ffi::{traits::Any, BareFnAny, BareFnSync};
#[derive(Debug)]
struct SetOnDrop<'a>(&'a AtomicBool);
impl Drop for SetOnDrop<'_> {
fn drop(&mut self) {
self.0.store(true, SeqCst);
}
}
let dropped = AtomicBool::new(false);
let check = SetOnDrop(&dropped);
let send_and_sync = BareFnSync::new_c_in(
move || {
let drop_flag = ✓
drop_flag.0.load(SeqCst)
},
&SLAB,
);
let typed_send: BareFnAny<_, dyn Send, _> = send_and_sync.upcast();
assert!(!dropped.load(SeqCst));
let untyped_any: UntypedBareFn<dyn Any, _> = typed_send.into();
assert!(!dropped.load(SeqCst));
unsafe {
let bare: unsafe extern "C" fn() -> bool = core::mem::transmute(untyped_any.bare());
assert!(!bare());
}
drop(untyped_any);
assert!(dropped.load(SeqCst));
}
#[cfg(feature = "std")]
#[test]
fn test_wrap() {
use closure_ffi::{
thunk_factory,
traits::{FnMutThunk, FnPtr, FnThunk},
BareFnSync,
};
fn lock_and_debug<B: FnPtr, F: Send>(fun: F) -> impl FnThunk<B> + Sync
where
for<'a, 'b, 'c> B::Ret<'a, 'b, 'c>: core::fmt::Debug,
(cc::C, F): FnMutThunk<B>,
{
let locked = std::sync::Mutex::new((cc::C, fun));
thunk_factory::make_sync(move |args| unsafe {
let ret = locked.lock().unwrap().call_mut(args);
println!("value: {ret:?}");
ret
})
}
let mut counter = 0;
let locked_inc = BareFnSync::with_thunk_in(
lock_and_debug(|n: usize| {
counter += n;
counter
}),
&SLAB,
);
std::thread::scope(|s| {
for _ in 0..1000 {
s.spawn(|| unsafe { locked_inc.bare()(5) });
}
});
drop(locked_inc);
assert_eq!(counter, 5000);
}