Skip to content

Commit

Permalink
Feat(block2): Add new with_encoding unsafe block constructors
Browse files Browse the repository at this point in the history
Signed-off-by: Paul Mabileau <[email protected]>
  • Loading branch information
PaulDance committed Jul 17, 2024
1 parent 581c372 commit f796013
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/block2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ unstable-private = []
[dependencies]
objc2 = { path = "../objc2", version = "0.5.2", default-features = false }

[dev-dependencies.objc2-foundation]
path = "../../framework-crates/objc2-foundation"
version = "0.2.2"
default-features = false
features = ["NSError"]

[package.metadata.docs.rs]
default-target = "aarch64-apple-darwin"
features = ["unstable-private"]
Expand Down
2 changes: 1 addition & 1 deletion crates/block2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ pub use self::block::Block;
pub use self::global::GlobalBlock;
pub use self::rc_block::RcBlock;
pub use self::stack::StackBlock;
pub use self::traits::{BlockFn, IntoBlock};
pub use self::traits::{BlockFn, IntoBlock, ManualBlockEncoding};

/// Deprecated alias for a `'static` `StackBlock`.
#[deprecated = "renamed to `StackBlock`"]
Expand Down
53 changes: 50 additions & 3 deletions crates/block2/src/rc_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use objc2::encode::{EncodeArguments, EncodeReturn};

use crate::abi::BlockHeader;
use crate::debug::debug_block_header;
use crate::traits::{ManualBlockEncoding, NoBlockEncoding};
use crate::{ffi, Block, IntoBlock, StackBlock};

/// A reference-counted Objective-C block that is stored on the heap.
Expand Down Expand Up @@ -91,14 +92,58 @@ impl<F: ?Sized> RcBlock<F> {
///
/// When the block is called, it will return the value that results from
/// calling the closure.
//
#[inline]
pub fn new<'f, A, R, Closure>(closure: Closure) -> Self
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R, Dyn = F>,
{
// SAFETY: no encoding is given.
unsafe { Self::with_encoding::<_, _, _, NoBlockEncoding<A, R>>(closure) }
}

/// Constructs a new [`RcBlock`] with the given function and encoding
/// information.
///
/// See [`StackBlock::with_encoding`] as to why and how this could be
/// useful. The same requirements as [`Self::new`] apply here as well.
///
/// # Safety
///
/// The raw encoding string given through `E` must be correct with respect
/// to the given function's input and output types: see
/// [`ManualBlockEncoding`].
///
/// # Example
///
/// ```
/// # use core::ffi::CStr;
/// # use block2::{Block, ManualBlockEncoding, RcBlock};
/// # use objc2_foundation::NSError;
/// #
/// struct MyBlockEncoding;
/// unsafe impl ManualBlockEncoding for MyBlockEncoding {
/// type Arguments = (*mut NSError,);
/// type Return = i32;
/// const ENCODING_CSTR: &'static CStr = cr#"i16@?0@"NSError"8"#;
/// }
///
/// let my_block = unsafe {
/// RcBlock::with_encoding::<_, _, _, MyBlockEncoding>(|_err: *mut NSError| {
/// 42i32
/// })
/// };
/// assert_eq!(my_block.call((std::ptr::null_mut(),)), 42);
/// ```
// Note: Unsure if this should be #[inline], but I think it may be able to
// benefit from not being so.
pub fn new<'f, A, R, Closure>(closure: Closure) -> Self
pub unsafe fn with_encoding<'f, A, R, Closure, E>(closure: Closure) -> Self
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R, Dyn = F>,
E: ManualBlockEncoding<Arguments = A, Return = R>,
{
// SAFETY: The stack block is copied once below.
//
Expand All @@ -108,7 +153,9 @@ impl<F: ?Sized> RcBlock<F> {
//
// Clang doesn't do this optimization either.
// <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/clang/lib/CodeGen/CGBlocks.cpp#L281-L284>
let block = unsafe { StackBlock::new_no_clone(closure) };
//
// Encoding safety is supposed to be upheld by the caller.
let block = unsafe { StackBlock::new_no_clone::<E>(closure) };

// Transfer ownership from the stack to the heap.
let mut block = ManuallyDrop::new(block);
Expand Down
187 changes: 168 additions & 19 deletions crates/block2/src/stack.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use core::ffi::c_void;
use core::ffi::{c_void, CStr};
use core::fmt;
use core::marker::PhantomData;
use core::mem::{self, MaybeUninit};
Expand All @@ -10,9 +10,11 @@ use std::os::raw::c_ulong;
use objc2::encode::{EncodeArguments, EncodeReturn, Encoding, RefEncode};

use crate::abi::{
BlockDescriptor, BlockDescriptorCopyDispose, BlockDescriptorPtr, BlockFlags, BlockHeader,
BlockDescriptor, BlockDescriptorCopyDispose, BlockDescriptorCopyDisposeSignature,
BlockDescriptorPtr, BlockDescriptorSignature, BlockFlags, BlockHeader,
};
use crate::debug::debug_block_header;
use crate::traits::{ManualBlockEncoding, ManualBlockEncodingExt, NoBlockEncoding};
use crate::{ffi, Block, IntoBlock};

/// An Objective-C block constructed on the stack.
Expand Down Expand Up @@ -103,6 +105,17 @@ impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
reserved: 0,
size: Self::SIZE,
};

/// The [`BlockDescriptorSignature`] corresponding to
/// [`Self::DESCRIPTOR_BASIC`] with the given `encoding` additionally set.
#[inline(always)]
const fn descriptor_basic_with_encoding(encoding: &'static CStr) -> BlockDescriptorSignature {
BlockDescriptorSignature {
reserved: Self::DESCRIPTOR_BASIC.reserved,
size: Self::DESCRIPTOR_BASIC.size,
encoding: encoding.as_ptr(),
}
}
}

// `StackBlock::new`
Expand Down Expand Up @@ -140,9 +153,30 @@ impl<'f, A, R, Closure: Clone> StackBlock<'f, A, R, Closure> {
copy: Some(Self::clone_closure),
dispose: Some(Self::drop_closure),
};

/// The [`BlockDescriptorCopyDisposeSignature`] corresponding to
/// [`Self::DESCRIPTOR_WITH_CLONE`] with the given `encoding` additionally
/// set.
#[inline(always)]
const fn descriptor_with_clone_and_encoding(
encoding: &'static CStr,
) -> BlockDescriptorCopyDisposeSignature {
BlockDescriptorCopyDisposeSignature {
reserved: Self::DESCRIPTOR_WITH_CLONE.reserved,
size: Self::DESCRIPTOR_WITH_CLONE.size,
copy: Self::DESCRIPTOR_WITH_CLONE.copy,
dispose: Self::DESCRIPTOR_WITH_CLONE.dispose,
encoding: encoding.as_ptr(),
}
}
}

impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure>
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R> + Clone,
{
/// Construct a `StackBlock` with the given closure.
///
/// Note that this requires [`Clone`], as a C block is generally assumed
Expand All @@ -154,21 +188,90 @@ impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
///
/// [`RcBlock::new`]: crate::RcBlock::new
#[inline]
pub fn new(closure: Closure) -> Self
pub fn new(closure: Closure) -> Self {
// SAFETY: no encoding is given.
unsafe { Self::with_encoding::<NoBlockEncoding<A, R>>(closure) }
}

/// Constructs a new [`StackBlock`] with the given function and encoding
/// information.
///
/// Some particular newer-ish Apple Objective-C APIs expect the block they
/// are given to be created with encoding information set in the block
/// object itself and crash the calling process if they fail to find it,
/// which renders them pretty much unusable with only [`Self::new`] that
/// currently does not set such encoding information. This is for example
/// the case in [`FileProvider`] for [`NSFileProviderManager`]'s
/// [`reimportItemsBelowItemWithIdentifier:completionHandler:`] and
/// [`waitForStabilizationWithCompletionHandler:`], but also in
/// [`NetworkExtension`] for [`NEFilterDataProvider`]'s
/// [`applySettings:completionHandler`]. A complete list of such APIs may
/// not be easily obtained, though.
///
/// This encoding information string could technically be generated at
/// compile time using the generic parameters already available to
/// [`Self::new`]. However, doing so would require some constant evaluation
/// features that are yet to be implemented and stabilized in the Rust
/// compiler. This function is therefore exposed in the meantime so users
/// may still be able to call the concerned APIs by providing the raw
/// encoding information string themselves, thus obtaining a block
/// containing it and working with these APIs.
///
/// The same requirements as [`Self::new`] apply here as well.
///
/// [`FileProvider`]: https://developer.apple.com/documentation/fileprovider?language=objc
/// [`NSFileProviderManager`]: https://developer.apple.com/documentation/fileprovider/nsfileprovidermanager?language=objc
/// [`reimportItemsBelowItemWithIdentifier:completionHandler:`]: https://developer.apple.com/documentation/fileprovider/nsfileprovidermanager/reimportitems(below:completionhandler:)?language=objc
/// [`waitForStabilizationWithCompletionHandler:`]: https://developer.apple.com/documentation/fileprovider/nsfileprovidermanager/waitforstabilization(completionhandler:)?language=objc
/// [`NetworkExtension`]: https://developer.apple.com/documentation/networkextension?language=objc
/// [`NEFilterDataProvider`]: https://developer.apple.com/documentation/networkextension/nefilterdataprovider?language=objc
/// [`applySettings:completionHandler`]: https://developer.apple.com/documentation/networkextension/nefilterdataprovider/3181998-applysettings?language=objc
///
/// # Safety
///
/// The raw encoding string given through `E` must be correct with respect
/// to the given function's input and output types: see
/// [`ManualBlockEncoding`].
///
/// # Example
///
/// ```
/// # use core::ffi::CStr;
/// # use block2::{Block, ManualBlockEncoding, StackBlock};
/// # use objc2_foundation::NSError;
/// #
/// struct MyBlockEncoding;
/// unsafe impl ManualBlockEncoding for MyBlockEncoding {
/// type Arguments = (*mut NSError,);
/// type Return = i32;
/// const ENCODING_CSTR: &'static CStr = cr#"i16@?0@"NSError"8"#;
/// }
///
/// let my_block = unsafe {
/// StackBlock::with_encoding::<MyBlockEncoding>(|_err: *mut NSError| {
/// 42i32
/// })
/// };
/// assert_eq!(my_block.call((std::ptr::null_mut(),)), 42);
/// ```
#[inline]
pub unsafe fn with_encoding<E>(closure: Closure) -> Self
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R> + Clone,
E: ManualBlockEncoding<Arguments = A, Return = R>,
{
let header = BlockHeader {
isa: unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) },
// TODO: Add signature.
flags: BlockFlags::BLOCK_HAS_COPY_DISPOSE,
flags: BlockFlags::BLOCK_HAS_COPY_DISPOSE
| if !E::is_none() {
BlockFlags::BLOCK_HAS_SIGNATURE
} else {
BlockFlags::EMPTY
},
reserved: MaybeUninit::new(0),
invoke: Some(Closure::__get_invoke_stack_block()),
// TODO: Use `Self::DESCRIPTOR_BASIC` when `F: Copy`
// (probably only possible with specialization).
descriptor: BlockDescriptorPtr {
descriptor: if E::is_none() {
// SAFETY: The descriptor must (probably) point to `static`
// memory, as Objective-C code may assume the block's
// descriptor to be alive past the lifetime of the block
Expand All @@ -184,7 +287,16 @@ impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
// does not contain `UnsafeCell` (it does not).
//
// [promotion]: https://doc.rust-lang.org/reference/destructors.html#constant-promotion
with_copy_dispose: &Self::DESCRIPTOR_WITH_CLONE,
BlockDescriptorPtr {
with_copy_dispose: &Self::DESCRIPTOR_WITH_CLONE,
}
} else {
// SAFETY: promotion is guaranteed with the used inline const.
BlockDescriptorPtr {
with_copy_dispose_signature: &const {
Self::descriptor_with_clone_and_encoding(E::ENCODING_CSTR)
},
}
},
};
Self {
Expand All @@ -195,11 +307,11 @@ impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
}
}

// `RcBlock::new`
// `RcBlock::with_encoding`
impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
crate::__c_unwind! {unsafe extern "C" fn empty_clone_closure(_dst: *mut c_void, _src: *const c_void) {
// We do nothing, the closure has been `memmove`'d already, and
// ownership will be passed in `RcBlock::new`.
// ownership will be passed in `RcBlock::with_encoding`.
}}

const DESCRIPTOR_WITH_DROP: BlockDescriptorCopyDispose = BlockDescriptorCopyDispose {
Expand All @@ -209,34 +321,71 @@ impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
dispose: Some(Self::drop_closure),
};

/// The [`BlockDescriptorCopyDisposeSignature`] corresponding to
/// [`Self::DESCRIPTOR_WITH_DROP`] with the given `encoding` additionally
/// set.
#[inline(always)]
const fn descriptor_with_drop_and_encoding(
encoding: &'static CStr,
) -> BlockDescriptorCopyDisposeSignature {
BlockDescriptorCopyDisposeSignature {
reserved: Self::DESCRIPTOR_WITH_DROP.reserved,
size: Self::DESCRIPTOR_WITH_DROP.size,
copy: Self::DESCRIPTOR_WITH_DROP.copy,
dispose: Self::DESCRIPTOR_WITH_DROP.dispose,
encoding: encoding.as_ptr(),
}
}

/// # Safety
///
/// `_Block_copy` must be called on the resulting stack block only once.
/// * `_Block_copy` must be called on the resulting stack block only once.
/// * Encoding must be correct with respect to the given function's input
/// and output types: see [`ManualBlockEncoding`].
#[inline]
pub(crate) unsafe fn new_no_clone(closure: Closure) -> Self
pub(crate) unsafe fn new_no_clone<E>(closure: Closure) -> Self
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R>,
E: ManualBlockEncoding<Arguments = A, Return = R>,
{
// Don't need to emit copy and dispose helpers if the closure
// doesn't need it.
//
// TODO: Add signature.
let flags = if mem::needs_drop::<Self>() {
BlockFlags::BLOCK_HAS_COPY_DISPOSE
} else {
BlockFlags::EMPTY
} | if !E::is_none() {
BlockFlags::BLOCK_HAS_SIGNATURE
} else {
BlockFlags::EMPTY
};
// See discussion in `new` above with regards to the safety of the
// pointer to the descriptor.
let descriptor = if mem::needs_drop::<Self>() {
if E::is_none() {
// SAFETY: see above.
BlockDescriptorPtr {
with_copy_dispose: &Self::DESCRIPTOR_WITH_DROP,
}
} else {
// SAFETY: promotion is guaranteed with the used inline const.
BlockDescriptorPtr {
with_copy_dispose_signature: &const {
Self::descriptor_with_drop_and_encoding(E::ENCODING_CSTR)
},
}
}
} else if E::is_none() {
// SAFETY: see above.
BlockDescriptorPtr {
with_copy_dispose: &Self::DESCRIPTOR_WITH_DROP,
basic: &Self::DESCRIPTOR_BASIC,
}
} else {
// SAFETY: promotion is guaranteed with the used inline const.
BlockDescriptorPtr {
basic: &Self::DESCRIPTOR_BASIC,
with_signature: &const { Self::descriptor_basic_with_encoding(E::ENCODING_CSTR) },
}
};

Expand Down
Loading

0 comments on commit f796013

Please sign in to comment.