-
-
Notifications
You must be signed in to change notification settings - Fork 76
Expand file tree
/
Copy pathlib.rs
More file actions
126 lines (113 loc) · 3.53 KB
/
Copy pathlib.rs
File metadata and controls
126 lines (113 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Rust interface for Objective-C's `@throw` and `@try`/`@catch` statements.
use std::mem;
use std::os::raw::{c_int, c_void};
use std::ptr;
#[link(name = "objc", kind = "dylib")]
// TODO: "C-unwind"
extern "C" {
/// See [`objc-exception.h`][objc-exception].
///
/// [objc-exception]: https://opensource.apple.com/source/objc4/objc4-818.2/runtime/objc-exception.h.auto.html
// Header marks this with _Nonnull, but LLVM output shows otherwise
fn objc_exception_throw(exception: *mut c_void) -> !;
// fn objc_exception_rethrow();
}
extern "C" {
fn RustObjCExceptionTryCatch(
r#try: extern "C" fn(*mut c_void),
context: *mut c_void,
error: *mut *mut c_void,
) -> c_int;
}
/// An opaque type representing any Objective-C object thrown as an exception.
pub enum Exception {}
/// Throws an Objective-C exception.
/// The argument must be a pointer to an Objective-C object.
///
/// # Safety
///
/// This unwinds from Objective-C, and the exception must be caught using an
/// Objective-C exception handler.
///
/// This also invokes undefined behaviour until `C-unwind` is stabilized, see
/// [RFC-2945].
///
/// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
#[inline]
pub unsafe fn throw(exception: *mut Exception) -> ! {
objc_exception_throw(exception as *mut _)
}
unsafe fn try_no_ret<F>(closure: F) -> Result<(), *mut Exception>
where
F: FnOnce(),
{
extern "C" fn try_objc_execute_closure<F>(closure: &mut Option<F>)
where
F: FnOnce(),
{
// This is always passed Some, so it's safe to unwrap
let closure = closure.take().unwrap();
closure();
}
let f: extern "C" fn(&mut Option<F>) = try_objc_execute_closure;
let f: extern "C" fn(*mut c_void) = mem::transmute(f);
// Wrap the closure in an Option so it can be taken
let mut closure = Some(closure);
let context = &mut closure as *mut _ as *mut c_void;
let mut exception = ptr::null_mut();
let success = RustObjCExceptionTryCatch(f, context, &mut exception);
if success == 0 {
Ok(())
} else {
Err(exception as *mut _)
}
}
/// Tries to execute the given closure and catches an Objective-C exception
/// if one is thrown.
///
/// Returns a `Result` that is either `Ok` if the closure succeeded without an
/// exception being thrown, or an `Err` with a pointer to an exception if one
/// was thrown. The exception is retained and so must be released.
///
/// # Safety
///
/// This encourages unwinding through the closure from
/// Objective-C, which is not safe.
pub unsafe fn r#try<F, R>(closure: F) -> Result<R, *mut Exception>
where
F: FnOnce() -> R,
{
let mut value = None;
let result = {
let value_ref = &mut value;
try_no_ret(move || {
*value_ref = Some(closure());
})
};
// If the try succeeded, this was set so it's safe to unwrap
result.map(|_| value.unwrap())
}
#[cfg(test)]
mod tests {
use super::{r#try, throw};
use std::ptr;
#[test]
fn test_try() {
unsafe {
let s = "Hello".to_string();
let result = r#try(move || {
if !s.is_empty() {
throw(ptr::null_mut());
}
s.len()
});
assert!(result.unwrap_err() == ptr::null_mut());
let mut s = "Hello".to_string();
let result = r#try(move || {
s.push_str(", World!");
s
});
assert!(result.unwrap() == "Hello, World!");
}
}
}