1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 //! # Low-level support for calling rust functions
6 //!
7 //! This module helps the scaffolding code make calls to rust functions and pass back the result to the FFI bindings code.
8 //!
9 //! It handles:
10 //!    - Catching panics
11 //!    - Adapting the result of `Return::lower_return()` into either a return value or an
12 //!      exception
13 
14 use crate::{FfiDefault, Lower, RustBuffer, UniFfiTag};
15 use std::mem::MaybeUninit;
16 use std::panic;
17 
18 /// Represents the success/error of a rust call
19 ///
20 /// ## Usage
21 ///
22 /// - The consumer code creates a [RustCallStatus] with an empty [RustBuffer] and
23 ///   [RustCallStatusCode::Success] (0) as the status code
24 /// - A pointer to this object is passed to the rust FFI function.  This is an
25 ///   "out parameter" which will be updated with any error that occurred during the function's
26 ///   execution.
27 /// - After the call, if `code` is [RustCallStatusCode::Error] or [RustCallStatusCode::UnexpectedError]
28 ///   then `error_buf` will be updated to contain a serialized error object.   See
29 ///   [RustCallStatusCode] for what gets serialized. The consumer is responsible for freeing `error_buf`.
30 ///
31 /// ## Layout/fields
32 ///
33 /// The layout of this struct is important since consumers on the other side of the FFI need to
34 /// construct it.  If this were a C struct, it would look like:
35 ///
36 /// ```c,no_run
37 /// struct RustCallStatus {
38 ///     int8_t code;
39 ///     RustBuffer error_buf;
40 /// };
41 /// ```
42 #[repr(C)]
43 pub struct RustCallStatus {
44     pub code: RustCallStatusCode,
45     // code is signed because unsigned types are experimental in Kotlin
46     pub error_buf: MaybeUninit<RustBuffer>,
47     // error_buf is MaybeUninit to avoid dropping the value that the consumer code sends in:
48     //   - Consumers should send in a zeroed out RustBuffer.  In this case dropping is a no-op and
49     //     avoiding the drop is a small optimization.
50     //   - If consumers pass in invalid data, then we should avoid trying to drop it.  In
51     //     particular, we don't want to try to free any data the consumer has allocated.
52     //
53     // `MaybeUninit` requires unsafe code, since we are preventing rust from dropping the value.
54     // To use this safely we need to make sure that no code paths set this twice, since that will
55     // leak the first `RustBuffer`.
56 }
57 
58 impl RustCallStatus {
new() -> Self59     pub fn new() -> Self {
60         Self {
61             code: RustCallStatusCode::Success,
62             error_buf: MaybeUninit::new(RustBuffer::new()),
63         }
64     }
65 
cancelled() -> Self66     pub fn cancelled() -> Self {
67         Self {
68             code: RustCallStatusCode::Cancelled,
69             error_buf: MaybeUninit::new(RustBuffer::new()),
70         }
71     }
72 
error(message: impl Into<String>) -> Self73     pub fn error(message: impl Into<String>) -> Self {
74         Self {
75             code: RustCallStatusCode::UnexpectedError,
76             error_buf: MaybeUninit::new(<String as Lower<UniFfiTag>>::lower(message.into())),
77         }
78     }
79 }
80 
81 impl Default for RustCallStatus {
default() -> Self82     fn default() -> Self {
83         Self {
84             code: RustCallStatusCode::Success,
85             error_buf: MaybeUninit::uninit(),
86         }
87     }
88 }
89 
90 /// Result of a FFI call to a Rust function
91 #[repr(i8)]
92 #[derive(Debug, PartialEq, Eq)]
93 pub enum RustCallStatusCode {
94     /// Successful call.
95     Success = 0,
96     /// Expected error, corresponding to the `Result::Err` variant.  [RustCallStatus::error_buf]
97     /// will contain the serialized error.
98     Error = 1,
99     /// Unexpected error.  [RustCallStatus::error_buf] will contain a serialized message string
100     UnexpectedError = 2,
101     /// Async function cancelled.  [RustCallStatus::error_buf] will be empty and does not need to
102     /// be freed.
103     ///
104     /// This is only returned for async functions and only if the bindings code uses the
105     /// [rust_future_cancel] call.
106     Cancelled = 3,
107 }
108 
109 /// Handle a scaffolding calls
110 ///
111 /// `callback` is responsible for making the actual Rust call and returning a special result type:
112 ///   - For successful calls, return `Ok(value)`
113 ///   - For errors that should be translated into thrown exceptions in the foreign code, serialize
114 ///     the error into a `RustBuffer`, then return `Ok(buf)`
115 ///   - The success type, must implement `FfiDefault`.
116 ///   - `Return::lower_return` returns `Result<>` types that meet the above criteria>
117 /// - If the function returns a `Ok` value it will be unwrapped and returned
118 /// - If the function returns a `Err` value:
119 ///     - `out_status.code` will be set to [RustCallStatusCode::Error].
120 ///     - `out_status.error_buf` will be set to a newly allocated `RustBuffer` containing the error.  The calling
121 ///       code is responsible for freeing the `RustBuffer`
122 ///     - `FfiDefault::ffi_default()` is returned, although foreign code should ignore this value
123 /// - If the function panics:
124 ///     - `out_status.code` will be set to `CALL_PANIC`
125 ///     - `out_status.error_buf` will be set to a newly allocated `RustBuffer` containing a
126 ///       serialized error message.  The calling code is responsible for freeing the `RustBuffer`
127 ///     - `FfiDefault::ffi_default()` is returned, although foreign code should ignore this value
rust_call<F, R>(out_status: &mut RustCallStatus, callback: F) -> R where F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>, R: FfiDefault,128 pub fn rust_call<F, R>(out_status: &mut RustCallStatus, callback: F) -> R
129 where
130     F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>,
131     R: FfiDefault,
132 {
133     rust_call_with_out_status(out_status, callback).unwrap_or_else(R::ffi_default)
134 }
135 
136 /// Make a Rust call and update `RustCallStatus` based on the result.
137 ///
138 /// If the call succeeds this returns Some(v) and doesn't touch out_status
139 /// If the call fails (including Err results), this returns None and updates out_status
140 ///
141 /// This contains the shared code between `rust_call` and `rustfuture::do_wake`.
rust_call_with_out_status<F, R>( out_status: &mut RustCallStatus, callback: F, ) -> Option<R> where F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>,142 pub(crate) fn rust_call_with_out_status<F, R>(
143     out_status: &mut RustCallStatus,
144     callback: F,
145 ) -> Option<R>
146 where
147     F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>,
148 {
149     let result = panic::catch_unwind(|| {
150         crate::panichook::ensure_setup();
151         callback()
152     });
153     match result {
154         // Happy path.  Note: no need to update out_status in this case because the calling code
155         // initializes it to [RustCallStatusCode::Success]
156         Ok(Ok(v)) => Some(v),
157         // Callback returned an Err.
158         Ok(Err(buf)) => {
159             out_status.code = RustCallStatusCode::Error;
160             unsafe {
161                 // Unsafe because we're setting the `MaybeUninit` value, see above for safety
162                 // invariants.
163                 out_status.error_buf.as_mut_ptr().write(buf);
164             }
165             None
166         }
167         // Callback panicked
168         Err(cause) => {
169             out_status.code = RustCallStatusCode::UnexpectedError;
170             // Try to coerce the cause into a RustBuffer containing a String.  Since this code can
171             // panic, we need to use a second catch_unwind().
172             let message_result = panic::catch_unwind(panic::AssertUnwindSafe(move || {
173                 // The documentation suggests that it will *usually* be a str or String.
174                 let message = if let Some(s) = cause.downcast_ref::<&'static str>() {
175                     (*s).to_string()
176                 } else if let Some(s) = cause.downcast_ref::<String>() {
177                     s.clone()
178                 } else {
179                     "Unknown panic!".to_string()
180                 };
181                 log::error!("Caught a panic calling rust code: {:?}", message);
182                 <String as Lower<UniFfiTag>>::lower(message)
183             }));
184             if let Ok(buf) = message_result {
185                 unsafe {
186                     // Unsafe because we're setting the `MaybeUninit` value, see above for safety
187                     // invariants.
188                     out_status.error_buf.as_mut_ptr().write(buf);
189                 }
190             }
191             // Ignore the error case.  We've done all that we can at this point.  In the bindings
192             // code, we handle this by checking if `error_buf` still has an empty `RustBuffer` and
193             // using a generic message.
194             None
195         }
196     }
197 }
198 
199 #[cfg(test)]
200 mod test {
201     use super::*;
202     use crate::{test_util::TestError, Lift, LowerReturn};
203 
create_call_status() -> RustCallStatus204     fn create_call_status() -> RustCallStatus {
205         RustCallStatus {
206             code: RustCallStatusCode::Success,
207             error_buf: MaybeUninit::new(RustBuffer::new()),
208         }
209     }
210 
test_callback(a: u8) -> Result<i8, TestError>211     fn test_callback(a: u8) -> Result<i8, TestError> {
212         match a {
213             0 => Ok(100),
214             1 => Err(TestError("Error".to_owned())),
215             x => panic!("Unexpected value: {x}"),
216         }
217     }
218 
219     #[test]
test_rust_call()220     fn test_rust_call() {
221         let mut status = create_call_status();
222         let return_value = rust_call(&mut status, || {
223             <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(0))
224         });
225 
226         assert_eq!(status.code, RustCallStatusCode::Success);
227         assert_eq!(return_value, 100);
228 
229         rust_call(&mut status, || {
230             <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(1))
231         });
232         assert_eq!(status.code, RustCallStatusCode::Error);
233         unsafe {
234             assert_eq!(
235                 <TestError as Lift<UniFfiTag>>::try_lift(status.error_buf.assume_init()).unwrap(),
236                 TestError("Error".to_owned())
237             );
238         }
239 
240         let mut status = create_call_status();
241         rust_call(&mut status, || {
242             <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(2))
243         });
244         assert_eq!(status.code, RustCallStatusCode::UnexpectedError);
245         unsafe {
246             assert_eq!(
247                 <String as Lift<UniFfiTag>>::try_lift(status.error_buf.assume_init()).unwrap(),
248                 "Unexpected value: 2"
249             );
250         }
251     }
252 }
253