block2/
traits.rs

1use core::ffi::CStr;
2use core::marker::PhantomData;
3use core::mem;
4use core::ptr;
5
6use objc2::encode::EncodeArguments;
7use objc2::encode::{EncodeArgument, EncodeReturn};
8
9use crate::{Block, StackBlock};
10
11mod private {
12    pub trait Sealed<A, R> {}
13}
14
15/// Types that represent closure parameters/arguments and return types in a
16/// block.
17///
18/// This is implemented for [`dyn`] [`Fn`] closures with up to 12 parameters,
19/// where each parameter implements [`EncodeArgument`] and the return type
20/// implements [`EncodeReturn`].
21///
22/// [`dyn`]: https://doc.rust-lang.org/std/keyword.dyn.html
23///
24///
25/// # Safety
26///
27/// This is a sealed trait, and should not need to be implemented. Open an
28/// issue if you know a use-case where this restrition should be lifted!
29pub unsafe trait BlockFn: private::Sealed<Self::Args, Self::Output> {
30    /// The parameters/arguments to the block.
31    type Args: EncodeArguments;
32
33    /// The return type of the block.
34    type Output: EncodeReturn;
35
36    /// Calls the given invoke function with the block and arguments.
37    #[doc(hidden)]
38    unsafe fn __call_block(
39        invoke: unsafe extern "C-unwind" fn(),
40        block: *mut Block<Self>,
41        args: Self::Args,
42    ) -> Self::Output;
43}
44
45/// Types that may be converted into a block.
46///
47/// This is implemented for [`Fn`] closures of up to 12 parameters, where each
48/// parameter implements [`EncodeArgument`] and the return type implements
49/// [`EncodeReturn`].
50///
51///
52/// # Safety
53///
54/// This is a sealed trait, and should not need to be implemented. Open an
55/// issue if you know a use-case where this restrition should be lifted!
56pub unsafe trait IntoBlock<'f, A, R>: private::Sealed<A, R>
57where
58    A: EncodeArguments,
59    R: EncodeReturn,
60{
61    /// The type-erased `dyn Fn(...Args) -> R + 'f`.
62    type Dyn: ?Sized + BlockFn<Args = A, Output = R>;
63
64    #[doc(hidden)]
65    fn __get_invoke_stack_block() -> unsafe extern "C-unwind" fn();
66}
67
68macro_rules! impl_traits {
69    ($($a:ident: $t:ident),*) => (
70        impl<$($t: EncodeArgument,)* R: EncodeReturn, Closure> private::Sealed<($($t,)*), R> for Closure
71        where
72            Closure: ?Sized + Fn($($t),*) -> R,
73        {}
74
75        // TODO: Add `+ Send`, `+ Sync` and `+ Send + Sync` versions.
76        unsafe impl<$($t: EncodeArgument,)* R: EncodeReturn> BlockFn for dyn Fn($($t),*) -> R + '_ {
77            type Args = ($($t,)*);
78            type Output = R;
79
80            #[inline]
81            unsafe fn __call_block(
82                invoke: unsafe extern "C-unwind" fn(),
83                block: *mut Block<Self>,
84                ($($a,)*): Self::Args,
85            ) -> Self::Output {
86                // Very similar to `MessageArguments::__invoke`
87                let invoke: unsafe extern "C-unwind" fn(*mut Block<Self> $(, $t)*) -> R = unsafe {
88                    mem::transmute(invoke)
89                };
90
91                unsafe { invoke(block $(, $a)*) }
92            }
93        }
94
95        unsafe impl<'f, $($t,)* R, Closure> IntoBlock<'f, ($($t,)*), R> for Closure
96        where
97            $($t: EncodeArgument,)*
98            R: EncodeReturn,
99            Closure: Fn($($t),*) -> R + 'f,
100        {
101            type Dyn = dyn Fn($($t),*) -> R + 'f;
102
103            #[inline]
104            fn __get_invoke_stack_block() -> unsafe extern "C-unwind" fn() {
105                unsafe extern "C-unwind" fn invoke<'f, $($t,)* R, Closure>(
106                    block: *mut StackBlock<'f, ($($t,)*), R, Closure>,
107                    $($a: $t,)*
108                ) -> R
109                where
110                    Closure: Fn($($t),*) -> R + 'f
111                {
112                    let closure = unsafe { &*ptr::addr_of!((*block).closure) };
113                    (closure)($($a),*)
114                }
115
116                unsafe {
117                    mem::transmute::<
118                        unsafe extern "C-unwind" fn(*mut StackBlock<'f, ($($t,)*), R, Closure>, $($t,)*) -> R,
119                        unsafe extern "C-unwind" fn(),
120                    >(invoke)
121                }
122            }
123        }
124    );
125}
126
127impl_traits!();
128impl_traits!(t0: T0);
129impl_traits!(t0: T0, t1: T1);
130impl_traits!(t0: T0, t1: T1, t2: T2);
131impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3);
132impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4);
133impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5);
134impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6);
135impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7);
136impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8);
137impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8, t9: T9);
138impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8, t9: T9, t10: T10);
139impl_traits!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8, t9: T9, t10: T10, t11: T11);
140
141/// Interim abstraction to manually provide block encodings for use at compile
142/// time with [`StackBlock::with_encoding`] and [`RcBlock::with_encoding`].
143///
144/// See these functions for examples of how to implement and use this trait,
145/// since its sole purpose is passing values at compile time to them.
146///
147/// As a side note, one might be surprised by the additional [`Self::Arguments`]
148/// and [`Self::Return`] associated types requiring concrete implementations to
149/// specify them while they are not actually used. This is intentional:
150///
151///  * the types are checked at compile-time to be equal to the ones used with
152///    [`RcBlock::with_encoding`] and [`StackBlock::with_encoding`], usually
153///    inferred by the compiler when giving a closure: this should help avoid
154///    some easy oversights;
155///  * the user is forced to write both the standard Rust types and the
156///    encoding string at the same time, so particular attention to the types
157///    is put to the forefront for them;
158///  * reading a block encoding string is tough when not initiated, so these
159///    also serve as self-documentation;
160///  * the safety validation can be moved to the trait implementation, so that
161///    the use can be marked safe.
162///
163/// [`RcBlock::with_encoding`]: crate::RcBlock::with_encoding
164///
165/// # Safety
166///
167/// [`Self::ENCODING_CSTR`] must correspond to the actual signature string a
168/// recent-enough Objective-C compiler would generate for a block taking in
169/// [`Self::Arguments`] as input and returning [`Self::Return`] as output.
170/// This information is actually used by the Objective-C runtime in order to
171/// correctly invoke the block, so specifying a wrong encoding is definitely a
172/// soundness issue: see [this issue comment][i442-sign-check] for more details
173/// about what exactly goes on behind the scenes in order to justify all the
174/// following precautions.
175///
176/// The easiest way to do this is probably to ask Clang; the following program
177/// will print the signature of the block (if you're having trouble linking,
178/// you should be able to find the signature in the assembly output).
179///
180/// ```objective-c
181/// #import <Foundation/Foundation.h>
182///
183/// // Unstable API, but fine for test usage like this.
184/// const char * _Block_signature(void *);
185///
186/// int main() {
187///     // Declare the signature of your block.
188///     // This one takes `id` and `int`, and returns `NSString*`.
189///     id block = ^NSString* (id a, int b) {
190///         return nil;
191///     };
192///
193///     printf("%s\n", _Block_signature((void*)block));
194///     return 0;
195/// }
196/// ```
197///
198/// A more thorough but manual approach is to only follow the rules described
199/// below.
200///
201/// In this process, you may be able to use [`Encoding::to_string`][enc2s] in
202/// order to get the various components of the signature string and then
203/// concatenate them manually with the required numbers (described below)
204/// inserted at their correct place.
205///
206/// [enc2s]: objc2::encode::Encoding#impl-Display-for-Encoding
207/// [i442-sign-check]: https://github.com/madsmtm/objc2/issues/442#issuecomment-2284932726
208///
209/// # Encoding string generation
210///
211/// This is the result of the `@encode` Objective-C compiler directive. The
212/// [Apple documentation] and [GCC documentation] explain how each base type is
213/// encoded into a string representation. See there for a somewhat-formal
214/// specification and a few basic examples. See also [`Encoding`].
215///
216/// See also the [GCC method signatures] section. It is mostly valid for blocks
217/// as well, since they are basically functions with captured environment --
218/// i.e. closures, except that no selector is implicitly sent, only the block
219/// object is. In short, the "signature" is a null-terminated string, composed
220/// of the following, in order:
221///
222///  * The return type, including type qualifiers. For example, a block
223///    returning `int` ([`i32`]) would have `i` here.
224///  * The total size (in bytes) required to pass all the parameters: the call
225///    frame size. This includes the hidden block object parameter that is
226///    passed as a pointer, so at least 4 bytes when under a 32-bit system or
227///    most probably 8 bytes when under a 64-bit one.
228///  * Each argument, with the type encoding, followed by the offset (in bytes)
229///    of the argument in the list of parameters. The first is the always the
230///    hidden block object pointer and is therefore `@?0`.
231///
232/// Examples:
233///
234/// | Objective-C signature    | Runtime encoding                           |
235/// | ------------------------ | ------------------------------------------ |
236/// | `void (^)(void)`         | `v8@?0`                                    |
237/// | `int (^)(void)`          | `i8@?0`                                    |
238/// | `int (^)(float)`         | `i12@?0f8`                                 |
239/// | `int (^)(float, _Bool)`  | `i16@?0f8B12`                              |
240/// | `void (^)(int*)`         | `v16@?0^i8`                                |
241/// | `void (^)(NSError*)`     | `v16@?0@8` or `v16@?0@"NSError"8`          |
242/// | `NSError* (^)(NSError*)` | `@16@?0@8` or `@"NSError"16@?0@"NSError"8` |
243///
244/// [Apple documentation]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
245/// [`Encoding`]: objc2::encode::Encoding
246/// [GCC documentation]: https://gcc.gnu.org/onlinedocs/gcc/Type-encoding.html
247/// [GCC method signatures]: https://gcc.gnu.org/onlinedocs/gcc/Method-signatures.html
248pub unsafe trait ManualBlockEncoding {
249    /// The function's input argument types.
250    type Arguments: EncodeArguments;
251    /// The function's return type.
252    type Return: EncodeReturn;
253    /// The raw encoding information string.
254    const ENCODING_CSTR: &'static CStr;
255}
256
257/// Particular [`ManualBlockEncoding`] that indicates no actual encoding should
258/// be set in the block's descriptor.
259///
260/// This is used in a bit of a hackish way in order to share more code between
261/// the encoded and non-encoded paths.
262pub(crate) struct NoBlockEncoding<A, R>
263where
264    A: EncodeArguments,
265    R: EncodeReturn,
266{
267    _a: PhantomData<A>,
268    _r: PhantomData<R>,
269}
270
271// SAFETY: The encoding here is incorrect, but it will never be used because
272// we specify `IS_NONE = true` in `ManualBlockEncodingExt`.
273unsafe impl<A, R> ManualBlockEncoding for NoBlockEncoding<A, R>
274where
275    A: EncodeArguments,
276    R: EncodeReturn,
277{
278    type Arguments = A;
279    type Return = R;
280    // TODO: change this to `c""` when the MSRV is at least 1.77.
281    // SAFETY: the byte string is written here and contains exactly one nul byte.
282    const ENCODING_CSTR: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
283}
284
285/// Crate-private extension to [`ManualBlockEncoding`].
286pub(crate) trait ManualBlockEncodingExt: ManualBlockEncoding {
287    /// Only `true` for [`NoBlockEncoding`].
288    const IS_NONE: bool;
289}
290
291impl<E: ManualBlockEncoding> ManualBlockEncodingExt for UserSpecified<E> {
292    const IS_NONE: bool = false;
293}
294
295impl<A, R> ManualBlockEncodingExt for NoBlockEncoding<A, R>
296where
297    A: EncodeArguments,
298    R: EncodeReturn,
299{
300    const IS_NONE: bool = true;
301}
302
303/// Dummy newtype used to conditionally implement [`ManualBlockEncodingExt`]
304/// and therefore circumvent the need for specialization.
305#[repr(transparent)]
306pub(crate) struct UserSpecified<E: ManualBlockEncoding>(E);
307
308unsafe impl<E: ManualBlockEncoding> ManualBlockEncoding for UserSpecified<E> {
309    type Arguments = E::Arguments;
310    type Return = E::Return;
311    const ENCODING_CSTR: &'static CStr = E::ENCODING_CSTR;
312}
313
314/// Checks for encoding compatibility between the given generic parameters,
315/// panicking if it is not, but only on `cfg(debug_assertions)` and if `E` is
316/// not none.
317#[cfg_attr(not(debug_assertions), inline(always))]
318#[allow(unused)]
319pub(crate) fn debug_assert_block_encoding<A, R, E>()
320where
321    A: EncodeArguments,
322    R: EncodeReturn,
323    E: ManualBlockEncodingExt<Arguments = A, Return = R>,
324{
325    #[cfg(debug_assertions)]
326    {
327        if !E::IS_NONE {
328            // TODO: relax to check for equivalence instead of strict equality.
329            assert_eq!(
330                E::ENCODING_CSTR,
331                &*crate::encoding::block_signature_string::<A, R>()
332            );
333        }
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use core::ffi::c_char;
340
341    use super::*;
342
343    #[test]
344    fn test_manual_block_encoding_is_none() {
345        // Normal case.
346        struct Enc1;
347        unsafe impl ManualBlockEncoding for Enc1 {
348            type Arguments = (i32, f32);
349            type Return = u8;
350            #[cfg(target_pointer_width = "64")]
351            const ENCODING_CSTR: &'static CStr =
352                // Somehow, using a C string literal seems to fail the MSRV
353                // check here, so use the old way instead here.
354                unsafe { CStr::from_bytes_with_nul_unchecked(b"C16@?0i8f12\0") };
355            #[cfg(not(target_pointer_width = "64"))]
356            const ENCODING_CSTR: &'static CStr =
357                unsafe { CStr::from_bytes_with_nul_unchecked(b"C12@?0i4f8\0") };
358        }
359        // HACK: use `identity` in order to circumvent a Clippy warning.
360        assert!(!core::convert::identity(UserSpecified::<Enc1>::IS_NONE));
361
362        // No input + no output case.
363        struct Enc2;
364        unsafe impl ManualBlockEncoding for Enc2 {
365            type Arguments = ();
366            type Return = ();
367            #[cfg(target_pointer_width = "64")]
368            const ENCODING_CSTR: &'static CStr =
369                unsafe { CStr::from_bytes_with_nul_unchecked(b"v8@?0\0") };
370            #[cfg(not(target_pointer_width = "64"))]
371            const ENCODING_CSTR: &'static CStr =
372                unsafe { CStr::from_bytes_with_nul_unchecked(b"v4@?0\0") };
373        }
374        assert!(!core::convert::identity(UserSpecified::<Enc2>::IS_NONE));
375
376        // Ensure we don't rely on the encoding string's emptiness.
377        struct Enc3;
378        unsafe impl ManualBlockEncoding for Enc3 {
379            type Arguments = ();
380            type Return = ();
381            const ENCODING_CSTR: &'static CStr =
382                unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
383        }
384        assert!(!core::convert::identity(UserSpecified::<Enc3>::IS_NONE));
385
386        // Only `NoBlockEncoding` should be `IS_NONE`.
387        assert!(core::convert::identity(NoBlockEncoding::<(), ()>::IS_NONE));
388        assert!(core::convert::identity(
389            NoBlockEncoding::<(i32, f32), u8>::IS_NONE
390        ));
391        assert!(core::convert::identity(
392            NoBlockEncoding::<(*const u8,), *const c_char>::IS_NONE
393        ));
394    }
395}