1
+ //! Objective-C's @throw and @try/@catch.
2
+ //!
3
+ //! This is only available when the `exception` feature is enabled.
4
+ //!
5
+ //! See the following links for more information:
6
+ //! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html>
7
+ //! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocExceptionHandling.html>
8
+ //! - <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html>
9
+ //! - <https://llvm.org/docs/ExceptionHandling.html>
10
+
11
+ use core:: ffi:: c_void;
12
+ use core:: mem;
13
+ use core:: ptr;
1
14
use core:: ptr:: NonNull ;
15
+ use std:: os:: raw:: c_uchar;
2
16
3
17
use crate :: rc:: { Id , Shared } ;
4
18
use crate :: runtime:: Object ;
5
- use objc2_exception:: r#try;
6
19
7
- // Comment copied from `objc2_exception`
20
+ use objc2_sys:: { objc_exception_throw, objc_object} ;
21
+
22
+ extern "C" {
23
+ fn rust_objc_try_catch_exception (
24
+ f : extern "C" fn ( * mut c_void ) ,
25
+ context : * mut c_void ,
26
+ error : * mut * mut objc_object ,
27
+ ) -> c_uchar ;
28
+ }
29
+
30
+ /// Throws an Objective-C exception.
31
+ ///
32
+ /// The argument must be a pointer to an Objective-C object.
33
+ ///
34
+ /// # Safety
35
+ ///
36
+ /// This unwinds from Objective-C, and the exception must be caught using an
37
+ /// Objective-C exception handler like [`catch`] (and specifically not
38
+ /// [`catch_unwind`]).
39
+ ///
40
+ /// This also invokes undefined behaviour until `C-unwind` is stabilized, see
41
+ /// [RFC-2945].
42
+ ///
43
+ /// [`catch_unwind`]: std::panic::catch_unwind
44
+ /// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
45
+ #[ inline]
46
+ pub unsafe fn throw ( exception : Option < & Id < Object , Shared > > ) -> ! {
47
+ let exception = match exception {
48
+ Some ( id) => & * * id as * const Object as * mut objc_object ,
49
+ None => ptr:: null_mut ( ) ,
50
+ } ;
51
+ objc_exception_throw ( exception)
52
+ }
53
+
54
+ unsafe fn try_no_ret < F : FnOnce ( ) > ( closure : F ) -> Result < ( ) , Option < Id < Object , Shared > > > {
55
+ extern "C" fn try_objc_execute_closure < F : FnOnce ( ) > ( closure : & mut Option < F > ) {
56
+ // This is always passed Some, so it's safe to unwrap
57
+ let closure = closure. take ( ) . unwrap ( ) ;
58
+ closure ( ) ;
59
+ }
60
+
61
+ let f: extern "C" fn ( & mut Option < F > ) = try_objc_execute_closure;
62
+ let f: extern "C" fn ( * mut c_void ) = mem:: transmute ( f) ;
63
+ // Wrap the closure in an Option so it can be taken
64
+ let mut closure = Some ( closure) ;
65
+ let context = & mut closure as * mut _ as * mut c_void ;
66
+
67
+ let mut exception = ptr:: null_mut ( ) ;
68
+ let success = rust_objc_try_catch_exception ( f, context, & mut exception) ;
69
+
70
+ if success == 0 {
71
+ Ok ( ( ) )
72
+ } else {
73
+ // SAFETY:
74
+ // The exception is always a valid object (or NULL, but that has been
75
+ // checked).
76
+ //
77
+ // The ownership is safe as Shared; Objective-C code throwing an
78
+ // exception knows that they don't hold sole access to that exception
79
+ // instance any more, and Rust code is forbidden by requiring a Shared
80
+ // Id in `throw` (instead of just a shared reference, which could have
81
+ // come from an Owned Id).
82
+ Err ( NonNull :: new ( exception as * mut Object ) . map ( |e| Id :: new ( e) ) )
83
+ }
84
+ }
8
85
9
86
/// Tries to execute the given closure and catches an Objective-C exception
10
87
/// if one is thrown.
@@ -15,14 +92,64 @@ use objc2_exception::r#try;
15
92
///
16
93
/// # Safety
17
94
///
18
- /// The given closure must not panic.
95
+ /// The given closure must not panic (e.g. normal Rust unwinding into this
96
+ /// causes undefined behaviour).
19
97
///
20
98
/// Additionally, this unwinds through the closure from Objective-C, which is
21
99
/// undefined behaviour until `C-unwind` is stabilized, see [RFC-2945].
22
100
///
23
101
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
24
- pub unsafe fn catch_exception < R > (
25
- closure : impl FnOnce ( ) -> R ,
26
- ) -> Result < R , Option < Id < Object , Shared > > > {
27
- r#try ( closure) . map_err ( |e| NonNull :: new ( e) . map ( |e| Id :: new ( e. cast ( ) ) ) )
102
+ pub unsafe fn catch < R > ( closure : impl FnOnce ( ) -> R ) -> Result < R , Option < Id < Object , Shared > > > {
103
+ let mut value = None ;
104
+ let result = {
105
+ let value_ref = & mut value;
106
+ try_no_ret ( move || {
107
+ * value_ref = Some ( closure ( ) ) ;
108
+ } )
109
+ } ;
110
+ // If the try succeeded, this was set so it's safe to unwrap
111
+ result. map ( |_| value. unwrap ( ) )
112
+ }
113
+
114
+ #[ cfg( test) ]
115
+ mod tests {
116
+ use alloc:: string:: ToString ;
117
+
118
+ use super :: * ;
119
+
120
+ #[ test]
121
+ fn test_catch ( ) {
122
+ let mut s = "Hello" . to_string ( ) ;
123
+ let result = unsafe {
124
+ catch ( move || {
125
+ s. push_str ( ", World!" ) ;
126
+ s
127
+ } )
128
+ } ;
129
+ assert_eq ! ( result. unwrap( ) , "Hello, World!" ) ;
130
+ }
131
+
132
+ #[ test]
133
+ fn test_throw_catch_none ( ) {
134
+ let s = "Hello" . to_string ( ) ;
135
+ let result = unsafe {
136
+ catch ( move || {
137
+ if !s. is_empty ( ) {
138
+ throw ( None ) ;
139
+ }
140
+ s. len ( )
141
+ } )
142
+ } ;
143
+ assert ! ( result. unwrap_err( ) . is_none( ) ) ;
144
+ }
145
+
146
+ #[ test]
147
+ fn test_throw_catch_object ( ) {
148
+ let obj: Id < Object , Shared > = unsafe { Id :: new ( msg_send ! [ class!( NSObject ) , new] ) } ;
149
+
150
+ let result = unsafe { catch ( || throw ( Some ( & obj) ) ) } ;
151
+ let e = result. unwrap_err ( ) . unwrap ( ) ;
152
+ // Compare pointers
153
+ assert_eq ! ( & * e as * const Object , & * obj as * const Object ) ;
154
+ }
28
155
}
0 commit comments