ffi_support/error.rs
1/* Copyright 2018-2019 Mozilla Foundation
2 *
3 * Licensed under the Apache License (Version 2.0), or the MIT license,
4 * (the "Licenses") at your option. You may not use this file except in
5 * compliance with one of the Licenses. You may obtain copies of the
6 * Licenses at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * http://opensource.org/licenses/MIT
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the Licenses is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the Licenses for the specific language governing permissions and
15 * limitations under the Licenses. */
16
17use crate::string::{destroy_c_string, rust_string_to_c};
18use std::os::raw::c_char;
19use std::{self, ptr};
20
21/// Represents an error that occured within rust, storing both an error code, and additional data
22/// that may be used by the caller.
23///
24/// Misuse of this type can cause numerous issues, so please read the entire documentation before
25/// usage.
26///
27/// ## Rationale
28///
29/// This library encourages a pattern of taking a `&mut ExternError` as the final parameter for
30/// functions exposed over the FFI. This is an "out parameter" which we use to write error/success
31/// information that occurred during the function's execution.
32///
33/// To be clear, this means instances of `ExternError` will be created on the other side of the FFI,
34/// and passed (by mutable reference) into Rust.
35///
36/// While this pattern is not particularly ergonomic in Rust (although hopefully this library
37/// helps!), it offers two main benefits over something more ergonomic (which might be `Result`
38/// shaped).
39///
40/// 1. It avoids defining a large number of `Result`-shaped types in the FFI consumer, as would
41/// be required with something like an `struct ExternResult<T> { ok: *mut T, err:... }`
42///
43/// 2. It offers additional type safety over `struct ExternResult { ok: *mut c_void, err:... }`,
44/// which helps avoid memory safety errors. It also can offer better performance for returning
45/// primitives and repr(C) structs (no boxing required).
46///
47/// It also is less tricky to use properly than giving consumers a `get_last_error()` function, or
48/// similar.
49///
50/// ## Caveats
51///
52/// Note that the order of the fields is `code` (an i32) then `message` (a `*mut c_char`), getting
53/// this wrong on the other side of the FFI will cause memory corruption and crashes.
54///
55/// The fields are public largely for documentation purposes, but you should use
56/// [`ExternError::new_error`] or [`ExternError::success`] to create these.
57///
58/// ## Layout/fields
59///
60/// This struct's field are not `pub` (mostly so that we can soundly implement `Send`, but also so
61/// that we can verify rust users are constructing them appropriately), the fields, their types, and
62/// their order are *very much* a part of the public API of this type. Consumers on the other side
63/// of the FFI will need to know its layout.
64///
65/// If this were a C struct, it would look like
66///
67/// ```c,no_run
68/// struct ExternError {
69/// int32_t code;
70/// char *message; // note: nullable
71/// };
72/// ```
73///
74/// In rust, there are two fields, in this order: `code: ErrorCode`, and `message: *mut c_char`.
75/// Note that ErrorCode is a `#[repr(transparent)]` wrapper around an `i32`, so the first property
76/// is equivalent to an `i32`.
77///
78/// #### The `code` field.
79///
80/// This is the error code, 0 represents success, all other values represent failure. If the `code`
81/// field is nonzero, there should always be a message, and if it's zero, the message will always be
82/// null.
83///
84/// #### The `message` field.
85///
86/// This is a null-terminated C string containing some amount of additional information about the
87/// error. If the `code` property is nonzero, there should always be an error message. Otherwise,
88/// this should will be null.
89///
90/// This string (when not null) is allocated on the rust heap (using this crate's
91/// [`rust_string_to_c`]), and must be freed on it as well. Critically, if there are multiple rust
92/// packages using being used in the same application, it *must be freed on the same heap that
93/// allocated it*, or you will corrupt both heaps.
94///
95/// Typically, this object is managed on the other side of the FFI (on the "FFI consumer"), which
96/// means you must expose a function to release the resources of `message` which can be done easily
97/// using the [`define_string_destructor!`] macro provided by this crate.
98///
99/// If, for some reason, you need to release the resources directly, you may call
100/// `ExternError::release()`. Note that you probably do not need to do this, and it's
101/// intentional that this is not called automatically by implementing `drop`.
102///
103/// ## Example
104///
105/// ```rust,no_run
106/// use ffi_support::{ExternError, ErrorCode};
107///
108/// #[derive(Debug)]
109/// pub enum MyError {
110/// IllegalFoo(String),
111/// InvalidBar(i64),
112/// // ...
113/// }
114///
115/// // Putting these in a module is obviously optional, but it allows documentation, and helps
116/// // avoid accidental reuse.
117/// pub mod error_codes {
118/// // note: -1 and 0 are reserved by ffi_support
119/// pub const ILLEGAL_FOO: i32 = 1;
120/// pub const INVALID_BAR: i32 = 2;
121/// // ...
122/// }
123///
124/// fn get_code(e: &MyError) -> ErrorCode {
125/// match e {
126/// MyError::IllegalFoo(_) => ErrorCode::new(error_codes::ILLEGAL_FOO),
127/// MyError::InvalidBar(_) => ErrorCode::new(error_codes::INVALID_BAR),
128/// // ...
129/// }
130/// }
131///
132/// impl From<MyError> for ExternError {
133/// fn from(e: MyError) -> ExternError {
134/// ExternError::new_error(get_code(&e), format!("{:?}", e))
135/// }
136/// }
137/// ```
138#[repr(C)]
139// Note: We're intentionally not implementing Clone -- it's too risky.
140#[derive(Debug, PartialEq)]
141pub struct ExternError {
142 // Don't reorder or add anything here!
143 code: ErrorCode,
144 message: *mut c_char,
145}
146
147impl std::panic::UnwindSafe for ExternError {}
148impl std::panic::RefUnwindSafe for ExternError {}
149
150/// This is sound so long as our fields are private.
151unsafe impl Send for ExternError {}
152
153impl ExternError {
154 /// Construct an ExternError representing failure from a code and a message.
155 #[inline]
156 pub fn new_error(code: ErrorCode, message: impl Into<String>) -> Self {
157 assert!(
158 !code.is_success(),
159 "Attempted to construct a success ExternError with a message"
160 );
161 Self {
162 code,
163 message: rust_string_to_c(message),
164 }
165 }
166
167 /// Returns a ExternError representing a success. Also returned by ExternError::default()
168 #[inline]
169 pub fn success() -> Self {
170 Self {
171 code: ErrorCode::SUCCESS,
172 message: ptr::null_mut(),
173 }
174 }
175
176 /// Helper for the case where we aren't exposing this back over the FFI and
177 /// we just want to warn if an error occurred and then release the allocated
178 /// memory.
179 ///
180 /// Typically, this is done if the error will still be detected and reported
181 /// by other channels.
182 ///
183 /// We assume we're not inside a catch_unwind, and so we wrap inside one
184 /// ourselves.
185 pub fn consume_and_log_if_error(self) {
186 if !self.code.is_success() {
187 // in practice this should never panic, but you never know...
188 crate::abort_on_panic::call_with_output(|| {
189 log::error!("Unhandled ExternError({:?}) {:?}", self.code, unsafe {
190 crate::FfiStr::from_raw(self.message)
191 });
192 unsafe {
193 self.manually_release();
194 }
195 })
196 }
197 }
198
199 /// Get the `code` property.
200 #[inline]
201 pub fn get_code(&self) -> ErrorCode {
202 self.code
203 }
204
205 /// Get the `message` property as a pointer to c_char.
206 #[inline]
207 pub fn get_raw_message(&self) -> *const c_char {
208 self.message as *const _
209 }
210
211 /// Get the `message` property as an [`FfiStr`][crate::FfiStr]
212 #[inline]
213 pub fn get_message(&self) -> crate::FfiStr<'_> {
214 // Safe because the lifetime is the same as our lifetime.
215 unsafe { crate::FfiStr::from_raw(self.get_raw_message()) }
216 }
217
218 /// Get the `message` property as a String, or None if this is not an error result.
219 ///
220 /// ## Safety
221 ///
222 /// You should only call this if you are certain that the other side of the FFI doesn't have a
223 /// reference to this result (more specifically, to the `message` property) anywhere!
224 #[inline]
225 pub unsafe fn get_and_consume_message(self) -> Option<String> {
226 if self.code.is_success() {
227 None
228 } else {
229 let res = self.get_message().into_string();
230 self.manually_release();
231 Some(res)
232 }
233 }
234
235 /// Manually release the memory behind this string. You probably don't want to call this.
236 ///
237 /// ## Safety
238 ///
239 /// You should only call this if you are certain that the other side of the FFI doesn't have a
240 /// reference to this result (more specifically, to the `message` property) anywhere!
241 pub unsafe fn manually_release(self) {
242 if !self.message.is_null() {
243 destroy_c_string(self.message)
244 }
245 }
246}
247
248impl Default for ExternError {
249 #[inline]
250 fn default() -> Self {
251 ExternError::success()
252 }
253}
254
255// This is the `Err` of std::thread::Result, which is what
256// `panic::catch_unwind` returns.
257impl From<Box<dyn std::any::Any + Send + 'static>> for ExternError {
258 fn from(e: Box<dyn std::any::Any + Send + 'static>) -> Self {
259 // The documentation suggests that it will *usually* be a str or String.
260 let message = if let Some(s) = e.downcast_ref::<&'static str>() {
261 (*s).to_string()
262 } else if let Some(s) = e.downcast_ref::<String>() {
263 s.clone()
264 } else {
265 "Unknown panic!".to_string()
266 };
267 log::error!("Caught a panic calling rust code: {:?}", message);
268 ExternError::new_error(ErrorCode::PANIC, message)
269 }
270}
271
272/// A wrapper around error codes, which is represented identically to an i32 on the other side of
273/// the FFI. Essentially exists to check that we don't accidentally reuse success/panic codes for
274/// other things.
275#[repr(transparent)]
276#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
277pub struct ErrorCode(i32);
278
279impl ErrorCode {
280 /// The ErrorCode used for success.
281 pub const SUCCESS: ErrorCode = ErrorCode(0);
282
283 /// The ErrorCode used for panics. It's unlikely you need to ever use this.
284 // TODO: Consider moving to the reserved region...
285 pub const PANIC: ErrorCode = ErrorCode(-1);
286
287 /// The ErrorCode used for handle map errors.
288 pub const INVALID_HANDLE: ErrorCode = ErrorCode(-1000);
289
290 /// Construct an error code from an integer code.
291 ///
292 /// ## Panics
293 ///
294 /// Panics if you call it with 0 (reserved for success, but you can use `ErrorCode::SUCCESS` if
295 /// that's what you want), or -1 (reserved for panics, but you can use `ErrorCode::PANIC` if
296 /// that's what you want).
297 pub fn new(code: i32) -> Self {
298 assert!(code > ErrorCode::INVALID_HANDLE.0 && code != ErrorCode::PANIC.0 && code != ErrorCode::SUCCESS.0,
299 "Error: The ErrorCodes `{success}`, `{panic}`, and all error codes less than or equal \
300 to `{reserved}` are reserved (got {code}). You may use the associated constants on this \
301 type (`ErrorCode::PANIC`, etc) if you'd like instances of those error codes.",
302 panic = ErrorCode::PANIC.0,
303 success = ErrorCode::SUCCESS.0,
304 reserved = ErrorCode::INVALID_HANDLE.0,
305 code = code,
306 );
307
308 ErrorCode(code)
309 }
310
311 /// Get the raw numeric value of this ErrorCode.
312 #[inline]
313 pub fn code(self) -> i32 {
314 self.0
315 }
316
317 /// Returns whether or not this is a success code.
318 #[inline]
319 pub fn is_success(self) -> bool {
320 self.code() == 0
321 }
322}
323
324#[cfg(test)]
325mod test {
326 use super::*;
327
328 #[test]
329 #[should_panic]
330 fn test_code_new_reserved_success() {
331 ErrorCode::new(0);
332 }
333
334 #[test]
335 #[should_panic]
336 fn test_code_new_reserved_panic() {
337 ErrorCode::new(-1);
338 }
339
340 #[test]
341 #[should_panic]
342 fn test_code_new_reserved_handle_error() {
343 ErrorCode::new(-1000);
344 }
345 #[test]
346 #[should_panic]
347 fn test_code_new_reserved_unknown() {
348 // Everything below -1000 should be reserved.
349 ErrorCode::new(-1043);
350 }
351
352 #[test]
353 fn test_code_new_allowed() {
354 // Should not panic
355 ErrorCode::new(-2);
356 }
357
358 #[test]
359 fn test_code() {
360 assert!(!ErrorCode::PANIC.is_success());
361 assert!(!ErrorCode::INVALID_HANDLE.is_success());
362 assert!(ErrorCode::SUCCESS.is_success());
363 assert_eq!(ErrorCode::default(), ErrorCode::SUCCESS);
364 }
365}