diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/ffi-support/src/error.rs | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/ffi-support/src/error.rs')
-rw-r--r-- | third_party/rust/ffi-support/src/error.rs | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/third_party/rust/ffi-support/src/error.rs b/third_party/rust/ffi-support/src/error.rs new file mode 100644 index 0000000000..6bc2808b2c --- /dev/null +++ b/third_party/rust/ffi-support/src/error.rs @@ -0,0 +1,365 @@ +/* Copyright 2018-2019 Mozilla Foundation + * + * Licensed under the Apache License (Version 2.0), or the MIT license, + * (the "Licenses") at your option. You may not use this file except in + * compliance with one of the Licenses. You may obtain copies of the + * Licenses at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licenses is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. */ + +use crate::string::{destroy_c_string, rust_string_to_c}; +use std::os::raw::c_char; +use std::{self, ptr}; + +/// Represents an error that occured within rust, storing both an error code, and additional data +/// that may be used by the caller. +/// +/// Misuse of this type can cause numerous issues, so please read the entire documentation before +/// usage. +/// +/// ## Rationale +/// +/// This library encourages a pattern of taking a `&mut ExternError` as the final parameter for +/// functions exposed over the FFI. This is an "out parameter" which we use to write error/success +/// information that occurred during the function's execution. +/// +/// To be clear, this means instances of `ExternError` will be created on the other side of the FFI, +/// and passed (by mutable reference) into Rust. +/// +/// While this pattern is not particularly ergonomic in Rust (although hopefully this library +/// helps!), it offers two main benefits over something more ergonomic (which might be `Result` +/// shaped). +/// +/// 1. It avoids defining a large number of `Result`-shaped types in the FFI consumer, as would +/// be required with something like an `struct ExternResult<T> { ok: *mut T, err:... }` +/// +/// 2. It offers additional type safety over `struct ExternResult { ok: *mut c_void, err:... }`, +/// which helps avoid memory safety errors. It also can offer better performance for returning +/// primitives and repr(C) structs (no boxing required). +/// +/// It also is less tricky to use properly than giving consumers a `get_last_error()` function, or +/// similar. +/// +/// ## Caveats +/// +/// Note that the order of the fields is `code` (an i32) then `message` (a `*mut c_char`), getting +/// this wrong on the other side of the FFI will cause memory corruption and crashes. +/// +/// The fields are public largely for documentation purposes, but you should use +/// [`ExternError::new_error`] or [`ExternError::success`] to create these. +/// +/// ## Layout/fields +/// +/// This struct's field are not `pub` (mostly so that we can soundly implement `Send`, but also so +/// that we can verify rust users are constructing them appropriately), the fields, their types, and +/// their order are *very much* a part of the public API of this type. Consumers on the other side +/// of the FFI will need to know its layout. +/// +/// If this were a C struct, it would look like +/// +/// ```c,no_run +/// struct ExternError { +/// int32_t code; +/// char *message; // note: nullable +/// }; +/// ``` +/// +/// In rust, there are two fields, in this order: `code: ErrorCode`, and `message: *mut c_char`. +/// Note that ErrorCode is a `#[repr(transparent)]` wrapper around an `i32`, so the first property +/// is equivalent to an `i32`. +/// +/// #### The `code` field. +/// +/// This is the error code, 0 represents success, all other values represent failure. If the `code` +/// field is nonzero, there should always be a message, and if it's zero, the message will always be +/// null. +/// +/// #### The `message` field. +/// +/// This is a null-terminated C string containing some amount of additional information about the +/// error. If the `code` property is nonzero, there should always be an error message. Otherwise, +/// this should will be null. +/// +/// This string (when not null) is allocated on the rust heap (using this crate's +/// [`rust_string_to_c`]), and must be freed on it as well. Critically, if there are multiple rust +/// packages using being used in the same application, it *must be freed on the same heap that +/// allocated it*, or you will corrupt both heaps. +/// +/// Typically, this object is managed on the other side of the FFI (on the "FFI consumer"), which +/// means you must expose a function to release the resources of `message` which can be done easily +/// using the [`define_string_destructor!`] macro provided by this crate. +/// +/// If, for some reason, you need to release the resources directly, you may call +/// `ExternError::release()`. Note that you probably do not need to do this, and it's +/// intentional that this is not called automatically by implementing `drop`. +/// +/// ## Example +/// +/// ```rust,no_run +/// use ffi_support::{ExternError, ErrorCode}; +/// +/// #[derive(Debug)] +/// pub enum MyError { +/// IllegalFoo(String), +/// InvalidBar(i64), +/// // ... +/// } +/// +/// // Putting these in a module is obviously optional, but it allows documentation, and helps +/// // avoid accidental reuse. +/// pub mod error_codes { +/// // note: -1 and 0 are reserved by ffi_support +/// pub const ILLEGAL_FOO: i32 = 1; +/// pub const INVALID_BAR: i32 = 2; +/// // ... +/// } +/// +/// fn get_code(e: &MyError) -> ErrorCode { +/// match e { +/// MyError::IllegalFoo(_) => ErrorCode::new(error_codes::ILLEGAL_FOO), +/// MyError::InvalidBar(_) => ErrorCode::new(error_codes::INVALID_BAR), +/// // ... +/// } +/// } +/// +/// impl From<MyError> for ExternError { +/// fn from(e: MyError) -> ExternError { +/// ExternError::new_error(get_code(&e), format!("{:?}", e)) +/// } +/// } +/// ``` +#[repr(C)] +// Note: We're intentionally not implementing Clone -- it's too risky. +#[derive(Debug, PartialEq)] +pub struct ExternError { + // Don't reorder or add anything here! + code: ErrorCode, + message: *mut c_char, +} + +impl std::panic::UnwindSafe for ExternError {} +impl std::panic::RefUnwindSafe for ExternError {} + +/// This is sound so long as our fields are private. +unsafe impl Send for ExternError {} + +impl ExternError { + /// Construct an ExternError representing failure from a code and a message. + #[inline] + pub fn new_error(code: ErrorCode, message: impl Into<String>) -> Self { + assert!( + !code.is_success(), + "Attempted to construct a success ExternError with a message" + ); + Self { + code, + message: rust_string_to_c(message), + } + } + + /// Returns a ExternError representing a success. Also returned by ExternError::default() + #[inline] + pub fn success() -> Self { + Self { + code: ErrorCode::SUCCESS, + message: ptr::null_mut(), + } + } + + /// Helper for the case where we aren't exposing this back over the FFI and + /// we just want to warn if an error occurred and then release the allocated + /// memory. + /// + /// Typically, this is done if the error will still be detected and reported + /// by other channels. + /// + /// We assume we're not inside a catch_unwind, and so we wrap inside one + /// ourselves. + pub fn consume_and_log_if_error(self) { + if !self.code.is_success() { + // in practice this should never panic, but you never know... + crate::abort_on_panic::call_with_output(|| { + log::error!("Unhandled ExternError({:?}) {:?}", self.code, unsafe { + crate::FfiStr::from_raw(self.message) + }); + unsafe { + self.manually_release(); + } + }) + } + } + + /// Get the `code` property. + #[inline] + pub fn get_code(&self) -> ErrorCode { + self.code + } + + /// Get the `message` property as a pointer to c_char. + #[inline] + pub fn get_raw_message(&self) -> *const c_char { + self.message as *const _ + } + + /// Get the `message` property as an [`FfiStr`][crate::FfiStr] + #[inline] + pub fn get_message(&self) -> crate::FfiStr<'_> { + // Safe because the lifetime is the same as our lifetime. + unsafe { crate::FfiStr::from_raw(self.get_raw_message()) } + } + + /// Get the `message` property as a String, or None if this is not an error result. + /// + /// ## Safety + /// + /// You should only call this if you are certain that the other side of the FFI doesn't have a + /// reference to this result (more specifically, to the `message` property) anywhere! + #[inline] + pub unsafe fn get_and_consume_message(self) -> Option<String> { + if self.code.is_success() { + None + } else { + let res = self.get_message().into_string(); + self.manually_release(); + Some(res) + } + } + + /// Manually release the memory behind this string. You probably don't want to call this. + /// + /// ## Safety + /// + /// You should only call this if you are certain that the other side of the FFI doesn't have a + /// reference to this result (more specifically, to the `message` property) anywhere! + pub unsafe fn manually_release(self) { + if !self.message.is_null() { + destroy_c_string(self.message) + } + } +} + +impl Default for ExternError { + #[inline] + fn default() -> Self { + ExternError::success() + } +} + +// This is the `Err` of std::thread::Result, which is what +// `panic::catch_unwind` returns. +impl From<Box<dyn std::any::Any + Send + 'static>> for ExternError { + fn from(e: Box<dyn std::any::Any + Send + 'static>) -> Self { + // The documentation suggests that it will *usually* be a str or String. + let message = if let Some(s) = e.downcast_ref::<&'static str>() { + (*s).to_string() + } else if let Some(s) = e.downcast_ref::<String>() { + s.clone() + } else { + "Unknown panic!".to_string() + }; + log::error!("Caught a panic calling rust code: {:?}", message); + ExternError::new_error(ErrorCode::PANIC, message) + } +} + +/// A wrapper around error codes, which is represented identically to an i32 on the other side of +/// the FFI. Essentially exists to check that we don't accidentally reuse success/panic codes for +/// other things. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct ErrorCode(i32); + +impl ErrorCode { + /// The ErrorCode used for success. + pub const SUCCESS: ErrorCode = ErrorCode(0); + + /// The ErrorCode used for panics. It's unlikely you need to ever use this. + // TODO: Consider moving to the reserved region... + pub const PANIC: ErrorCode = ErrorCode(-1); + + /// The ErrorCode used for handle map errors. + pub const INVALID_HANDLE: ErrorCode = ErrorCode(-1000); + + /// Construct an error code from an integer code. + /// + /// ## Panics + /// + /// Panics if you call it with 0 (reserved for success, but you can use `ErrorCode::SUCCESS` if + /// that's what you want), or -1 (reserved for panics, but you can use `ErrorCode::PANIC` if + /// that's what you want). + pub fn new(code: i32) -> Self { + assert!(code > ErrorCode::INVALID_HANDLE.0 && code != ErrorCode::PANIC.0 && code != ErrorCode::SUCCESS.0, + "Error: The ErrorCodes `{success}`, `{panic}`, and all error codes less than or equal \ + to `{reserved}` are reserved (got {code}). You may use the associated constants on this \ + type (`ErrorCode::PANIC`, etc) if you'd like instances of those error codes.", + panic = ErrorCode::PANIC.0, + success = ErrorCode::SUCCESS.0, + reserved = ErrorCode::INVALID_HANDLE.0, + code = code, + ); + + ErrorCode(code) + } + + /// Get the raw numeric value of this ErrorCode. + #[inline] + pub fn code(self) -> i32 { + self.0 + } + + /// Returns whether or not this is a success code. + #[inline] + pub fn is_success(self) -> bool { + self.code() == 0 + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[should_panic] + fn test_code_new_reserved_success() { + ErrorCode::new(0); + } + + #[test] + #[should_panic] + fn test_code_new_reserved_panic() { + ErrorCode::new(-1); + } + + #[test] + #[should_panic] + fn test_code_new_reserved_handle_error() { + ErrorCode::new(-1000); + } + #[test] + #[should_panic] + fn test_code_new_reserved_unknown() { + // Everything below -1000 should be reserved. + ErrorCode::new(-1043); + } + + #[test] + fn test_code_new_allowed() { + // Should not panic + ErrorCode::new(-2); + } + + #[test] + fn test_code() { + assert!(!ErrorCode::PANIC.is_success()); + assert!(!ErrorCode::INVALID_HANDLE.is_success()); + assert!(ErrorCode::SUCCESS.is_success()); + assert_eq!(ErrorCode::default(), ErrorCode::SUCCESS); + } +} |