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}