diff --git a/crates/tests/src/block.rs b/crates/tests/src/block.rs index 267abedd7..b1e44155e 100644 --- a/crates/tests/src/block.rs +++ b/crates/tests/src/block.rs @@ -1,8 +1,9 @@ +use alloc::string::ToString; use core::cell::RefCell; +use std::ffi::CStr; use std::thread_local; -use alloc::string::ToString; -use block2::{global_block, Block, RcBlock, StackBlock}; +use block2::{global_block, Block, ManualBlockEncoding, RcBlock, StackBlock}; use objc2::encode::{Encode, Encoding}; use objc2::rc::Retained; use objc2::runtime::{AnyObject, Bool, NSObject}; @@ -35,6 +36,36 @@ unsafe impl Encode for LargeStruct { type Add12 = Block i32>; +struct VoidToVoid; +unsafe impl ManualBlockEncoding for VoidToVoid { + type Arguments = (); + type Return = (); + #[cfg(target_pointer_width = "64")] + const ENCODING_CSTR: &'static CStr = c"v8@?0"; + #[cfg(target_pointer_width = "32")] + const ENCODING_CSTR: &'static CStr = c"v4@?0"; +} + +struct VoidToInt; +unsafe impl ManualBlockEncoding for VoidToInt { + type Arguments = (); + type Return = i32; + #[cfg(target_pointer_width = "64")] + const ENCODING_CSTR: &'static CStr = c"i8@?0"; + #[cfg(target_pointer_width = "32")] + const ENCODING_CSTR: &'static CStr = c"i4@?0"; +} + +struct IntToInt; +unsafe impl ManualBlockEncoding for IntToInt { + type Arguments = (i32,); + type Return = i32; + #[cfg(target_pointer_width = "64")] + const ENCODING_CSTR: &'static CStr = c"i12@?0i8"; + #[cfg(target_pointer_width = "32")] + const ENCODING_CSTR: &'static CStr = c"i8@?0i4"; +} + extern "C" { /// Returns a pointer to a global block that returns 7. fn get_int_block() -> *mut Block i32>; @@ -108,6 +139,14 @@ fn test_int_block() { ); invoke_assert(&StackBlock::new(|| 10), 10); invoke_assert(&RcBlock::new(|| 6), 6); + invoke_assert( + unsafe { &StackBlock::with_encoding::(|| 10) }, + 10, + ); + invoke_assert( + unsafe { &RcBlock::with_encoding::<_, _, _, VoidToInt>(|| 6) }, + 6, + ); invoke_assert(&GLOBAL_BLOCK, 42); } @@ -132,6 +171,14 @@ fn test_add_block() { ); invoke_assert(&StackBlock::new(|a: i32| a + 6), 11); invoke_assert(&RcBlock::new(|a: i32| a + 6), 11); + invoke_assert( + unsafe { &StackBlock::with_encoding::(|a: i32| a + 6) }, + 11, + ); + invoke_assert( + unsafe { &RcBlock::with_encoding::<_, _, _, IntToInt>(|a: i32| a + 6) }, + 11, + ); invoke_assert(&GLOBAL_BLOCK, 47); } @@ -149,6 +196,16 @@ fn test_add_12() { ); } + struct Enc; + unsafe impl ManualBlockEncoding for Enc { + type Arguments = (i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32); + type Return = i32; + #[cfg(target_pointer_width = "64")] + const ENCODING_CSTR: &'static CStr = c"i56@?0i8i12i16i20i24i28i32i36i40i44i48i52"; + #[cfg(target_pointer_width = "32")] + const ENCODING_CSTR: &'static CStr = c"i52@?0i4i8i12i16i20i24i28i32i36i40i44i48"; + } + global_block! { static GLOBAL_BLOCK = | a1: i32, a2: i32, a3: i32, a4: i32, @@ -166,6 +223,11 @@ fn test_add_12() { }; invoke_assert(&StackBlock::new(closure), 78); invoke_assert(&RcBlock::new(closure), 78); + invoke_assert(unsafe { &StackBlock::with_encoding::(closure) }, 78); + invoke_assert( + unsafe { &RcBlock::with_encoding::<_, _, _, Enc>(closure) }, + 78, + ); invoke_assert(&GLOBAL_BLOCK, 120); } @@ -192,6 +254,16 @@ fn test_large_struct_block() { }; } + struct Enc; + unsafe impl ManualBlockEncoding for Enc { + type Arguments = (LargeStruct,); + type Return = LargeStruct; + #[cfg(target_pointer_width = "64")] + const ENCODING_CSTR: &'static CStr = c"{LargeStruct=f[100C]}112@?0{LargeStruct=f[100C]}8"; + #[cfg(target_pointer_width = "32")] + const ENCODING_CSTR: &'static CStr = c"{LargeStruct=f[100C]}108@?0{LargeStruct=f[100C]}4"; + } + let data = LargeStruct::get(); let mut new_data = data; new_data.mutate(); @@ -206,17 +278,31 @@ fn test_large_struct_block() { assert_eq!(unsafe { invoke_large_struct_block(&block, data) }, new_data); let block = block.copy(); assert_eq!(unsafe { invoke_large_struct_block(&block, data) }, new_data); + + let block = unsafe { + StackBlock::with_encoding::(|mut x: LargeStruct| { + x.mutate(); + x + }) + }; + assert_eq!(unsafe { invoke_large_struct_block(&block, data) }, new_data); + let block = block.copy(); + assert_eq!(unsafe { invoke_large_struct_block(&block, data) }, new_data); } #[test] fn test_block_copy() { let s = "Hello!".to_string(); let expected_len = s.len() as i32; - let block = StackBlock::new(move || s.len() as i32); - assert_eq!(unsafe { invoke_int_block(&block) }, expected_len); - - let copied = block.copy(); - assert_eq!(unsafe { invoke_int_block(&copied) }, expected_len); + let closure = move || s.len() as i32; + + for block in [StackBlock::new(closure.clone()), unsafe { + StackBlock::with_encoding::(closure) + }] { + assert_eq!(unsafe { invoke_int_block(&block) }, expected_len); + let copied = block.copy(); + assert_eq!(unsafe { invoke_int_block(&copied) }, expected_len); + } } #[test] @@ -225,9 +311,17 @@ fn test_block_stack_move() { let x = 7; StackBlock::new(move || x) } + fn make_block_with_encoding() -> StackBlock<'static, (), i32, impl Fn() -> i32> { + let x = 7; + unsafe { StackBlock::with_encoding::(move || x) } + } - let block = make_block(); - assert_eq!(unsafe { invoke_int_block(&block) }, 7); + for block in [ + &make_block() as &Block i32>, + &make_block_with_encoding() as &Block i32>, + ] { + assert_eq!(unsafe { invoke_int_block(block) }, 7); + } } #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] @@ -289,117 +383,153 @@ impl Drop for CloneDropTracker { #[test] fn stack_new_clone_drop() { - let mut expected = Count::current(); - - let counter = CloneDropTracker::new(); - expected.new += 1; - expected.assert_current(); - - let block = StackBlock::new(move || { - let _ = &counter; - }); - expected.assert_current(); - - let clone = block.clone(); - expected.clone += 1; - expected.assert_current(); - - drop(clone); - expected.drop += 1; - expected.assert_current(); + macro_rules! test_with { + ($ctor:path) => {{ + let mut expected = Count::current(); + let counter = CloneDropTracker::new(); + expected.new += 1; + expected.assert_current(); + + #[allow(unused_unsafe)] + let block = unsafe { + $ctor(move || { + let _ = &counter; + }) + }; + expected.assert_current(); + + let clone = block.clone(); + expected.clone += 1; + expected.assert_current(); + + drop(clone); + expected.drop += 1; + expected.assert_current(); + + drop(block); + expected.drop += 1; + expected.assert_current(); + }}; + } - drop(block); - expected.drop += 1; - expected.assert_current(); + test_with!(StackBlock::new); + test_with!(StackBlock::with_encoding::); } #[test] fn rc_new_clone_drop() { - let mut expected = Count::current(); - - let counter = CloneDropTracker::new(); - expected.new += 1; - expected.assert_current(); - - let block = RcBlock::new(move || { - let _ = &counter; - }); - expected.assert_current(); - - let clone = block.clone(); - expected.assert_current(); - - drop(clone); - expected.assert_current(); + macro_rules! test_with { + ($ctor:path) => {{ + let mut expected = Count::current(); + let counter = CloneDropTracker::new(); + expected.new += 1; + expected.assert_current(); + + #[allow(unused_unsafe)] + let block = unsafe { + $ctor(move || { + let _ = &counter; + }) + }; + expected.assert_current(); + + let clone = block.clone(); + expected.assert_current(); + + drop(clone); + expected.assert_current(); + + drop(block); + expected.drop += 1; + expected.assert_current(); + }}; + } - drop(block); - expected.drop += 1; - expected.assert_current(); + test_with!(RcBlock::new); + test_with!(RcBlock::with_encoding::<_, _, _, VoidToVoid>); } #[test] fn stack_to_rc() { - let mut expected = Count::current(); - - let counter = CloneDropTracker::new(); - expected.new += 1; - expected.assert_current(); - - let stack = StackBlock::new(move || { - let _ = &counter; - }); - expected.assert_current(); - - let rc1 = stack.copy(); - expected.clone += 1; - expected.assert_current(); - - let rc2 = stack.copy(); - expected.clone += 1; - expected.assert_current(); - - let clone2 = rc2.clone(); - expected.assert_current(); - - drop(rc2); - expected.assert_current(); - - drop(stack); - expected.drop += 1; - expected.assert_current(); - - drop(rc1); - expected.drop += 1; - expected.assert_current(); + macro_rules! test_with { + ($ctor:path) => {{ + let mut expected = Count::current(); + let counter = CloneDropTracker::new(); + expected.new += 1; + expected.assert_current(); + + #[allow(unused_unsafe)] + let stack = unsafe { + $ctor(move || { + let _ = &counter; + }) + }; + expected.assert_current(); + + let rc1 = stack.copy(); + expected.clone += 1; + expected.assert_current(); + + let rc2 = stack.copy(); + expected.clone += 1; + expected.assert_current(); + + let clone2 = rc2.clone(); + expected.assert_current(); + + drop(rc2); + expected.assert_current(); + + drop(stack); + expected.drop += 1; + expected.assert_current(); + + drop(rc1); + expected.drop += 1; + expected.assert_current(); + + drop(clone2); + expected.drop += 1; + expected.assert_current(); + }}; + } - drop(clone2); - expected.drop += 1; - expected.assert_current(); + test_with!(StackBlock::new); + test_with!(StackBlock::with_encoding::); } #[test] fn retain_release_rc_block() { - let mut expected = Count::current(); - - let counter = CloneDropTracker::new(); - expected.new += 1; - expected.assert_current(); - - let block = RcBlock::new(move || { - let _ = &counter; - }); - expected.assert_current(); - - let ptr = &*block as *const Block<_> as *mut AnyObject; - let obj = unsafe { Retained::retain(ptr) }.unwrap(); - expected.assert_current(); - - drop(block); - expected.assert_current(); + macro_rules! test_with { + ($ctor:path) => {{ + let mut expected = Count::current(); + let counter = CloneDropTracker::new(); + expected.new += 1; + expected.assert_current(); + + #[allow(unused_unsafe)] + let block = unsafe { + $ctor(move || { + let _ = &counter; + }) + }; + expected.assert_current(); + + let ptr = &*block as *const Block<_> as *mut AnyObject; + let obj = unsafe { Retained::retain(ptr) }.unwrap(); + expected.assert_current(); + + drop(block); + expected.assert_current(); + + drop(obj); + expected.drop += 1; + expected.assert_current(); + }}; + } - drop(obj); - expected.drop += 1; - expected.assert_current(); + test_with!(RcBlock::new); + test_with!(RcBlock::with_encoding::<_, _, _, VoidToVoid>); } /// Retaining/releasing stack blocks is kinda weird and unsupported. @@ -447,13 +577,27 @@ fn retain_release_stack_block() { #[test] fn capture_id() { - let stack_block = { - let obj1 = NSObject::new(); - let obj2 = NSObject::new(); - StackBlock::new(move || Bool::new(obj1 == obj2)) - }; - - let rc_block = stack_block.copy(); + let obj1 = NSObject::new(); + let obj2 = NSObject::new(); + let closure = move || Bool::new(obj1 == obj2); + + struct Enc; + unsafe impl ManualBlockEncoding for Enc { + type Arguments = (); + type Return = Bool; + + #[cfg(all(target_os = "macos", target_arch = "x86_64"))] + const ENCODING_CSTR: &'static CStr = c"c8@?0"; + #[cfg(all(target_os = "linux", target_pointer_width = "64"))] + const ENCODING_CSTR: &'static CStr = c"C8@?0"; + #[cfg(all(target_os = "linux", target_pointer_width = "32"))] + const ENCODING_CSTR: &'static CStr = c"C4@?0"; + } - assert!(rc_block.call(()).is_false()); + for stack_block in [StackBlock::new(closure.clone()), unsafe { + StackBlock::with_encoding::(closure) + }] { + let rc_block = stack_block.copy(); + assert!(rc_block.call(()).is_false()); + } }