diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/uniffi_core | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi_core')
17 files changed, 4210 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_core/.cargo-checksum.json b/third_party/rust/uniffi_core/.cargo-checksum.json new file mode 100644 index 0000000000..59804f7c89 --- /dev/null +++ b/third_party/rust/uniffi_core/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"b074f0db902264714faf879e99bdbc07df1550d75694f96751b499f98fecd16d","release.toml":"b150796411fc6ff90b481218cb50f8ac7c07f5845aebdb8e17877d47e55b05b9","src/ffi/callbackinterface.rs":"9e8650f0df087bf5e030a13d28f4990079e53613e656789b4b539d937a7fd288","src/ffi/ffidefault.rs":"f1ce099b92adbb12b160d513bae93342c7b6d806d7f6ebb665067db10af9a681","src/ffi/foreignbytes.rs":"d2b46e1a6317aa64801b855e0d12af6bcdef118d8036603d11c3cdaf6f35fdfe","src/ffi/foreigncallbacks.rs":"af8129a69ef23b92859e1cca0d666c95f0ed2c1fb2797f4495d824b65f774d03","src/ffi/foreignexecutor.rs":"123687921ce6dfb7f5bfa0736a630cfeff7f376b776ea03fc651da21ffd1cab8","src/ffi/mod.rs":"8117b08bbb7af3e97f66ed69c9690b60e8da0d6d8940349c7b9659a47cd8c92f","src/ffi/rustbuffer.rs":"8cc1f94b9ecba52b911da6a68155921c1b7f51b899d9874ddbc281a379941473","src/ffi/rustcalls.rs":"7caaa35ba8898c4b4983f07cefa80584ba00e753a11d496e578c80abe0cabe8b","src/ffi/rustfuture.rs":"d240426c8c8b83e3f6a2c0013e905298611287b2bb2022eb8161532209c635ca","src/ffi_converter_impls.rs":"82c1b47e02718610f2a5556997cd29ba5d8daf149d6353f470be0d9b971d968a","src/ffi_converter_traits.rs":"646c0d4aeb807d3e40db4d289f909030d0b2684087871a7d40d337680096b7d6","src/lib.rs":"4ad1a2899944a20e80a55d1c7bd01ff28395ace743a083c65847e6ea216fc5c8","src/metadata.rs":"6520ffcf2568a0d95f0f854acb6fc8aeaae26ef1f23fc576c2c50db72aa30eee","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851"},"package":"6121a127a3af1665cd90d12dd2b3683c2643c5103281d0fed5838324ca1fad5b"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_core/Cargo.toml b/third_party/rust/uniffi_core/Cargo.toml new file mode 100644 index 0000000000..4d4cbe2758 --- /dev/null +++ b/third_party/rust/uniffi_core/Cargo.toml @@ -0,0 +1,60 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "uniffi_core" +version = "0.25.3" +authors = ["Firefox Sync Team <sync-team@mozilla.com>"] +description = "a multi-language bindings generator for rust (runtime support code)" +homepage = "https://mozilla.github.io/uniffi-rs" +documentation = "https://mozilla.github.io/uniffi-rs" +keywords = [ + "ffi", + "bindgen", +] +license = "MPL-2.0" +repository = "https://github.com/mozilla/uniffi-rs" + +[dependencies.anyhow] +version = "1" + +[dependencies.async-compat] +version = "0.2.1" +optional = true + +[dependencies.bytes] +version = "1.3" + +[dependencies.camino] +version = "1.0.8" + +[dependencies.log] +version = "0.4" + +[dependencies.once_cell] +version = "1.10.0" + +[dependencies.oneshot] +version = "0.1.5" +features = ["async"] +package = "oneshot-uniffi" + +[dependencies.paste] +version = "1.0" + +[dependencies.static_assertions] +version = "1.1.0" + +[features] +default = [] +extern-rustbuffer = [] +tokio = ["dep:async-compat"] diff --git a/third_party/rust/uniffi_core/release.toml b/third_party/rust/uniffi_core/release.toml new file mode 100644 index 0000000000..2ff9c83f02 --- /dev/null +++ b/third_party/rust/uniffi_core/release.toml @@ -0,0 +1,15 @@ +# Note that this `release.toml` exists to capture things that must only be +# done once for `cargo release-backend-crates`. +# +# [../uniffi/release.toml](../uniffi/release.toml) captures things that must only be done for `cargo release-uniffi` +# +# All other config exists in [../release.toml](../release.toml). + +tag = false + +# This is how we manage the sections in CHANGELOG.md +pre-release-replacements = [ + {file="../CHANGELOG.md", search="\\[\\[UnreleasedBackendVersion\\]\\]", replace="v{{version}}", exactly=1}, + {file="../CHANGELOG.md", search="\\[\\[ReleaseDate\\]\\]", replace="{{date}}", exactly=1}, + {file="../CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n\n## [[NextUnreleasedUniFFIVersion]] (backend crates: [[UnreleasedBackendVersion]]) - (_[[ReleaseDate]]_)\n\n[All changes in [[NextUnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v{{version}}...NEXT_HEAD).", exactly=1}, +] diff --git a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs new file mode 100644 index 0000000000..7be66880bb --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs @@ -0,0 +1,308 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Callback interfaces are traits specified in UDL which can be implemented by foreign languages. +//! +//! # Using callback interfaces +//! +//! 1. Define a Rust trait. +//! +//! This toy example defines a way of Rust accessing a key-value store exposed +//! by the host operating system (e.g. the key chain). +//! +//! ``` +//! trait Keychain: Send { +//! fn get(&self, key: String) -> Option<String>; +//! fn put(&self, key: String, value: String); +//! } +//! ``` +//! +//! 2. Define a callback interface in the UDL +//! +//! ```idl +//! callback interface Keychain { +//! string? get(string key); +//! void put(string key, string data); +//! }; +//! ``` +//! +//! 3. And allow it to be passed into Rust. +//! +//! Here, we define a constructor to pass the keychain to rust, and then another method +//! which may use it. +//! +//! In UDL: +//! ```idl +//! object Authenticator { +//! constructor(Keychain keychain); +//! void login(); +//! } +//! ``` +//! +//! In Rust: +//! +//! ``` +//!# trait Keychain: Send { +//!# fn get(&self, key: String) -> Option<String>; +//!# fn put(&self, key: String, value: String); +//!# } +//! struct Authenticator { +//! keychain: Box<dyn Keychain>, +//! } +//! +//! impl Authenticator { +//! pub fn new(keychain: Box<dyn Keychain>) -> Self { +//! Self { keychain } +//! } +//! pub fn login(&self) { +//! let username = self.keychain.get("username".into()); +//! let password = self.keychain.get("password".into()); +//! } +//! } +//! ``` +//! 4. Create an foreign language implementation of the callback interface. +//! +//! In this example, here's a Kotlin implementation. +//! +//! ```kotlin +//! class AndroidKeychain: Keychain { +//! override fun get(key: String): String? { +//! // … elide the implementation. +//! return value +//! } +//! override fun put(key: String) { +//! // … elide the implementation. +//! } +//! } +//! ``` +//! 5. Pass the implementation to Rust. +//! +//! Again, in Kotlin +//! +//! ```kotlin +//! val authenticator = Authenticator(AndroidKeychain()) +//! authenticator.login() +//! ``` +//! +//! # How it works. +//! +//! ## High level +//! +//! Uniffi generates a protocol or interface in client code in the foreign language must implement. +//! +//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals` +//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface. +//! +//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the +//! `KeychainCallbackInternals` to store the instance in a handlemap. +//! +//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements +//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to +//! client code as `Box<dyn Keychain>`. +//! +//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`. +//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the +//! object handle, and the method selector. +//! +//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap, +//! and calls the actual implementation of the method. +//! +//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for +//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct +//! type and then returns to client code. +//! + +use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer}; +use std::fmt; + +/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it, +/// and it can be deleted from the handle map. +pub const IDX_CALLBACK_FREE: u32 = 0; + +/// Result of a foreign callback invocation +#[repr(i32)] +#[derive(Debug, PartialEq, Eq)] +pub enum CallbackResult { + /// Successful call. + /// The return value is serialized to `buf_ptr`. + Success = 0, + /// Expected error. + /// This is returned when a foreign method throws an exception that corresponds to the Rust Err half of a Result. + /// The error value is serialized to `buf_ptr`. + Error = 1, + /// Unexpected error. + /// An error message string is serialized to `buf_ptr`. + UnexpectedError = 2, +} + +impl TryFrom<i32> for CallbackResult { + // On errors we return the unconverted value + type Error = i32; + + fn try_from(value: i32) -> Result<Self, i32> { + match value { + 0 => Ok(Self::Success), + 1 => Ok(Self::Error), + 2 => Ok(Self::UnexpectedError), + n => Err(n), + } + } +} + +/// Struct to hold a foreign callback. +pub struct ForeignCallbackInternals { + callback_cell: ForeignCallbackCell, +} + +impl ForeignCallbackInternals { + pub const fn new() -> Self { + ForeignCallbackInternals { + callback_cell: ForeignCallbackCell::new(), + } + } + + pub fn set_callback(&self, callback: ForeignCallback) { + self.callback_cell.set(callback); + } + + /// Invoke a callback interface method on the foreign side and return the result + pub fn invoke_callback<R, UniFfiTag>(&self, handle: u64, method: u32, args: RustBuffer) -> R + where + R: LiftReturn<UniFfiTag>, + { + let mut ret_rbuf = RustBuffer::new(); + let callback = self.callback_cell.get(); + let raw_result = unsafe { + callback( + handle, + method, + args.data_pointer(), + args.len() as i32, + &mut ret_rbuf, + ) + }; + let result = CallbackResult::try_from(raw_result) + .unwrap_or_else(|code| panic!("Callback failed with unexpected return code: {code}")); + match result { + CallbackResult::Success => R::lift_callback_return(ret_rbuf), + CallbackResult::Error => R::lift_callback_error(ret_rbuf), + CallbackResult::UnexpectedError => { + let reason = if !ret_rbuf.is_empty() { + match <String as Lift<UniFfiTag>>::try_lift(ret_rbuf) { + Ok(s) => s, + Err(e) => { + log::error!("{{ trait_name }} Error reading ret_buf: {e}"); + String::from("[Error reading reason]") + } + } + } else { + RustBuffer::destroy(ret_rbuf); + String::from("[Unknown Reason]") + }; + R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason }) + } + } + } +} + +/// Used when internal/unexpected error happened when calling a foreign callback, for example when +/// a unknown exception is raised +/// +/// User callback error types must implement a From impl from this type to their own error type. +#[derive(Debug)] +pub struct UnexpectedUniFFICallbackError { + pub reason: String, +} + +impl UnexpectedUniFFICallbackError { + pub fn from_reason(reason: String) -> Self { + Self { reason } + } +} + +impl fmt::Display for UnexpectedUniFFICallbackError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "UnexpectedUniFFICallbackError(reason: {:?})", + self.reason + ) + } +} + +impl std::error::Error for UnexpectedUniFFICallbackError {} + +// Autoref-based specialization for converting UnexpectedUniFFICallbackError into error types. +// +// For more details, see: +// https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md + +// Define two ZST types: +// - One implements `try_convert_unexpected_callback_error` by always returning an error value. +// - The specialized version implements it using `From<UnexpectedUniFFICallbackError>` + +#[doc(hidden)] +#[derive(Debug)] +pub struct UnexpectedUniFFICallbackErrorConverterGeneric; + +impl UnexpectedUniFFICallbackErrorConverterGeneric { + pub fn try_convert_unexpected_callback_error<E>( + &self, + e: UnexpectedUniFFICallbackError, + ) -> anyhow::Result<E> { + Err(e.into()) + } +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct UnexpectedUniFFICallbackErrorConverterSpecialized; + +impl UnexpectedUniFFICallbackErrorConverterSpecialized { + pub fn try_convert_unexpected_callback_error<E>( + &self, + e: UnexpectedUniFFICallbackError, + ) -> anyhow::Result<E> + where + E: From<UnexpectedUniFFICallbackError>, + { + Ok(E::from(e)) + } +} + +// Macro to convert an UnexpectedUniFFICallbackError value for a particular type. This is used in +// the `ConvertError` implementation. +#[doc(hidden)] +#[macro_export] +macro_rules! convert_unexpected_error { + ($error:ident, $ty:ty) => {{ + // Trait for generic conversion, implemented for all &T. + pub trait GetConverterGeneric { + fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterGeneric; + } + + impl<T> GetConverterGeneric for &T { + fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterGeneric { + $crate::UnexpectedUniFFICallbackErrorConverterGeneric + } + } + // Trait for specialized conversion, implemented for all T that implements + // `Into<ErrorType>`. I.e. it's implemented for UnexpectedUniFFICallbackError when + // ErrorType implements From<UnexpectedUniFFICallbackError>. + pub trait GetConverterSpecialized { + fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterSpecialized; + } + + impl<T: Into<$ty>> GetConverterSpecialized for T { + fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterSpecialized { + $crate::UnexpectedUniFFICallbackErrorConverterSpecialized + } + } + // Here's the hack. Because of the auto-ref rules, this will use `GetConverterSpecialized` + // if it's implemented and `GetConverterGeneric` if not. + (&$error) + .get_converter() + .try_convert_unexpected_callback_error($error) + }}; +} diff --git a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs new file mode 100644 index 0000000000..1f86f6b13b --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! FfiDefault trait +//! +//! When we make a FFI call into Rust we always need to return a value, even if that value will be +//! ignored because we're flagging an exception. This trait defines what that value is for our +//! supported FFI types. + +use paste::paste; + +pub trait FfiDefault { + fn ffi_default() -> Self; +} + +// Most types can be handled by delegating to Default +macro_rules! impl_ffi_default_with_default { + ($($T:ty,)+) => { impl_ffi_default_with_default!($($T),+); }; + ($($T:ty),*) => { + $( + paste! { + impl FfiDefault for $T { + fn ffi_default() -> Self { + $T::default() + } + } + } + )* + }; +} + +impl_ffi_default_with_default! { + bool, i8, u8, i16, u16, i32, u32, i64, u64, f32, f64 +} + +// Implement FfiDefault for the remaining types +impl FfiDefault for () { + fn ffi_default() {} +} + +impl FfiDefault for *const std::ffi::c_void { + fn ffi_default() -> Self { + std::ptr::null() + } +} + +impl FfiDefault for crate::RustBuffer { + fn ffi_default() -> Self { + unsafe { Self::from_raw_parts(std::ptr::null_mut(), 0, 0) } + } +} + +impl FfiDefault for crate::ForeignExecutorHandle { + fn ffi_default() -> Self { + Self(std::ptr::null()) + } +} + +impl<T> FfiDefault for Option<T> { + fn ffi_default() -> Self { + None + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/foreignbytes.rs b/third_party/rust/uniffi_core/src/ffi/foreignbytes.rs new file mode 100644 index 0000000000..9516f61844 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/foreignbytes.rs @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Support for reading a slice of foreign-language-allocated bytes over the FFI. +/// +/// Foreign language code can pass a slice of bytes by providing a data pointer +/// and length, and this struct provides a convenient wrapper for working with +/// that pair. Naturally, this can be tremendously unsafe! So here are the details: +/// +/// * The foreign language code must ensure the provided buffer stays alive +/// and unchanged for the duration of the call to which the `ForeignBytes` +/// struct was provided. +/// +/// To work with the bytes in Rust code, use `as_slice()` to view the data +/// as a `&[u8]`. +/// +/// Implementation note: all the fields of this struct are private and it has no +/// constructors, so consuming crates cant create instances of it. If you've +/// got a `ForeignBytes`, then you received it over the FFI and are assuming that +/// the foreign language code is upholding the above invariants. +/// +/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified +/// to give a read-only view of externally-provided bytes. +#[repr(C)] +pub struct ForeignBytes { + /// The length of the pointed-to data. + /// We use an `i32` for compatibility with JNA. + len: i32, + /// The pointer to the foreign-owned bytes. + data: *const u8, +} + +impl ForeignBytes { + /// Creates a `ForeignBytes` from its constituent fields. + /// + /// This is intended mainly as an internal convenience function and should not + /// be used outside of this module. + /// + /// # Safety + /// + /// You must ensure that the raw parts uphold the documented invariants of this class. + pub unsafe fn from_raw_parts(data: *const u8, len: i32) -> Self { + Self { len, data } + } + + /// View the foreign bytes as a `&[u8]`. + /// + /// # Panics + /// + /// Panics if the provided struct has a null pointer but non-zero length. + /// Panics if the provided length is negative. + pub fn as_slice(&self) -> &[u8] { + if self.data.is_null() { + assert!(self.len == 0, "null ForeignBytes had non-zero length"); + &[] + } else { + unsafe { std::slice::from_raw_parts(self.data, self.len()) } + } + } + + /// Get the length of this slice of bytes. + /// + /// # Panics + /// + /// Panics if the provided length is negative. + pub fn len(&self) -> usize { + self.len + .try_into() + .expect("bytes length negative or overflowed") + } + + /// Returns true if the length of this slice of bytes is 0. + pub fn is_empty(&self) -> bool { + self.len == 0 + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_foreignbytes_access() { + let v = [1u8, 2, 3]; + let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), 3) }; + assert_eq!(fbuf.len(), 3); + assert_eq!(fbuf.as_slice(), &[1u8, 2, 3]); + } + + #[test] + fn test_foreignbytes_empty() { + let v = Vec::<u8>::new(); + let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), 0) }; + assert_eq!(fbuf.len(), 0); + assert_eq!(fbuf.as_slice(), &[0u8; 0]); + } + + #[test] + fn test_foreignbytes_null_means_empty() { + let fbuf = unsafe { ForeignBytes::from_raw_parts(std::ptr::null_mut(), 0) }; + assert_eq!(fbuf.as_slice(), &[0u8; 0]); + } + + #[test] + #[should_panic] + fn test_foreignbytes_null_must_have_zero_length() { + let fbuf = unsafe { ForeignBytes::from_raw_parts(std::ptr::null_mut(), 12) }; + fbuf.as_slice(); + } + + #[test] + #[should_panic] + fn test_foreignbytes_provided_len_must_be_non_negative() { + let v = [0u8, 1, 2]; + let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), -1) }; + fbuf.as_slice(); + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs new file mode 100644 index 0000000000..ac2463cd8e --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! This module contains code to handle foreign callbacks - C-ABI functions that are defined by a +//! foreign language, then registered with UniFFI. These callbacks are used to implement callback +//! interfaces, async scheduling etc. Foreign callbacks are registered at startup, when the foreign +//! code loads the exported library. For each callback type, we also define a "cell" type for +//! storing the callback. + +use std::sync::atomic::{AtomicUsize, Ordering}; + +use crate::{ForeignExecutorHandle, RustBuffer, RustTaskCallback}; + +/// ForeignCallback is the Rust representation of a foreign language function. +/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface, +/// at library start up time. +/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language. +/// +/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object +/// that implements the callback interface/trait. +/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from +/// the IDL. The list is 1 indexed. Note that the list of methods is generated by UniFFI from the IDL and used in all +/// bindings, so we can rely on the method list being stable within the same run of UniFFI. +/// * `args_data` and `args_len` represents a serialized buffer of arguments to the function. The scaffolding code +/// writes the callback arguments to this buffer, in order, using `FfiConverter.write()`. The bindings code reads the +/// arguments from the buffer and passes them to the user's callback. +/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a +/// buffer to write the result into. +/// * Callbacks return one of the `CallbackResult` values +/// Note: The output buffer might still contain 0 bytes of data. +pub type ForeignCallback = unsafe extern "C" fn( + handle: u64, + method: u32, + args_data: *const u8, + args_len: i32, + buf_ptr: *mut RustBuffer, +) -> i32; + +/// Callback to schedule a Rust call with a `ForeignExecutor`. The bindings code registers exactly +/// one of these with the Rust code. +/// +/// Delay is an approximate amount of ms to wait before scheduling the call. Delay is usually 0, +/// which means schedule sometime soon. +/// +/// As a special case, when Rust drops the foreign executor, with `task=null`. The foreign +/// bindings should release the reference to the executor that was reserved for Rust. +/// +/// This callback can be invoked from any thread, including threads created by Rust. +/// +/// The callback should return one of the `ForeignExecutorCallbackResult` values. +pub type ForeignExecutorCallback = extern "C" fn( + executor: ForeignExecutorHandle, + delay: u32, + task: Option<RustTaskCallback>, + task_data: *const (), +) -> i8; + +/// Store a [ForeignCallback] pointer +pub(crate) struct ForeignCallbackCell(AtomicUsize); + +/// Store a [ForeignExecutorCallback] pointer +pub(crate) struct ForeignExecutorCallbackCell(AtomicUsize); + +/// Macro to define foreign callback types as well as the callback cell. +macro_rules! impl_foreign_callback_cell { + ($callback_type:ident, $cell_type:ident) => { + // Overly-paranoid sanity checking to ensure that these types are + // convertible between each-other. `transmute` actually should check this for + // us too, but this helps document the invariants we rely on in this code. + // + // Note that these are guaranteed by + // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html + // and thus this is a little paranoid. + static_assertions::assert_eq_size!(usize, $callback_type); + static_assertions::assert_eq_size!(usize, Option<$callback_type>); + + impl $cell_type { + pub const fn new() -> Self { + Self(AtomicUsize::new(0)) + } + + pub fn set(&self, callback: $callback_type) { + // Store the pointer using Ordering::Relaxed. This is sufficient since callback + // should be set at startup, before there's any chance of using them. + self.0.store(callback as usize, Ordering::Relaxed); + } + + pub fn get(&self) -> $callback_type { + let ptr_value = self.0.load(Ordering::Relaxed); + unsafe { + // SAFETY: self.0 was set in `set` from our function pointer type, so + // it's safe to transmute it back here. + ::std::mem::transmute::<usize, Option<$callback_type>>(ptr_value) + .expect("Bug: callback not set. This is likely a uniffi bug.") + } + } + } + }; +} + +impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell); +impl_foreign_callback_cell!(ForeignExecutorCallback, ForeignExecutorCallbackCell); diff --git a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs b/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs new file mode 100644 index 0000000000..7b1cb9bd80 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs @@ -0,0 +1,487 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Schedule tasks using a foreign executor. + +use std::panic; + +use crate::{ForeignExecutorCallback, ForeignExecutorCallbackCell}; + +/// Opaque handle for a foreign task executor. +/// +/// Foreign code can either use an actual pointer, or use an integer value casted to it. +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct ForeignExecutorHandle(pub(crate) *const ()); + +// Implement Send + Sync for `ForeignExecutor`. The foreign bindings code is responsible for +// making the `ForeignExecutorCallback` thread-safe. +unsafe impl Send for ForeignExecutorHandle {} + +unsafe impl Sync for ForeignExecutorHandle {} + +/// Result code returned by `ForeignExecutorCallback` +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum ForeignExecutorCallbackResult { + /// Callback was scheduled successfully + Success = 0, + /// Callback couldn't be scheduled because the foreign executor is canceled/closed. + Cancelled = 1, + /// Callback couldn't be scheduled because of some other error + Error = 2, +} + +impl ForeignExecutorCallbackResult { + /// Check the result code for the foreign executor callback + /// + /// If the result was `ForeignExecutorCallbackResult.Success`, this method returns `true`. + /// + /// If not, this method returns `false`, logging errors for any unexpected return values + pub fn check_result_code(result: i8) -> bool { + match result { + n if n == ForeignExecutorCallbackResult::Success as i8 => true, + n if n == ForeignExecutorCallbackResult::Cancelled as i8 => false, + n if n == ForeignExecutorCallbackResult::Error as i8 => { + log::error!( + "ForeignExecutorCallbackResult::Error returned by foreign executor callback" + ); + false + } + n => { + log::error!("Unknown code ({n}) returned by foreign executor callback"); + false + } + } + } +} + +// Option<RustTaskCallback> should use the null pointer optimization and be represented in C as a +// regular pointer. Let's check that. +static_assertions::assert_eq_size!(usize, Option<RustTaskCallback>); + +/// Callback for a Rust task, this is what the foreign executor invokes +/// +/// The task will be passed the `task_data` passed to `ForeignExecutorCallback` in addition to one +/// of the `RustTaskCallbackCode` values. +pub type RustTaskCallback = extern "C" fn(*const (), RustTaskCallbackCode); + +/// Passed to a `RustTaskCallback` function when the executor invokes them. +/// +/// Every `RustTaskCallback` will be invoked eventually, this code is used to distinguish the times +/// when it's invoked successfully vs times when the callback is being called because the foreign +/// executor has been cancelled / shutdown +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum RustTaskCallbackCode { + /// Successful task callback invocation + Success = 0, + /// The `ForeignExecutor` has been cancelled. + /// + /// This signals that any progress using the executor should be halted. In particular, Futures + /// should not continue to progress. + Cancelled = 1, +} + +static FOREIGN_EXECUTOR_CALLBACK: ForeignExecutorCallbackCell = ForeignExecutorCallbackCell::new(); + +/// Set the global ForeignExecutorCallback. This is called by the foreign bindings, normally +/// during initialization. +pub fn foreign_executor_callback_set(callback: ForeignExecutorCallback) { + FOREIGN_EXECUTOR_CALLBACK.set(callback); +} + +/// Schedule Rust calls using a foreign executor +#[derive(Debug)] +pub struct ForeignExecutor { + pub(crate) handle: ForeignExecutorHandle, +} + +impl ForeignExecutor { + pub fn new(executor: ForeignExecutorHandle) -> Self { + Self { handle: executor } + } + + /// Schedule a closure to be run. + /// + /// This method can be used for "fire-and-forget" style calls, where the calling code doesn't + /// need to await the result. + /// + /// Closure requirements: + /// - Send: since the closure will likely run on a different thread + /// - 'static: since it runs at an arbitrary time, so all references need to be 'static + /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data + pub fn schedule<F: FnOnce() + Send + 'static + panic::UnwindSafe>(&self, delay: u32, task: F) { + let leaked_ptr: *mut F = Box::leak(Box::new(task)); + if !schedule_raw( + self.handle, + delay, + schedule_callback::<F>, + leaked_ptr as *const (), + ) { + // If schedule_raw() failed, drop the leaked box since `schedule_callback()` has not been + // scheduled to run. + unsafe { + drop(Box::<F>::from_raw(leaked_ptr)); + }; + } + } + + /// Schedule a closure to be run and get a Future for the result + /// + /// Closure requirements: + /// - Send: since the closure will likely run on a different thread + /// - 'static: since it runs at an arbitrary time, so all references need to be 'static + /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data + pub async fn run<F, T>(&self, delay: u32, closure: F) -> T + where + F: FnOnce() -> T + Send + 'static + panic::UnwindSafe, + T: Send + 'static, + { + // Create a oneshot channel to handle the future + let (sender, receiver) = oneshot::channel(); + // We can use `AssertUnwindSafe` here because: + // - The closure is unwind safe + // - `Sender` is not marked unwind safe, maybe this is just an oversight in the oneshot + // library. However, calling `send()` and dropping the Sender should certainly be + // unwind safe. `send()` should probably not panic at all and if it does it shouldn't + // do it in a way that breaks the Receiver. + // - Calling `expect` may result in a panic, but this should should not break either the + // Sender or Receiver. + self.schedule( + delay, + panic::AssertUnwindSafe(move || { + sender.send(closure()).expect("Error sending future result") + }), + ); + receiver.await.expect("Error receiving future result") + } +} + +/// Low-level schedule interface +/// +/// When using this function, take care to ensure that the `ForeignExecutor` that holds the +/// `ForeignExecutorHandle` has not been dropped. +/// +/// Returns true if the callback was successfully scheduled +pub(crate) fn schedule_raw( + handle: ForeignExecutorHandle, + delay: u32, + callback: RustTaskCallback, + data: *const (), +) -> bool { + let result_code = (FOREIGN_EXECUTOR_CALLBACK.get())(handle, delay, Some(callback), data); + ForeignExecutorCallbackResult::check_result_code(result_code) +} + +impl Drop for ForeignExecutor { + fn drop(&mut self) { + (FOREIGN_EXECUTOR_CALLBACK.get())(self.handle, 0, None, std::ptr::null()); + } +} + +extern "C" fn schedule_callback<F>(data: *const (), status_code: RustTaskCallbackCode) +where + F: FnOnce() + Send + 'static + panic::UnwindSafe, +{ + // No matter what, we need to call Box::from_raw() to balance the Box::leak() call. + let task = unsafe { Box::from_raw(data as *mut F) }; + // Skip running the task for the `RustTaskCallbackCode::Cancelled` code + if status_code == RustTaskCallbackCode::Success { + run_task(task); + } +} + +/// Run a scheduled task, catching any panics. +/// +/// If there are panics, then we will log a warning and return None. +fn run_task<F: FnOnce() -> T + panic::UnwindSafe, T>(task: F) -> Option<T> { + match panic::catch_unwind(task) { + Ok(v) => Some(v), + Err(cause) => { + let message = if let Some(s) = cause.downcast_ref::<&'static str>() { + (*s).to_string() + } else if let Some(s) = cause.downcast_ref::<String>() { + s.clone() + } else { + "Unknown panic!".to_string() + }; + log::warn!("Error calling UniFFI callback function: {message}"); + None + } + } +} + +#[cfg(test)] +pub use test::MockEventLoop; + +#[cfg(test)] +mod test { + use super::*; + use std::{ + future::Future, + pin::Pin, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, Once, + }, + task::{Context, Poll, Wake, Waker}, + }; + + /// Simulate an event loop / task queue / coroutine scope on the foreign side + /// + /// This simply collects scheduled calls into a Vec for testing purposes. + /// + /// Most of the MockEventLoop methods are `pub` since it's also used by the `rustfuture` tests. + pub struct MockEventLoop { + // Wrap everything in a mutex since we typically share access to MockEventLoop via an Arc + inner: Mutex<MockEventLoopInner>, + } + + pub struct MockEventLoopInner { + // calls that have been scheduled + calls: Vec<(u32, Option<RustTaskCallback>, *const ())>, + // has the event loop been shutdown? + is_shutdown: bool, + } + + unsafe impl Send for MockEventLoopInner {} + + static FOREIGN_EXECUTOR_CALLBACK_INIT: Once = Once::new(); + + impl MockEventLoop { + pub fn new() -> Arc<Self> { + // Make sure we install a foreign executor callback that can deal with mock event loops + FOREIGN_EXECUTOR_CALLBACK_INIT + .call_once(|| foreign_executor_callback_set(mock_executor_callback)); + + Arc::new(Self { + inner: Mutex::new(MockEventLoopInner { + calls: vec![], + is_shutdown: false, + }), + }) + } + + /// Create a new ForeignExecutorHandle + pub fn new_handle(self: &Arc<Self>) -> ForeignExecutorHandle { + // To keep the memory management simple, we simply leak an arc reference for this. We + // only create a handful of these in the tests so there's no need for proper cleanup. + ForeignExecutorHandle(Arc::into_raw(Arc::clone(self)) as *const ()) + } + + pub fn new_executor(self: &Arc<Self>) -> ForeignExecutor { + ForeignExecutor { + handle: self.new_handle(), + } + } + + /// Get the current number of scheduled calls + pub fn call_count(&self) -> usize { + self.inner.lock().unwrap().calls.len() + } + + /// Get the last scheduled call + pub fn last_call(&self) -> (u32, Option<RustTaskCallback>, *const ()) { + self.inner + .lock() + .unwrap() + .calls + .last() + .cloned() + .expect("no calls scheduled") + } + + /// Run all currently scheduled calls + pub fn run_all_calls(&self) { + let mut inner = self.inner.lock().unwrap(); + let is_shutdown = inner.is_shutdown; + for (_delay, callback, data) in inner.calls.drain(..) { + if !is_shutdown { + callback.unwrap()(data, RustTaskCallbackCode::Success); + } else { + callback.unwrap()(data, RustTaskCallbackCode::Cancelled); + } + } + } + + /// Shutdown the eventloop, causing scheduled calls and future calls to be cancelled + pub fn shutdown(&self) { + self.inner.lock().unwrap().is_shutdown = true; + } + } + + // `ForeignExecutorCallback` that we install for testing + extern "C" fn mock_executor_callback( + handle: ForeignExecutorHandle, + delay: u32, + task: Option<RustTaskCallback>, + task_data: *const (), + ) -> i8 { + let eventloop = handle.0 as *const MockEventLoop; + let mut inner = unsafe { (*eventloop).inner.lock().unwrap() }; + if inner.is_shutdown { + ForeignExecutorCallbackResult::Cancelled as i8 + } else { + inner.calls.push((delay, task, task_data)); + ForeignExecutorCallbackResult::Success as i8 + } + } + + #[test] + fn test_schedule_raw() { + extern "C" fn callback(data: *const (), _status_code: RustTaskCallbackCode) { + unsafe { + *(data as *mut u32) += 1; + } + } + + let eventloop = MockEventLoop::new(); + + let value: u32 = 0; + assert_eq!(eventloop.call_count(), 0); + + schedule_raw( + eventloop.new_handle(), + 0, + callback, + &value as *const u32 as *const (), + ); + assert_eq!(eventloop.call_count(), 1); + assert_eq!(value, 0); + + eventloop.run_all_calls(); + assert_eq!(eventloop.call_count(), 0); + assert_eq!(value, 1); + } + + #[test] + fn test_schedule() { + let eventloop = MockEventLoop::new(); + let executor = eventloop.new_executor(); + let value = Arc::new(AtomicU32::new(0)); + assert_eq!(eventloop.call_count(), 0); + + let value2 = value.clone(); + executor.schedule(0, move || { + value2.fetch_add(1, Ordering::Relaxed); + }); + assert_eq!(eventloop.call_count(), 1); + assert_eq!(value.load(Ordering::Relaxed), 0); + + eventloop.run_all_calls(); + assert_eq!(eventloop.call_count(), 0); + assert_eq!(value.load(Ordering::Relaxed), 1); + } + + #[derive(Default)] + struct MockWaker { + wake_count: AtomicU32, + } + + impl Wake for MockWaker { + fn wake(self: Arc<Self>) { + self.wake_count.fetch_add(1, Ordering::Relaxed); + } + } + + #[test] + fn test_run() { + let eventloop = MockEventLoop::new(); + let executor = eventloop.new_executor(); + let mock_waker = Arc::new(MockWaker::default()); + let waker = Waker::from(mock_waker.clone()); + let mut context = Context::from_waker(&waker); + assert_eq!(eventloop.call_count(), 0); + + let mut future = executor.run(0, move || "test-return-value"); + unsafe { + assert_eq!( + Pin::new_unchecked(&mut future).poll(&mut context), + Poll::Pending + ); + } + assert_eq!(eventloop.call_count(), 1); + assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 0); + + eventloop.run_all_calls(); + assert_eq!(eventloop.call_count(), 0); + assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 1); + unsafe { + assert_eq!( + Pin::new_unchecked(&mut future).poll(&mut context), + Poll::Ready("test-return-value") + ); + } + } + + #[test] + fn test_drop() { + let eventloop = MockEventLoop::new(); + let executor = eventloop.new_executor(); + + drop(executor); + // Calling drop should schedule a call with null task data. + assert_eq!(eventloop.call_count(), 1); + assert_eq!(eventloop.last_call().1, None); + } + + // Test that cancelled calls never run + #[test] + fn test_cancelled_call() { + let eventloop = MockEventLoop::new(); + let executor = eventloop.new_executor(); + // Create a shared counter + let counter = Arc::new(AtomicU32::new(0)); + // schedule increments using both `schedule()` and run()` + let counter_clone = Arc::clone(&counter); + executor.schedule(0, move || { + counter_clone.fetch_add(1, Ordering::Relaxed); + }); + let counter_clone = Arc::clone(&counter); + let future = executor.run(0, move || { + counter_clone.fetch_add(1, Ordering::Relaxed); + }); + // shutdown the eventloop before the scheduled call gets a chance to run. + eventloop.shutdown(); + // `run_all_calls()` will cause the scheduled task callbacks to run, but will pass + // `RustTaskCallbackCode::Cancelled` to it. This drop the scheduled closure without executing + // it. + eventloop.run_all_calls(); + + assert_eq!(counter.load(Ordering::Relaxed), 0); + drop(future); + } + + // Test that when scheduled calls are cancelled, the closures are dropped properly + #[test] + fn test_cancellation_drops_closures() { + let eventloop = MockEventLoop::new(); + let executor = eventloop.new_executor(); + + // Create an Arc<> that we will move into the closures to test if they are dropped or not + let arc = Arc::new(0); + let arc_clone = Arc::clone(&arc); + executor.schedule(0, move || assert_eq!(*arc_clone, 0)); + let arc_clone = Arc::clone(&arc); + let future = executor.run(0, move || assert_eq!(*arc_clone, 0)); + + // shutdown the eventloop and run the (cancelled) scheduled calls. + eventloop.shutdown(); + eventloop.run_all_calls(); + // try to schedule some more calls now that the loop has been shutdown + let arc_clone = Arc::clone(&arc); + executor.schedule(0, move || assert_eq!(*arc_clone, 0)); + let arc_clone = Arc::clone(&arc); + let future2 = executor.run(0, move || assert_eq!(*arc_clone, 0)); + + // Drop the futures so they don't hold on to any references + drop(future); + drop(future2); + + // All of these closures should have been dropped by now, there only remaining arc + // reference should be the original + assert_eq!(Arc::strong_count(&arc), 1); + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/mod.rs b/third_party/rust/uniffi_core/src/ffi/mod.rs new file mode 100644 index 0000000000..b606323297 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/mod.rs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Types that can cross the FFI boundary. + +pub mod callbackinterface; +pub mod ffidefault; +pub mod foreignbytes; +pub mod foreigncallbacks; +pub mod foreignexecutor; +pub mod rustbuffer; +pub mod rustcalls; +pub mod rustfuture; + +pub use callbackinterface::*; +pub use ffidefault::FfiDefault; +pub use foreignbytes::*; +pub use foreigncallbacks::*; +pub use foreignexecutor::*; +pub use rustbuffer::*; +pub use rustcalls::*; +pub use rustfuture::*; diff --git a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs new file mode 100644 index 0000000000..e09e3be89a --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs @@ -0,0 +1,421 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::ffi::{rust_call, ForeignBytes, RustCallStatus}; + +/// Support for passing an allocated-by-Rust buffer of bytes over the FFI. +/// +/// We can pass a `Vec<u8>` to foreign language code by decomposing it into +/// its raw parts (buffer pointer, length, and capacity) and passing those +/// around as a struct. Naturally, this can be tremendously unsafe! So here +/// are the details: +/// +/// * `RustBuffer` structs must only ever be constructed from a `Vec<u8>`, +/// either explicitly via `RustBuffer::from_vec` or indirectly by calling +/// one of the `RustBuffer::new*` constructors. +/// +/// * `RustBuffer` structs do not implement `Drop`, since they are intended +/// to be passed to foreign-language code outside of the control of Rust's +/// ownership system. To avoid memory leaks they *must* passed back into +/// Rust and either explicitly destroyed using `RustBuffer::destroy`, or +/// converted back to a `Vec<u8>` using `RustBuffer::destroy_into_vec` +/// (which will then be dropped via Rust's usual ownership-tracking system). +/// +/// Foreign-language code should not construct `RustBuffer` structs other than +/// by receiving them from a call into the Rust code, and should not modify them +/// apart from the following safe operations: +/// +/// * Writing bytes into the buffer pointed to by `data`, without writing +/// beyond the indicated `capacity`. +/// +/// * Adjusting the `len` property to indicate the amount of data written, +/// while ensuring that 0 <= `len` <= `capacity`. +/// +/// * As a special case, constructing a `RustBuffer` with zero capacity, zero +/// length, and a null `data` pointer to indicate an empty buffer. +/// +/// In particular, it is not safe for foreign-language code to construct a `RustBuffer` +/// that points to its own allocated memory; use the `ForeignBytes` struct to +/// pass a view of foreign-owned memory in to Rust code. +/// +/// Implementation note: all the fields of this struct are private, so you can't +/// manually construct instances that don't come from a `Vec<u8>`. If you've got +/// a `RustBuffer` then it either came from a public constructor (all of which +/// are safe) or it came from foreign-language code (which should have in turn +/// received it by calling some Rust function, and should be respecting the +/// invariants listed above). +/// +/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified +/// to retain unallocated capacity rather than truncating to the occupied length. +#[repr(C)] +#[derive(Debug)] +pub struct RustBuffer { + /// The allocated capacity of the underlying `Vec<u8>`. + /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. + capacity: i32, + /// The occupied length of the underlying `Vec<u8>`. + /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. + len: i32, + /// The pointer to the allocated buffer of the `Vec<u8>`. + data: *mut u8, +} + +// Mark `RustBuffer` as safe to send between threads, despite the `u8` pointer. The only mutable +// use of that pointer is in `destroy_into_vec()` which requires a &mut on the `RustBuffer`. This +// is required to send `RustBuffer` inside a `RustFuture` +unsafe impl Send for RustBuffer {} + +impl RustBuffer { + /// Creates an empty `RustBuffer`. + /// + /// The buffer will not allocate. + /// The resulting vector will not be automatically dropped; you must + /// arrange to call `destroy` or `destroy_into_vec` when finished with it. + pub fn new() -> Self { + Self::from_vec(Vec::new()) + } + + /// Creates a `RustBuffer` from its constituent fields. + /// + /// This is intended mainly as an internal convenience function and should not + /// be used outside of this module. + /// + /// # Safety + /// + /// You must ensure that the raw parts uphold the documented invariants of this class. + pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self { + Self { + capacity, + len, + data, + } + } + + /// Get the current length of the buffer, as a `usize`. + /// + /// This is mostly a helper function to convert the `i32` length field + /// into a `usize`, which is what Rust code usually expects. + /// + /// # Panics + /// + /// Panics if called on an invalid struct obtained from foreign-language code, + /// in which the `len` field is negative. + pub fn len(&self) -> usize { + self.len + .try_into() + .expect("buffer length negative or overflowed") + } + + /// Get a pointer to the data + pub fn data_pointer(&self) -> *const u8 { + self.data + } + + /// Returns true if the length of the buffer is 0. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Creates a `RustBuffer` zero-filed to the requested size. + /// + /// The resulting vector will not be automatically dropped; you must + /// arrange to call `destroy` or `destroy_into_vec` when finished with it. + /// + /// # Panics + /// + /// Panics if the requested size is too large to fit in an `i32`, and + /// hence would risk incompatibility with some foreign-language code. + pub fn new_with_size(size: usize) -> Self { + assert!( + size < i32::MAX as usize, + "RustBuffer requested size too large" + ); + Self::from_vec(vec![0u8; size]) + } + + /// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`. + /// + /// The resulting vector will not be automatically dropped; you must + /// arrange to call `destroy` or `destroy_into_vec` when finished with it. + /// + /// # Panics + /// + /// Panics if the vector's length or capacity are too large to fit in an `i32`, + /// and hence would risk incompatibility with some foreign-language code. + pub fn from_vec(v: Vec<u8>) -> Self { + let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32."); + let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32."); + let mut v = std::mem::ManuallyDrop::new(v); + unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) } + } + + /// Converts this `RustBuffer` back into an owned `Vec<u8>`. + /// + /// This restores ownership of the underlying buffer to Rust, meaning it will + /// be dropped when the `Vec<u8>` is dropped. The `RustBuffer` *must* have been + /// previously obtained from a valid `Vec<u8>` owned by this Rust code. + /// + /// # Panics + /// + /// Panics if called on an invalid struct obtained from foreign-language code, + /// which does not respect the invairiants on `len` and `capacity`. + pub fn destroy_into_vec(self) -> Vec<u8> { + // Rust will never give us a null `data` pointer for a `Vec`, but + // foreign-language code can use it to cheaply pass an empty buffer. + if self.data.is_null() { + assert!(self.capacity == 0, "null RustBuffer had non-zero capacity"); + assert!(self.len == 0, "null RustBuffer had non-zero length"); + vec![] + } else { + let capacity: usize = self + .capacity + .try_into() + .expect("buffer capacity negative or overflowed"); + let len: usize = self + .len + .try_into() + .expect("buffer length negative or overflowed"); + assert!(len <= capacity, "RustBuffer length exceeds capacity"); + unsafe { Vec::from_raw_parts(self.data, len, capacity) } + } + } + + /// Reclaim memory stored in this `RustBuffer`. + /// + /// # Panics + /// + /// Panics if called on an invalid struct obtained from foreign-language code, + /// which does not respect the invairiants on `len` and `capacity`. + pub fn destroy(self) { + drop(self.destroy_into_vec()); + } +} + +impl Default for RustBuffer { + fn default() -> Self { + Self::new() + } +} + +// extern "C" functions for the RustBuffer functionality. +// +// These are used in two ways: +// 1. Code that statically links to UniFFI can use these directly to handle RustBuffer +// allocation/destruction. The plan is to use this for the Firefox desktop JS bindings. +// +// 2. The scaffolding code re-exports these functions, prefixed with the component name and UDL +// hash This creates a separate set of functions for each UniFFIed component, which is needed +// in the case where we create multiple dylib artifacts since each dylib will have its own +// allocator. + +/// This helper allocates a new byte buffer owned by the Rust code, and returns it +/// to the foreign-language code as a `RustBuffer` struct. Callers must eventually +/// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined +/// below, or by passing ownership of the buffer back into Rust code. +#[cfg(feature = "extern-rustbuffer")] +#[no_mangle] +pub extern "C" fn uniffi_rustbuffer_alloc( + size: i32, + call_status: &mut RustCallStatus, +) -> RustBuffer { + _uniffi_rustbuffer_alloc(size, call_status) +} + +#[cfg(not(feature = "extern-rustbuffer"))] +pub fn uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer { + _uniffi_rustbuffer_alloc(size, call_status) +} + +fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer { + rust_call(call_status, || { + Ok(RustBuffer::new_with_size(size.max(0) as usize)) + }) +} + +/// This helper copies bytes owned by the foreign-language code into a new byte buffer owned +/// by the Rust code, and returns it as a `RustBuffer` struct. Callers must eventually +/// free the resulting buffer, either by explicitly calling the destructor defined below, +/// or by passing ownership of the buffer back into Rust code. +/// +/// # Safety +/// This function will dereference a provided pointer in order to copy bytes from it, so +/// make sure the `ForeignBytes` struct contains a valid pointer and length. +#[cfg(feature = "extern-rustbuffer")] +#[no_mangle] +pub extern "C" fn uniffi_rustbuffer_from_bytes( + bytes: ForeignBytes, + call_status: &mut RustCallStatus, +) -> RustBuffer { + _uniffi_rustbuffer_from_bytes(bytes, call_status) +} + +#[cfg(not(feature = "extern-rustbuffer"))] +pub fn uniffi_rustbuffer_from_bytes( + bytes: ForeignBytes, + call_status: &mut RustCallStatus, +) -> RustBuffer { + _uniffi_rustbuffer_from_bytes(bytes, call_status) +} + +fn _uniffi_rustbuffer_from_bytes( + bytes: ForeignBytes, + call_status: &mut RustCallStatus, +) -> RustBuffer { + rust_call(call_status, || { + let bytes = bytes.as_slice(); + Ok(RustBuffer::from_vec(bytes.to_vec())) + }) +} + +/// Free a byte buffer that had previously been passed to the foreign language code. +/// +/// # Safety +/// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call +/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or +/// corrupting the allocator state. +#[cfg(feature = "extern-rustbuffer")] +#[no_mangle] +pub extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { + _uniffi_rustbuffer_free(buf, call_status) +} + +#[cfg(not(feature = "extern-rustbuffer"))] +pub fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { + _uniffi_rustbuffer_free(buf, call_status) +} + +fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { + rust_call(call_status, || { + RustBuffer::destroy(buf); + Ok(()) + }) +} + +/// Reserve additional capacity in a byte buffer that had previously been passed to the +/// foreign language code. +/// +/// The first argument *must* be a uniquely-owned `RustBuffer` previously +/// obtained from a call into the Rust code that returned a buffer. Its underlying data pointer +/// will be reallocated if necessary and returned in a new `RustBuffer` struct. +/// +/// The second argument must be the minimum number of *additional* bytes to reserve +/// capacity for in the buffer; it is likely to reserve additional capacity in practice +/// due to amortized growth strategy of Rust vectors. +/// +/// # Safety +/// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call +/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or +/// corrupting the allocator state. +#[cfg(feature = "extern-rustbuffer")] +#[no_mangle] +pub extern "C" fn uniffi_rustbuffer_reserve( + buf: RustBuffer, + additional: i32, + call_status: &mut RustCallStatus, +) -> RustBuffer { + _uniffi_rustbuffer_reserve(buf, additional, call_status) +} + +#[cfg(not(feature = "extern-rustbuffer"))] +pub fn uniffi_rustbuffer_reserve( + buf: RustBuffer, + additional: i32, + call_status: &mut RustCallStatus, +) -> RustBuffer { + _uniffi_rustbuffer_reserve(buf, additional, call_status) +} + +fn _uniffi_rustbuffer_reserve( + buf: RustBuffer, + additional: i32, + call_status: &mut RustCallStatus, +) -> RustBuffer { + rust_call(call_status, || { + let additional: usize = additional + .try_into() + .expect("additional buffer length negative or overflowed"); + let mut v = buf.destroy_into_vec(); + v.reserve(additional); + Ok(RustBuffer::from_vec(v)) + }) +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_rustbuffer_from_vec() { + let rbuf = RustBuffer::from_vec(vec![1u8, 2, 3]); + assert_eq!(rbuf.len(), 3); + assert_eq!(rbuf.destroy_into_vec(), vec![1u8, 2, 3]); + } + + #[test] + fn test_rustbuffer_empty() { + let rbuf = RustBuffer::new(); + assert_eq!(rbuf.len(), 0); + // Rust will never give us a null pointer, even for an empty buffer. + assert!(!rbuf.data.is_null()); + assert_eq!(rbuf.destroy_into_vec(), Vec::<u8>::new()); + } + + #[test] + fn test_rustbuffer_new_with_size() { + let rbuf = RustBuffer::new_with_size(5); + assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8, 0, 0, 0, 0]); + + let rbuf = RustBuffer::new_with_size(0); + assert!(!rbuf.data.is_null()); + assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]); + } + + #[test] + fn test_rustbuffer_null_means_empty() { + // This is how foreign-language code might cheaply indicate an empty buffer. + let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 0) }; + assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]); + } + + #[test] + #[should_panic] + fn test_rustbuffer_null_must_have_no_capacity() { + // We guard against foreign-language code providing this kind of invalid struct. + let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 1) }; + rbuf.destroy_into_vec(); + } + #[test] + #[should_panic] + fn test_rustbuffer_null_must_have_zero_length() { + // We guard against foreign-language code providing this kind of invalid struct. + let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 12, 0) }; + rbuf.destroy_into_vec(); + } + + #[test] + #[should_panic] + fn test_rustbuffer_provided_capacity_must_be_non_negative() { + // We guard against foreign-language code providing this kind of invalid struct. + let mut v = vec![0u8, 1, 2]; + let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) }; + rbuf.destroy_into_vec(); + } + + #[test] + #[should_panic] + fn test_rustbuffer_provided_len_must_be_non_negative() { + // We guard against foreign-language code providing this kind of invalid struct. + let mut v = vec![0u8, 1, 2]; + let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) }; + rbuf.destroy_into_vec(); + } + + #[test] + #[should_panic] + fn test_rustbuffer_provided_len_must_not_exceed_capacity() { + // We guard against foreign-language code providing this kind of invalid struct. + let mut v = vec![0u8, 1, 2]; + let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, 2) }; + rbuf.destroy_into_vec(); + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs new file mode 100644 index 0000000000..53265393c0 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs @@ -0,0 +1,245 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! # Low-level support for calling rust functions +//! +//! This module helps the scaffolding code make calls to rust functions and pass back the result to the FFI bindings code. +//! +//! It handles: +//! - Catching panics +//! - Adapting the result of `Return::lower_return()` into either a return value or an +//! exception + +use crate::{FfiDefault, Lower, RustBuffer, UniFfiTag}; +use std::mem::MaybeUninit; +use std::panic; + +/// Represents the success/error of a rust call +/// +/// ## Usage +/// +/// - The consumer code creates a [RustCallStatus] with an empty [RustBuffer] and +/// [RustCallStatusCode::Success] (0) as the status code +/// - A pointer to this object is passed to the rust FFI function. This is an +/// "out parameter" which will be updated with any error that occurred during the function's +/// execution. +/// - After the call, if `code` is [RustCallStatusCode::Error] or [RustCallStatusCode::UnexpectedError] +/// then `error_buf` will be updated to contain a serialized error object. See +/// [RustCallStatusCode] for what gets serialized. The consumer is responsible for freeing `error_buf`. +/// +/// ## Layout/fields +/// +/// The layout of this struct is important since consumers on the other side of the FFI need to +/// construct it. If this were a C struct, it would look like: +/// +/// ```c,no_run +/// struct RustCallStatus { +/// int8_t code; +/// RustBuffer error_buf; +/// }; +/// ``` +#[repr(C)] +pub struct RustCallStatus { + pub code: RustCallStatusCode, + // code is signed because unsigned types are experimental in Kotlin + pub error_buf: MaybeUninit<RustBuffer>, + // error_buf is MaybeUninit to avoid dropping the value that the consumer code sends in: + // - Consumers should send in a zeroed out RustBuffer. In this case dropping is a no-op and + // avoiding the drop is a small optimization. + // - If consumers pass in invalid data, then we should avoid trying to drop it. In + // particular, we don't want to try to free any data the consumer has allocated. + // + // `MaybeUninit` requires unsafe code, since we are preventing rust from dropping the value. + // To use this safely we need to make sure that no code paths set this twice, since that will + // leak the first `RustBuffer`. +} + +impl RustCallStatus { + pub fn cancelled() -> Self { + Self { + code: RustCallStatusCode::Cancelled, + error_buf: MaybeUninit::new(RustBuffer::new()), + } + } + + pub fn error(message: impl Into<String>) -> Self { + Self { + code: RustCallStatusCode::UnexpectedError, + error_buf: MaybeUninit::new(<String as Lower<UniFfiTag>>::lower(message.into())), + } + } +} + +impl Default for RustCallStatus { + fn default() -> Self { + Self { + code: RustCallStatusCode::Success, + error_buf: MaybeUninit::uninit(), + } + } +} + +/// Result of a FFI call to a Rust function +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum RustCallStatusCode { + /// Successful call. + Success = 0, + /// Expected error, corresponding to the `Result::Err` variant. [RustCallStatus::error_buf] + /// will contain the serialized error. + Error = 1, + /// Unexpected error. [RustCallStatus::error_buf] will contain a serialized message string + UnexpectedError = 2, + /// Async function cancelled. [RustCallStatus::error_buf] will be empty and does not need to + /// be freed. + /// + /// This is only returned for async functions and only if the bindings code uses the + /// [rust_future_cancel] call. + Cancelled = 3, +} + +/// Handle a scaffolding calls +/// +/// `callback` is responsible for making the actual Rust call and returning a special result type: +/// - For successfull calls, return `Ok(value)` +/// - For errors that should be translated into thrown exceptions in the foreign code, serialize +/// the error into a `RustBuffer`, then return `Ok(buf)` +/// - The success type, must implement `FfiDefault`. +/// - `Return::lower_return` returns `Result<>` types that meet the above criteria> +/// - If the function returns a `Ok` value it will be unwrapped and returned +/// - If the function returns a `Err` value: +/// - `out_status.code` will be set to [RustCallStatusCode::Error]. +/// - `out_status.error_buf` will be set to a newly allocated `RustBuffer` containing the error. The calling +/// code is responsible for freeing the `RustBuffer` +/// - `FfiDefault::ffi_default()` is returned, although foreign code should ignore this value +/// - If the function panics: +/// - `out_status.code` will be set to `CALL_PANIC` +/// - `out_status.error_buf` will be set to a newly allocated `RustBuffer` containing a +/// serialized error message. The calling code is responsible for freeing the `RustBuffer` +/// - `FfiDefault::ffi_default()` is returned, although foreign code should ignore this value +pub fn rust_call<F, R>(out_status: &mut RustCallStatus, callback: F) -> R +where + F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>, + R: FfiDefault, +{ + rust_call_with_out_status(out_status, callback).unwrap_or_else(R::ffi_default) +} + +/// Make a Rust call and update `RustCallStatus` based on the result. +/// +/// If the call succeeds this returns Some(v) and doesn't touch out_status +/// If the call fails (including Err results), this returns None and updates out_status +/// +/// This contains the shared code between `rust_call` and `rustfuture::do_wake`. +pub(crate) fn rust_call_with_out_status<F, R>( + out_status: &mut RustCallStatus, + callback: F, +) -> Option<R> +where + F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>, +{ + let result = panic::catch_unwind(|| { + crate::panichook::ensure_setup(); + callback() + }); + match result { + // Happy path. Note: no need to update out_status in this case because the calling code + // initializes it to [RustCallStatusCode::Success] + Ok(Ok(v)) => Some(v), + // Callback returned an Err. + Ok(Err(buf)) => { + out_status.code = RustCallStatusCode::Error; + unsafe { + // Unsafe because we're setting the `MaybeUninit` value, see above for safety + // invariants. + out_status.error_buf.as_mut_ptr().write(buf); + } + None + } + // Callback panicked + Err(cause) => { + out_status.code = RustCallStatusCode::UnexpectedError; + // Try to coerce the cause into a RustBuffer containing a String. Since this code can + // panic, we need to use a second catch_unwind(). + let message_result = panic::catch_unwind(panic::AssertUnwindSafe(move || { + // The documentation suggests that it will *usually* be a str or String. + let message = if let Some(s) = cause.downcast_ref::<&'static str>() { + (*s).to_string() + } else if let Some(s) = cause.downcast_ref::<String>() { + s.clone() + } else { + "Unknown panic!".to_string() + }; + log::error!("Caught a panic calling rust code: {:?}", message); + <String as Lower<UniFfiTag>>::lower(message) + })); + if let Ok(buf) = message_result { + unsafe { + // Unsafe because we're setting the `MaybeUninit` value, see above for safety + // invariants. + out_status.error_buf.as_mut_ptr().write(buf); + } + } + // Ignore the error case. We've done all that we can at this point. In the bindings + // code, we handle this by checking if `error_buf` still has an empty `RustBuffer` and + // using a generic message. + None + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{test_util::TestError, Lift, LowerReturn}; + + fn create_call_status() -> RustCallStatus { + RustCallStatus { + code: RustCallStatusCode::Success, + error_buf: MaybeUninit::new(RustBuffer::new()), + } + } + + fn test_callback(a: u8) -> Result<i8, TestError> { + match a { + 0 => Ok(100), + 1 => Err(TestError("Error".to_owned())), + x => panic!("Unexpected value: {x}"), + } + } + + #[test] + fn test_rust_call() { + let mut status = create_call_status(); + let return_value = rust_call(&mut status, || { + <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(0)) + }); + + assert_eq!(status.code, RustCallStatusCode::Success); + assert_eq!(return_value, 100); + + rust_call(&mut status, || { + <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(1)) + }); + assert_eq!(status.code, RustCallStatusCode::Error); + unsafe { + assert_eq!( + <TestError as Lift<UniFfiTag>>::try_lift(status.error_buf.assume_init()).unwrap(), + TestError("Error".to_owned()) + ); + } + + let mut status = create_call_status(); + rust_call(&mut status, || { + <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(2)) + }); + assert_eq!(status.code, RustCallStatusCode::UnexpectedError); + unsafe { + assert_eq!( + <String as Lift<UniFfiTag>>::try_lift(status.error_buf.assume_init()).unwrap(), + "Unexpected value: 2" + ); + } + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture.rs new file mode 100644 index 0000000000..0c1a24174b --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture.rs @@ -0,0 +1,735 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI. +//! +//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`. +//! +//! # The big picture +//! +//! We implement async foreign functions using a simplified version of the Future API: +//! +//! 0. At startup, register a [RustFutureContinuationCallback] by calling +//! rust_future_continuation_callback_set. +//! 1. Call the scaffolding function to get a [RustFutureHandle] +//! 2a. In a loop: +//! - Call [rust_future_poll] +//! - Suspend the function until the [rust_future_poll] continuation function is called +//! - If the continuation was function was called with [RustFuturePoll::Ready], then break +//! otherwise continue. +//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the +//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to +//! enter a cancelled state. +//! 3. Call [rust_future_complete] to get the result of the future. +//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This: +//! - Releases any resources held by the future +//! - Calls any continuation callbacks that have not been called yet +//! +//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*` +//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C", +//! and manually monomorphized in the case of [rust_future_complete]. See +//! `uniffi_macros/src/setup_scaffolding.rs` for details. +//! +//! ## How does `Future` work exactly? +//! +//! A [`Future`] in Rust does nothing. When calling an async function, it just +//! returns a `Future` but nothing has happened yet. To start the computation, +//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if +//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically +//! means: +//! +//! > Please, try to poll me later, maybe the result will be ready! +//! +//! This model is very different than what other languages do, but it can actually +//! be translated quite easily, fortunately for us! +//! +//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does +//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the +//! `Future`: that's where they are polled. +//! +//! But… wait another minute… how does the executor know when to poll a [`Future`]? +//! Does it poll them randomly in an endless loop? Well, no, actually it depends +//! on the executor! A well-designed `Future` and executor work as follows. +//! Normally, when [`Future::poll`] is called, a [`Context`] argument is +//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a +//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will +//! signal the executor to poll a particular `Future`. A `Future` will clone +//! or pass-by-ref the waker to somewhere, as a callback, a completion, a +//! function, or anything, to the system that is responsible to notify when a +//! task is completed. So, to recap, the waker is _not_ responsible for waking the +//! `Future`, it _is_ responsible for _signaling_ the executor that a particular +//! `Future` should be polled again. That's why the documentation of +//! [`Poll::Pending`] specifies: +//! +//! > When a function returns `Pending`, the function must also ensure that the +//! > current task is scheduled to be awoken when progress can be made. +//! +//! “awakening” is done by using the `Waker`. +//! +//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html +//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll +//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready +//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending +//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html +//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html +//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html + +use std::{ + future::Future, + marker::PhantomData, + mem, + ops::Deref, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Wake}, +}; + +use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; + +/// Result code for [rust_future_poll]. This is passed to the continuation function. +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum RustFuturePoll { + /// The future is ready and is waiting for [rust_future_complete] to be called + Ready = 0, + /// The future might be ready and [rust_future_poll] should be called again + MaybeReady = 1, +} + +/// Foreign callback that's passed to [rust_future_poll] +/// +/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again +/// to continue progress on the future. +pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll); + +/// Opaque handle for a Rust future that's stored by the foreign language code +#[repr(transparent)] +pub struct RustFutureHandle(*const ()); + +// === Public FFI API === + +/// Create a new [RustFutureHandle] +/// +/// For each exported async function, UniFFI will create a scaffolding function that uses this to +/// create the [RustFutureHandle] to pass to the foreign code. +pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> RustFutureHandle +where + // F is the future type returned by the exported async function. It needs to be Send + `static + // since it will move between threads for an indeterminate amount of time as the foreign + // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, + // since we synchronize all access to the values. + F: Future<Output = T> + Send + 'static, + // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + + // 'static for the same reason as F. + T: LowerReturn<UT> + Send + 'static, + // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. + UT: Send + 'static, +{ + // Create a RustFuture and coerce to `Arc<dyn RustFutureFfi>`, which is what we use to + // implement the FFI + let future_ffi = RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>; + // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it + // to the foreign code. + let boxed_ffi = Box::new(future_ffi); + // We can now create a RustFutureHandle + RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ()) +} + +/// Poll a Rust future +/// +/// When the future is ready to progress the continuation will be called with the `data` value and +/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called +/// exactly once. +/// +/// # Safety +/// +/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll<ReturnType>( + handle: RustFutureHandle, + callback: RustFutureContinuationCallback, + data: *const (), +) { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.clone().ffi_poll(callback, data) +} + +/// Cancel a Rust future +/// +/// Any current and future continuations will be immediately called with RustFuturePoll::Ready. +/// +/// This is needed for languages like Swift, which continuation to wait for the continuation to be +/// called when tasks are cancelled. +/// +/// # Safety +/// +/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.clone().ffi_cancel() +} + +/// Complete a Rust future +/// +/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for +/// each supported FFI type. +/// +/// # Safety +/// +/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free] +/// - The `T` param must correctly correspond to the [rust_future_new] call. It must +/// be `<Output as LowerReturn<UT>>::ReturnType` +pub unsafe fn rust_future_complete<ReturnType>( + handle: RustFutureHandle, + out_status: &mut RustCallStatus, +) -> ReturnType { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.ffi_complete(out_status) +} + +/// Free a Rust future, dropping the strong reference and releasing all references held by the +/// future. +/// +/// # Safety +/// +/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_free<ReturnType>(handle: RustFutureHandle) { + let future = Box::from_raw(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.ffi_free() +} + +/// Thread-safe storage for [RustFutureContinuationCallback] data +/// +/// The basic guarantee is that all data pointers passed in are passed out exactly once to the +/// foreign continuation callback. This enables us to uphold the [rust_future_poll] guarantee. +/// +/// [ContinuationDataCell] also tracks cancellation, which is closely tied to continuation data. +#[derive(Debug)] +enum ContinuationDataCell { + /// No continuations set, neither wake() nor cancel() called. + Empty, + /// `wake()` was called when there was no continuation set. The next time `store` is called, + /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady` + Waked, + /// The future has been cancelled, any future `store` calls should immediately result in the + /// continuation being called with `RustFuturePoll::Ready`. + Cancelled, + /// Continuation set, the next time `wake()` is called is called, we should invoke it. + Set(RustFutureContinuationCallback, *const ()), +} + +impl ContinuationDataCell { + fn new() -> Self { + Self::Empty + } + + /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or + /// `Cancelled` state, call the continuation immediately with the data. + fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) { + match self { + Self::Empty => *self = Self::Set(callback, data), + Self::Set(old_callback, old_data) => { + log::error!( + "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?" + ); + old_callback(*old_data, RustFuturePoll::Ready); + *self = Self::Set(callback, data); + } + Self::Waked => { + *self = Self::Empty; + callback(data, RustFuturePoll::MaybeReady); + } + Self::Cancelled => { + callback(data, RustFuturePoll::Ready); + } + } + } + + fn wake(&mut self) { + match self { + // If we had a continuation set, then call it and transition to the `Empty` state. + Self::Set(callback, old_data) => { + let old_data = *old_data; + let callback = *callback; + *self = Self::Empty; + callback(old_data, RustFuturePoll::MaybeReady); + } + // If we were in the `Empty` state, then transition to `Waked`. The next time `store` + // is called, we will immediately call the continuation. + Self::Empty => *self = Self::Waked, + // This is a no-op if we were in the `Cancelled` or `Waked` state. + _ => (), + } + } + + fn cancel(&mut self) { + if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) { + callback(old_data, RustFuturePoll::Ready); + } + } + + fn is_cancelled(&self) -> bool { + matches!(self, Self::Cancelled) + } +} + +// ContinuationDataCell is Send + Sync as long we handle the *const () pointer correctly + +unsafe impl Send for ContinuationDataCell {} +unsafe impl Sync for ContinuationDataCell {} + +/// Wraps the actual future we're polling +struct WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + // Note: this could be a single enum, but that would make it easy to mess up the future pinning + // guarantee. For example you might want to call `std::mem::take()` to try to get the result, + // but if the future happened to be stored that would move and break all internal references. + future: Option<F>, + result: Option<Result<T::ReturnType, RustCallStatus>>, +} + +impl<F, T, UT> WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn new(future: F) -> Self { + Self { + future: Some(future), + result: None, + } + } + + // Poll the future and check if it's ready or not + fn poll(&mut self, context: &mut Context<'_>) -> bool { + if self.result.is_some() { + true + } else if let Some(future) = &mut self.future { + // SAFETY: We can call Pin::new_unchecked because: + // - This is the only time we get a &mut to `self.future` + // - We never poll the future after it's moved (for example by using take()) + // - We never move RustFuture, which contains us. + // - RustFuture is private to this module so no other code can move it. + let pinned = unsafe { Pin::new_unchecked(future) }; + // Run the poll and lift the result if it's ready + let mut out_status = RustCallStatus::default(); + let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status( + &mut out_status, + // This closure uses a `&mut F` value, which means it's not UnwindSafe by + // default. If the future panics, it may be in an invalid state. + // + // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None` + // case below and we will never poll the future again. + panic::AssertUnwindSafe(|| match pinned.poll(context) { + Poll::Pending => Ok(Poll::Pending), + Poll::Ready(v) => T::lower_return(v).map(Poll::Ready), + }), + ); + match result { + Some(Poll::Pending) => false, + Some(Poll::Ready(v)) => { + self.future = None; + self.result = Some(Ok(v)); + true + } + None => { + self.future = None; + self.result = Some(Err(out_status)); + true + } + } + } else { + log::error!("poll with neither future nor result set"); + true + } + } + + fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType { + let mut return_value = T::ReturnType::ffi_default(); + match self.result.take() { + Some(Ok(v)) => return_value = v, + Some(Err(call_status)) => *out_status = call_status, + None => *out_status = RustCallStatus::cancelled(), + } + self.free(); + return_value + } + + fn free(&mut self) { + self.future = None; + self.result = None; + } +} + +// If F and T are Send, then WrappedFuture is too +// +// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising +// that we will treat the raw pointer properly, for example by not returning it twice. +unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ +} + +/// Future that the foreign code is awaiting +struct RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + // This Mutex should never block if our code is working correctly, since there should not be + // multiple threads calling [Self::poll] and/or [Self::complete] at the same time. + future: Mutex<WrappedFuture<F, T, UT>>, + continuation_data: Mutex<ContinuationDataCell>, + // UT is used as the generic parameter for [LowerReturn]. + // Let's model this with PhantomData as a function that inputs a UT value. + _phantom: PhantomData<fn(UT) -> ()>, +} + +impl<F, T, UT> RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn new(future: F, _tag: UT) -> Arc<Self> { + Arc::new(Self { + future: Mutex::new(WrappedFuture::new(future)), + continuation_data: Mutex::new(ContinuationDataCell::new()), + _phantom: PhantomData, + }) + } + + fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { + let ready = self.is_cancelled() || { + let mut locked = self.future.lock().unwrap(); + let waker: std::task::Waker = Arc::clone(&self).into(); + locked.poll(&mut Context::from_waker(&waker)) + }; + if ready { + callback(data, RustFuturePoll::Ready) + } else { + self.continuation_data.lock().unwrap().store(callback, data); + } + } + + fn is_cancelled(&self) -> bool { + self.continuation_data.lock().unwrap().is_cancelled() + } + + fn wake(&self) { + self.continuation_data.lock().unwrap().wake(); + } + + fn cancel(&self) { + self.continuation_data.lock().unwrap().cancel(); + } + + fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.future.lock().unwrap().complete(call_status) + } + + fn free(self: Arc<Self>) { + // Call cancel() to send any leftover data to the continuation callback + self.continuation_data.lock().unwrap().cancel(); + // Ensure we drop our inner future, releasing all held references + self.future.lock().unwrap().free(); + } +} + +impl<F, T, UT> Wake for RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn wake(self: Arc<Self>) { + self.deref().wake() + } + + fn wake_by_ref(self: &Arc<Self>) { + self.deref().wake() + } +} + +/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to +/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this +/// allows you to use RustFuture functionality without knowing the concrete Future type, which is +/// unnamable. +/// +/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of +/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need +/// to create a poll, cancel, complete, and free scaffolding function for each exported async +/// function. That would add ~1kb binary size per exported function based on a quick estimate on a +/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and +/// only create those functions for each of the 13 possible FFI return types. +#[doc(hidden)] +trait RustFutureFfi<ReturnType> { + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()); + fn ffi_cancel(&self); + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; + fn ffi_free(self: Arc<Self>); +} + +impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { + self.poll(callback, data) + } + + fn ffi_cancel(&self) { + self.cancel() + } + + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.complete(call_status) + } + + fn ffi_free(self: Arc<Self>) { + self.free(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; + use once_cell::sync::OnceCell; + use std::task::Waker; + + // Sender/Receiver pair that we use for testing + struct Channel { + result: Option<Result<String, TestError>>, + waker: Option<Waker>, + } + + struct Sender(Arc<Mutex<Channel>>); + + impl Sender { + fn wake(&self) { + let inner = self.0.lock().unwrap(); + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } + + fn send(&self, value: Result<String, TestError>) { + let mut inner = self.0.lock().unwrap(); + if inner.result.replace(value).is_some() { + panic!("value already sent"); + } + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } + } + + struct Receiver(Arc<Mutex<Channel>>); + + impl Future for Receiver { + type Output = Result<String, TestError>; + + fn poll( + self: Pin<&mut Self>, + context: &mut Context<'_>, + ) -> Poll<Result<String, TestError>> { + let mut inner = self.0.lock().unwrap(); + match &inner.result { + Some(v) => Poll::Ready(v.clone()), + None => { + inner.waker = Some(context.waker().clone()); + Poll::Pending + } + } + } + } + + // Create a sender and rust future that we can use for testing + fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) { + let channel = Arc::new(Mutex::new(Channel { + result: None, + waker: None, + })); + let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag); + (Sender(channel), rust_future) + } + + /// Poll a Rust future and get an OnceCell that's set when the continuation is called + fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> { + let cell = Arc::new(OnceCell::new()); + let cell_ptr = Arc::into_raw(cell.clone()) as *const (); + rust_future.clone().ffi_poll(poll_continuation, cell_ptr); + cell + } + + extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) { + let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) }; + cell.set(code).expect("Error setting OnceCell"); + } + + fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) { + let mut out_status_code = RustCallStatus::default(); + let return_value = rust_future.ffi_complete(&mut out_status_code); + (return_value, out_status_code) + } + + #[test] + fn test_success() { + let (sender, rust_future) = channel(); + + // Test polling the rust future before it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.wake(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Test polling the rust future when it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Ok("All done".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Future polls should immediately return ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Complete the future + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), + "All done" + ); + } + + #[test] + fn test_error() { + let (sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Err("Something went wrong".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Error); + unsafe { + assert_eq!( + <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer( + call_status.error_buf.assume_init() + ) + .unwrap(), + TestError::from("Something went wrong"), + ) + } + } + + // Once `complete` is called, the inner future should be released, even if wakers still hold a + // reference to the RustFuture + #[test] + fn test_cancel() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + rust_future.ffi_cancel(); + // Cancellation should immediately invoke the callback with RustFuturePoll::Ready + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Future polls should immediately invoke the callback with RustFuturePoll::Ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Cancelled); + } + + // Once `free` is called, the inner future should be released, even if wakers still hold a + // reference to the RustFuture + #[test] + fn test_release_future() { + let (sender, rust_future) = channel(); + // Create a weak reference to the channel to use to check if rust_future has dropped its + // future. + let channel_weak = Arc::downgrade(&sender.0); + drop(sender); + // Create an extra ref to rust_future, simulating a waker that still holds a reference to + // it + let rust_future2 = rust_future.clone(); + + // Complete the rust future + rust_future.ffi_free(); + // Even though rust_future is still alive, the channel shouldn't be + assert!(Arc::strong_count(&rust_future2) > 0); + assert_eq!(channel_weak.strong_count(), 0); + assert!(channel_weak.upgrade().is_none()); + } + + // If `free` is called with a continuation still stored, we should call it them then. + // + // This shouldn't happen in practice, but it seems like good defensive programming + #[test] + fn test_complete_with_stored_continuation() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + rust_future.ffi_free(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + } + + // Test what happens if we see a `wake()` call while we're polling the future. This can + // happen, for example, with futures that are handled by a tokio thread pool. We should + // schedule another poll of the future in this case. + #[test] + fn test_wake_during_poll() { + let mut first_time = true; + let future = std::future::poll_fn(move |ctx| { + if first_time { + first_time = false; + // Wake the future while we are in the middle of polling it + ctx.waker().clone().wake(); + Poll::Pending + } else { + // The second time we're polled, we're ready + Poll::Ready("All done".to_owned()) + } + }); + let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = + RustFuture::new(future, crate::UniFfiTag); + let continuation_result = poll(&rust_future); + // The continuation function should called immediately + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + // A second poll should finish the future + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), + "All done" + ); + } +} diff --git a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs new file mode 100644 index 0000000000..af18f3873b --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs @@ -0,0 +1,562 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// This module contains builtin `FFIConverter` implementations. These cover: +/// - Simple privitive types: u8, i32, String, Arc<T>, etc +/// - Composite types: Vec<T>, Option<T>, etc. +/// - SystemTime and Duration, which maybe shouldn`t be built-in, but have been historically and +/// we want to continue to support them for now. +/// +/// As described in +/// https://mozilla.github.io/uniffi-rs/internals/lifting_and_lowering.html#code-generation-and-the-fficonverter-trait, +/// we use the following system: +/// +/// - Each UniFFIed crate defines a unit struct named `UniFfiTag` +/// - We define an `impl FFIConverter<UniFfiTag> for Type` for each type that we want to pass +/// across the FFI. +/// - When generating the code, we use the `<T as ::uniffi::FFIConverter<crate::UniFfiTag>>` impl +/// to lift/lower/serialize types for a crate. +/// +/// This crate needs to implement `FFIConverter<UT>` on `UniFfiTag` instances for all UniFFI +/// consumer crates. To do this, it defines blanket impls like `impl<UT> FFIConverter<UT> for u8`. +/// "UT" means an abitrary `UniFfiTag` type. +use crate::{ + check_remaining, derive_ffi_traits, ffi_converter_rust_buffer_lift_and_lower, metadata, + ConvertError, FfiConverter, ForeignExecutor, Lift, LiftReturn, Lower, LowerReturn, + MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError, +}; +use anyhow::bail; +use bytes::buf::{Buf, BufMut}; +use paste::paste; +use std::{ + collections::HashMap, + convert::TryFrom, + error::Error, + sync::Arc, + time::{Duration, SystemTime}, +}; + +/// Blanket implementation of `FfiConverter` for numeric primitives. +/// +/// Numeric primitives have a straightforward mapping into C-compatible numeric types, +/// sice they are themselves a C-compatible numeric type! +macro_rules! impl_ffi_converter_for_num_primitive { + ($T:ty, $type_code:expr) => { + paste! { + unsafe impl<UT> FfiConverter<UT> for $T { + type FfiType = $T; + + fn lower(obj: $T) -> Self::FfiType { + obj + } + + fn try_lift(v: Self::FfiType) -> Result<$T> { + Ok(v) + } + + fn write(obj: $T, buf: &mut Vec<u8>) { + buf.[<put_ $T>](obj); + } + + fn try_read(buf: &mut &[u8]) -> Result<$T> { + check_remaining(buf, std::mem::size_of::<$T>())?; + Ok(buf.[<get_ $T>]()) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code($type_code); + } + } + }; +} + +impl_ffi_converter_for_num_primitive!(u8, metadata::codes::TYPE_U8); +impl_ffi_converter_for_num_primitive!(i8, metadata::codes::TYPE_I8); +impl_ffi_converter_for_num_primitive!(u16, metadata::codes::TYPE_U16); +impl_ffi_converter_for_num_primitive!(i16, metadata::codes::TYPE_I16); +impl_ffi_converter_for_num_primitive!(u32, metadata::codes::TYPE_U32); +impl_ffi_converter_for_num_primitive!(i32, metadata::codes::TYPE_I32); +impl_ffi_converter_for_num_primitive!(u64, metadata::codes::TYPE_U64); +impl_ffi_converter_for_num_primitive!(i64, metadata::codes::TYPE_I64); +impl_ffi_converter_for_num_primitive!(f32, metadata::codes::TYPE_F32); +impl_ffi_converter_for_num_primitive!(f64, metadata::codes::TYPE_F64); + +/// Support for passing boolean values via the FFI. +/// +/// Booleans are passed as an `i8` in order to avoid problems with handling +/// C-compatible boolean values on JVM-based languages. +unsafe impl<UT> FfiConverter<UT> for bool { + type FfiType = i8; + + fn lower(obj: bool) -> Self::FfiType { + i8::from(obj) + } + + fn try_lift(v: Self::FfiType) -> Result<bool> { + Ok(match v { + 0 => false, + 1 => true, + _ => bail!("unexpected byte for Boolean"), + }) + } + + fn write(obj: bool, buf: &mut Vec<u8>) { + buf.put_i8(<Self as FfiConverter<UT>>::lower(obj)); + } + + fn try_read(buf: &mut &[u8]) -> Result<bool> { + check_remaining(buf, 1)?; + <Self as FfiConverter<UT>>::try_lift(buf.get_i8()) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_BOOL); +} + +/// Support for passing Strings via the FFI. +/// +/// Unlike many other implementations of `FfiConverter`, this passes a struct containing +/// a raw pointer rather than copying the data from one side to the other. This is a +/// safety hazard, but turns out to be pretty nice for useability. This struct +/// *must* be a valid `RustBuffer` and it *must* contain valid utf-8 data (in other +/// words, it *must* be a `Vec<u8>` suitable for use as an actual rust `String`). +/// +/// When serialized in a buffer, strings are represented as a i32 byte length +/// followed by utf8-encoded bytes. (It's a signed integer because unsigned types are +/// currently experimental in Kotlin). +unsafe impl<UT> FfiConverter<UT> for String { + type FfiType = RustBuffer; + + // This returns a struct with a raw pointer to the underlying bytes, so it's very + // important that it consume ownership of the String, which is relinquished to the + // foreign language code (and can be restored by it passing the pointer back). + fn lower(obj: String) -> Self::FfiType { + RustBuffer::from_vec(obj.into_bytes()) + } + + // The argument here *must* be a uniquely-owned `RustBuffer` previously obtained + // from `lower` above, and hence must be the bytes of a valid rust string. + fn try_lift(v: Self::FfiType) -> Result<String> { + let v = v.destroy_into_vec(); + // This turns the buffer back into a `String` without copying the data + // and without re-checking it for validity of the utf8. If the `RustBuffer` + // came from a valid String then there's no point in re-checking the utf8, + // and if it didn't then bad things are probably going to happen regardless + // of whether we check for valid utf8 data or not. + Ok(unsafe { String::from_utf8_unchecked(v) }) + } + + fn write(obj: String, buf: &mut Vec<u8>) { + // N.B. `len()` gives us the length in bytes, not in chars or graphemes. + // TODO: it would be nice not to panic here. + let len = i32::try_from(obj.len()).unwrap(); + buf.put_i32(len); // We limit strings to u32::MAX bytes + buf.put(obj.as_bytes()); + } + + fn try_read(buf: &mut &[u8]) -> Result<String> { + check_remaining(buf, 4)?; + let len = usize::try_from(buf.get_i32())?; + check_remaining(buf, len)?; + // N.B: In the general case `Buf::chunk()` may return partial data. + // But in the specific case of `<&[u8] as Buf>` it returns the full slice, + // so there is no risk of having less than `len` bytes available here. + let bytes = &buf.chunk()[..len]; + let res = String::from_utf8(bytes.to_vec())?; + buf.advance(len); + Ok(res) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_STRING); +} + +/// Support for passing timestamp values via the FFI. +/// +/// Timestamps values are currently always passed by serializing to a buffer. +/// +/// Timestamps are represented on the buffer by an i64 that indicates the +/// direction and the magnitude in seconds of the offset from epoch, and a +/// u32 that indicates the nanosecond portion of the offset magnitude. The +/// nanosecond portion is expected to be between 0 and 999,999,999. +/// +/// To build an epoch offset the absolute value of the seconds portion of the +/// offset should be combined with the nanosecond portion. This is because +/// the sign of the seconds portion represents the direction of the offset +/// overall. The sign of the seconds portion can then be used to determine +/// if the total offset should be added to or subtracted from the unix epoch. +unsafe impl<UT> FfiConverter<UT> for SystemTime { + ffi_converter_rust_buffer_lift_and_lower!(UT); + + fn write(obj: SystemTime, buf: &mut Vec<u8>) { + let mut sign = 1; + let epoch_offset = obj + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_else(|error| { + sign = -1; + error.duration() + }); + // This panic should never happen as SystemTime typically stores seconds as i64 + let seconds = sign + * i64::try_from(epoch_offset.as_secs()) + .expect("SystemTime overflow, seconds greater than i64::MAX"); + + buf.put_i64(seconds); + buf.put_u32(epoch_offset.subsec_nanos()); + } + + fn try_read(buf: &mut &[u8]) -> Result<SystemTime> { + check_remaining(buf, 12)?; + let seconds = buf.get_i64(); + let nanos = buf.get_u32(); + let epoch_offset = Duration::new(seconds.wrapping_abs() as u64, nanos); + + if seconds >= 0 { + Ok(SystemTime::UNIX_EPOCH + epoch_offset) + } else { + Ok(SystemTime::UNIX_EPOCH - epoch_offset) + } + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_SYSTEM_TIME); +} + +/// Support for passing duration values via the FFI. +/// +/// Duration values are currently always passed by serializing to a buffer. +/// +/// Durations are represented on the buffer by a u64 that indicates the +/// magnitude in seconds, and a u32 that indicates the nanosecond portion +/// of the magnitude. The nanosecond portion is expected to be between 0 +/// and 999,999,999. +unsafe impl<UT> FfiConverter<UT> for Duration { + ffi_converter_rust_buffer_lift_and_lower!(UT); + + fn write(obj: Duration, buf: &mut Vec<u8>) { + buf.put_u64(obj.as_secs()); + buf.put_u32(obj.subsec_nanos()); + } + + fn try_read(buf: &mut &[u8]) -> Result<Duration> { + check_remaining(buf, 12)?; + Ok(Duration::new(buf.get_u64(), buf.get_u32())) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_DURATION); +} + +// Support for passing optional values via the FFI. +// +// Optional values are currently always passed by serializing to a buffer. +// We write either a zero byte for `None`, or a one byte followed by the containing +// item for `Some`. +// +// In future we could do the same optimization as rust uses internally, where the +// `None` option is represented as a null pointer and the `Some` as a valid pointer, +// but that seems more fiddly and less safe in the short term, so it can wait. + +unsafe impl<UT, T: Lower<UT>> Lower<UT> for Option<T> { + type FfiType = RustBuffer; + + fn write(obj: Option<T>, buf: &mut Vec<u8>) { + match obj { + None => buf.put_i8(0), + Some(v) => { + buf.put_i8(1); + T::write(v, buf); + } + } + } + + fn lower(obj: Option<T>) -> RustBuffer { + Self::lower_into_rust_buffer(obj) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_OPTION).concat(T::TYPE_ID_META); +} + +unsafe impl<UT, T: Lift<UT>> Lift<UT> for Option<T> { + type FfiType = RustBuffer; + + fn try_read(buf: &mut &[u8]) -> Result<Option<T>> { + check_remaining(buf, 1)?; + Ok(match buf.get_i8() { + 0 => None, + 1 => Some(T::try_read(buf)?), + _ => bail!("unexpected tag byte for Option"), + }) + } + + fn try_lift(buf: RustBuffer) -> Result<Option<T>> { + Self::try_lift_from_rust_buffer(buf) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_OPTION).concat(T::TYPE_ID_META); +} + +// Support for passing vectors of values via the FFI. +// +// Vectors are currently always passed by serializing to a buffer. +// We write a `i32` item count followed by each item in turn. +// (It's a signed type due to limits of the JVM). +// +// Ideally we would pass `Vec<u8>` directly as a `RustBuffer` rather +// than serializing, and perhaps even pass other vector types using a +// similar struct. But that's for future work. + +unsafe impl<UT, T: Lower<UT>> Lower<UT> for Vec<T> { + type FfiType = RustBuffer; + + fn write(obj: Vec<T>, buf: &mut Vec<u8>) { + // TODO: would be nice not to panic here :-/ + let len = i32::try_from(obj.len()).unwrap(); + buf.put_i32(len); // We limit arrays to i32::MAX items + for item in obj { + <T as Lower<UT>>::write(item, buf); + } + } + + fn lower(obj: Vec<T>) -> RustBuffer { + Self::lower_into_rust_buffer(obj) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_VEC).concat(T::TYPE_ID_META); +} + +/// Support for associative arrays via the FFI - `record<u32, u64>` in UDL. +/// HashMaps are currently always passed by serializing to a buffer. +/// We write a `i32` entries count followed by each entry (string +/// key followed by the value) in turn. +/// (It's a signed type due to limits of the JVM). +unsafe impl<UT, T: Lift<UT>> Lift<UT> for Vec<T> { + type FfiType = RustBuffer; + + fn try_read(buf: &mut &[u8]) -> Result<Vec<T>> { + check_remaining(buf, 4)?; + let len = usize::try_from(buf.get_i32())?; + let mut vec = Vec::with_capacity(len); + for _ in 0..len { + vec.push(<T as Lift<UT>>::try_read(buf)?) + } + Ok(vec) + } + + fn try_lift(buf: RustBuffer) -> Result<Vec<T>> { + Self::try_lift_from_rust_buffer(buf) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_VEC).concat(T::TYPE_ID_META); +} + +unsafe impl<K, V, UT> Lower<UT> for HashMap<K, V> +where + K: Lower<UT> + std::hash::Hash + Eq, + V: Lower<UT>, +{ + type FfiType = RustBuffer; + + fn write(obj: HashMap<K, V>, buf: &mut Vec<u8>) { + // TODO: would be nice not to panic here :-/ + let len = i32::try_from(obj.len()).unwrap(); + buf.put_i32(len); // We limit HashMaps to i32::MAX entries + for (key, value) in obj { + <K as Lower<UT>>::write(key, buf); + <V as Lower<UT>>::write(value, buf); + } + } + + fn lower(obj: HashMap<K, V>) -> RustBuffer { + Self::lower_into_rust_buffer(obj) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_HASH_MAP) + .concat(K::TYPE_ID_META) + .concat(V::TYPE_ID_META); +} + +unsafe impl<K, V, UT> Lift<UT> for HashMap<K, V> +where + K: Lift<UT> + std::hash::Hash + Eq, + V: Lift<UT>, +{ + type FfiType = RustBuffer; + + fn try_read(buf: &mut &[u8]) -> Result<HashMap<K, V>> { + check_remaining(buf, 4)?; + let len = usize::try_from(buf.get_i32())?; + let mut map = HashMap::with_capacity(len); + for _ in 0..len { + let key = <K as Lift<UT>>::try_read(buf)?; + let value = <V as Lift<UT>>::try_read(buf)?; + map.insert(key, value); + } + Ok(map) + } + + fn try_lift(buf: RustBuffer) -> Result<HashMap<K, V>> { + Self::try_lift_from_rust_buffer(buf) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_HASH_MAP) + .concat(K::TYPE_ID_META) + .concat(V::TYPE_ID_META); +} + +/// FFI support for [ForeignExecutor] +/// +/// These are passed over the FFI as opaque pointer-sized types representing the foreign executor. +/// The foreign bindings may use an actual pointer to the executor object, or a usized integer +/// handle. +unsafe impl<UT> FfiConverter<UT> for ForeignExecutor { + type FfiType = crate::ForeignExecutorHandle; + + // Passing these back to the foreign bindings is currently not supported + fn lower(executor: Self) -> Self::FfiType { + executor.handle + } + + fn write(executor: Self, buf: &mut Vec<u8>) { + // Use native endian when writing these values, so they can be casted to pointer values + match std::mem::size_of::<usize>() { + // Use native endian when reading these values, so they can be casted to pointer values + 4 => buf.put_u32_ne(executor.handle.0 as u32), + 8 => buf.put_u64_ne(executor.handle.0 as u64), + n => panic!("Invalid usize width: {n}"), + }; + } + + fn try_lift(executor: Self::FfiType) -> Result<Self> { + Ok(ForeignExecutor::new(executor)) + } + + fn try_read(buf: &mut &[u8]) -> Result<Self> { + let usize_val = match std::mem::size_of::<usize>() { + // Use native endian when reading these values, so they can be casted to pointer values + 4 => buf.get_u32_ne() as usize, + 8 => buf.get_u64_ne() as usize, + n => panic!("Invalid usize width: {n}"), + }; + <Self as FfiConverter<UT>>::try_lift(crate::ForeignExecutorHandle(usize_val as *const ())) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_FOREIGN_EXECUTOR); +} + +derive_ffi_traits!(blanket u8); +derive_ffi_traits!(blanket i8); +derive_ffi_traits!(blanket u16); +derive_ffi_traits!(blanket i16); +derive_ffi_traits!(blanket u32); +derive_ffi_traits!(blanket i32); +derive_ffi_traits!(blanket u64); +derive_ffi_traits!(blanket i64); +derive_ffi_traits!(blanket f32); +derive_ffi_traits!(blanket f64); +derive_ffi_traits!(blanket bool); +derive_ffi_traits!(blanket String); +derive_ffi_traits!(blanket Duration); +derive_ffi_traits!(blanket SystemTime); +derive_ffi_traits!(blanket ForeignExecutor); + +// For composite types, derive LowerReturn, LiftReturn, etc, from Lift/Lower. +// +// Note that this means we don't get specialized return handling. For example, if we could return +// an `Option<Result<>>` we would always return that type directly and never throw. +derive_ffi_traits!(impl<T, UT> LowerReturn<UT> for Option<T> where Option<T>: Lower<UT>); +derive_ffi_traits!(impl<T, UT> LiftReturn<UT> for Option<T> where Option<T>: Lift<UT>); +derive_ffi_traits!(impl<T, UT> LiftRef<UT> for Option<T> where Option<T>: Lift<UT>); + +derive_ffi_traits!(impl<T, UT> LowerReturn<UT> for Vec<T> where Vec<T>: Lower<UT>); +derive_ffi_traits!(impl<T, UT> LiftReturn<UT> for Vec<T> where Vec<T>: Lift<UT>); +derive_ffi_traits!(impl<T, UT> LiftRef<UT> for Vec<T> where Vec<T>: Lift<UT>); + +derive_ffi_traits!(impl<K, V, UT> LowerReturn<UT> for HashMap<K, V> where HashMap<K, V>: Lower<UT>); +derive_ffi_traits!(impl<K, V, UT> LiftReturn<UT> for HashMap<K, V> where HashMap<K, V>: Lift<UT>); +derive_ffi_traits!(impl<K, V, UT> LiftRef<UT> for HashMap<K, V> where HashMap<K, V>: Lift<UT>); + +// For Arc we derive all the traits, but have to write it all out because we need an unsized T bound +derive_ffi_traits!(impl<T, UT> Lower<UT> for Arc<T> where Arc<T>: FfiConverter<UT>, T: ?Sized); +derive_ffi_traits!(impl<T, UT> Lift<UT> for Arc<T> where Arc<T>: FfiConverter<UT>, T: ?Sized); +derive_ffi_traits!(impl<T, UT> LowerReturn<UT> for Arc<T> where Arc<T>: Lower<UT>, T: ?Sized); +derive_ffi_traits!(impl<T, UT> LiftReturn<UT> for Arc<T> where Arc<T>: Lift<UT>, T: ?Sized); +derive_ffi_traits!(impl<T, UT> LiftRef<UT> for Arc<T> where Arc<T>: Lift<UT>, T: ?Sized); + +// Implement LowerReturn/LiftReturn for the unit type (void returns) + +unsafe impl<UT> LowerReturn<UT> for () { + type ReturnType = (); + + fn lower_return(_: ()) -> Result<Self::ReturnType, RustBuffer> { + Ok(()) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); +} + +unsafe impl<UT> LiftReturn<UT> for () { + fn lift_callback_return(_buf: RustBuffer) -> Self {} + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); +} + +// Implement LowerReturn/LiftReturn for `Result<R, E>`. This is where we handle exceptions/Err +// results. + +unsafe impl<UT, R, E> LowerReturn<UT> for Result<R, E> +where + R: LowerReturn<UT>, + E: Lower<UT> + Error + Send + Sync + 'static, +{ + type ReturnType = R::ReturnType; + + fn lower_return(v: Self) -> Result<Self::ReturnType, RustBuffer> { + match v { + Ok(r) => R::lower_return(r), + Err(e) => Err(E::lower_into_rust_buffer(e)), + } + } + + fn handle_failed_lift(arg_name: &str, err: anyhow::Error) -> Self { + match err.downcast::<E>() { + Ok(actual_error) => Err(actual_error), + Err(ohno) => panic!("Failed to convert arg '{arg_name}': {ohno}"), + } + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_RESULT) + .concat(R::TYPE_ID_META) + .concat(E::TYPE_ID_META); +} + +unsafe impl<UT, R, E> LiftReturn<UT> for Result<R, E> +where + R: LiftReturn<UT>, + E: Lift<UT> + ConvertError<UT>, +{ + fn lift_callback_return(buf: RustBuffer) -> Self { + Ok(R::lift_callback_return(buf)) + } + + fn lift_callback_error(buf: RustBuffer) -> Self { + match E::try_lift_from_rust_buffer(buf) { + Ok(lifted_error) => Err(lifted_error), + Err(anyhow_error) => { + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { + reason: format!("Error lifting from rust buffer: {anyhow_error}"), + }) + } + } + } + + fn handle_callback_unexpected_error(e: UnexpectedUniFFICallbackError) -> Self { + Err(E::try_convert_unexpected_callback_error(e).unwrap_or_else(|e| panic!("{e}"))) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_RESULT) + .concat(R::TYPE_ID_META) + .concat(E::TYPE_ID_META); +} diff --git a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs new file mode 100644 index 0000000000..3b5914e32f --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs @@ -0,0 +1,466 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Traits that define how to transfer values via the FFI layer. +//! +//! These traits define how to pass values over the FFI in various ways: as arguments or as return +//! values, from Rust to the foreign side and vice-versa. These traits are mainly used by the +//! proc-macro generated code. The goal is to allow the proc-macros to go from a type name to the +//! correct function for a given FFI operation. +//! +//! The traits form a sort-of tree structure from general to specific: +//! ```ignore +//! +//! [FfiConverter] +//! | +//! ----------------------------- +//! | | +//! [Lower] [Lift] +//! | | +//! | -------------- +//! | | | +//! [LowerReturn] [LiftRef] [LiftReturn] +//! ``` +//! +//! The `derive_ffi_traits` macro can be used to derive the specific traits from the general ones. +//! Here's the main ways we implement these traits: +//! +//! * For most types we implement [FfiConverter] and use [derive_ffi_traits] to implement the rest +//! * If a type can only be lifted/lowered, then we implement [Lift] or [Lower] and use +//! [derive_ffi_traits] to implement the rest +//! * If a type needs special-case handling, like `Result<>` and `()`, we implement the traits +//! directly. +//! +//! FfiConverter has a generic parameter, that's filled in with a type local to the UniFFI consumer crate. +//! This allows us to work around the Rust orphan rules for remote types. See +//! `https://mozilla.github.io/uniffi-rs/internals/lifting_and_lowering.html#code-generation-and-the-fficonverter-trait` +//! for details. +//! +//! ## Safety +//! +//! All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +//! that it's safe to pass your type out to foreign-language code and back again. Buggy +//! implementations of this trait might violate some assumptions made by the generated code, +//! or might not match with the corresponding code in the generated foreign-language bindings. +//! These traits should not be used directly, only in generated code, and the generated code should +//! have fixture tests to test that everything works correctly together. + +use std::{borrow::Borrow, sync::Arc}; + +use anyhow::bail; +use bytes::Buf; + +use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError}; + +/// Generalized FFI conversions +/// +/// This trait is not used directly by the code generation, but implement this and calling +/// [derive_ffi_traits] is a simple way to implement all the traits that are. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait FfiConverter<UT>: Sized { + /// The low-level type used for passing values of this type over the FFI. + /// + /// This must be a C-compatible type (e.g. a numeric primitive, a `#[repr(C)]` struct) into + /// which values of the target rust type can be converted. + /// + /// For complex data types, we currently recommend using `RustBuffer` and serializing + /// the data for transfer. In theory it could be possible to build a matching + /// `#[repr(C)]` struct for a complex data type and pass that instead, but explicit + /// serialization is simpler and safer as a starting point. + /// + /// If a type implements multiple FFI traits, `FfiType` must be the same for all of them. + type FfiType: FfiDefault; + + /// Lower a rust value of the target type, into an FFI value of type Self::FfiType. + /// + /// This trait method is used for sending data from rust to the foreign language code, + /// by (hopefully cheaply!) converting it into something that can be passed over the FFI + /// and reconstructed on the other side. + /// + /// Note that this method takes an owned value; this allows it to transfer ownership in turn to + /// the foreign language code, e.g. by boxing the value and passing a pointer. + fn lower(obj: Self) -> Self::FfiType; + + /// Lift a rust value of the target type, from an FFI value of type Self::FfiType. + /// + /// This trait method is used for receiving data from the foreign language code in rust, + /// by (hopefully cheaply!) converting it from a low-level FFI value of type Self::FfiType + /// into a high-level rust value of the target type. + /// + /// Since we cannot statically guarantee that the foreign-language code will send valid + /// values of type Self::FfiType, this method is fallible. + fn try_lift(v: Self::FfiType) -> Result<Self>; + + /// Write a rust value into a buffer, to send over the FFI in serialized form. + /// + /// This trait method can be used for sending data from rust to the foreign language code, + /// in cases where we're not able to use a special-purpose FFI type and must fall back to + /// sending serialized bytes. + /// + /// Note that this method takes an owned value because it's transferring ownership + /// to the foreign language code via the RustBuffer. + fn write(obj: Self, buf: &mut Vec<u8>); + + /// Read a rust value from a buffer, received over the FFI in serialized form. + /// + /// This trait method can be used for receiving data from the foreign language code in rust, + /// in cases where we're not able to use a special-purpose FFI type and must fall back to + /// receiving serialized bytes. + /// + /// Since we cannot statically guarantee that the foreign-language code will send valid + /// serialized bytes for the target type, this method is fallible. + /// + /// Note the slightly unusual type here - we want a mutable reference to a slice of bytes, + /// because we want to be able to advance the start of the slice after reading an item + /// from it (but will not mutate the actual contents of the slice). + fn try_read(buf: &mut &[u8]) -> Result<Self>; + + /// Type ID metadata, serialized into a [MetadataBuffer]. + /// + /// If a type implements multiple FFI traits, `TYPE_ID_META` must be the same for all of them. + const TYPE_ID_META: MetadataBuffer; +} + +/// FfiConverter for Arc-types +/// +/// This trait gets around the orphan rule limitations, which prevent library crates from +/// implementing `FfiConverter` on an Arc. When this is implemented for T, we generate an +/// `FfiConverter` impl for Arc<T>. +/// +/// Note: There's no need for `FfiConverterBox`, since Box is a fundamental type. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait FfiConverterArc<UT>: Send + Sync { + type FfiType: FfiDefault; + + fn lower(obj: Arc<Self>) -> Self::FfiType; + fn try_lift(v: Self::FfiType) -> Result<Arc<Self>>; + fn write(obj: Arc<Self>, buf: &mut Vec<u8>); + fn try_read(buf: &mut &[u8]) -> Result<Arc<Self>>; + + const TYPE_ID_META: MetadataBuffer; +} + +unsafe impl<T, UT> FfiConverter<UT> for Arc<T> +where + T: FfiConverterArc<UT> + ?Sized, +{ + type FfiType = T::FfiType; + + fn lower(obj: Self) -> Self::FfiType { + T::lower(obj) + } + + fn try_lift(v: Self::FfiType) -> Result<Self> { + T::try_lift(v) + } + + fn write(obj: Self, buf: &mut Vec<u8>) { + T::write(obj, buf) + } + + fn try_read(buf: &mut &[u8]) -> Result<Self> { + T::try_read(buf) + } + + const TYPE_ID_META: MetadataBuffer = T::TYPE_ID_META; +} + +/// Lift values passed by the foreign code over the FFI into Rust values +/// +/// This is used by the code generation to handle arguments. It's usually derived from +/// [FfiConverter], except for types that only support lifting but not lowering. +/// +/// See [FfiConverter] for a discussion of the methods +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait Lift<UT>: Sized { + type FfiType; + + fn try_lift(v: Self::FfiType) -> Result<Self>; + + fn try_read(buf: &mut &[u8]) -> Result<Self>; + + /// Convenience method + fn try_lift_from_rust_buffer(v: RustBuffer) -> Result<Self> { + let vec = v.destroy_into_vec(); + let mut buf = vec.as_slice(); + let value = Self::try_read(&mut buf)?; + match Buf::remaining(&buf) { + 0 => Ok(value), + n => bail!("junk data left in buffer after lifting (count: {n})",), + } + } + + const TYPE_ID_META: MetadataBuffer; +} + +/// Lower Rust values to pass them to the foreign code +/// +/// This is used to pass arguments to callback interfaces. It's usually derived from +/// [FfiConverter], except for types that only support lowering but not lifting. +/// +/// See [FfiConverter] for a discussion of the methods +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait Lower<UT>: Sized { + type FfiType: FfiDefault; + + fn lower(obj: Self) -> Self::FfiType; + + fn write(obj: Self, buf: &mut Vec<u8>); + + /// Convenience method + fn lower_into_rust_buffer(obj: Self) -> RustBuffer { + let mut buf = ::std::vec::Vec::new(); + Self::write(obj, &mut buf); + RustBuffer::from_vec(buf) + } + + const TYPE_ID_META: MetadataBuffer; +} + +/// Return Rust values to the foreign code +/// +/// This is usually derived from [Lift], but we special case types like `Result<>` and `()`. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait LowerReturn<UT>: Sized { + /// The type that should be returned by scaffolding functions for this type. + /// + /// When derived, it's the same as `FfiType`. + type ReturnType: FfiDefault; + + /// Lower this value for scaffolding function return + /// + /// This method converts values into the `Result<>` type that [rust_call] expects. For + /// successful calls, return `Ok(lower_return)`. For errors that should be translated into + /// thrown exceptions on the foreign code, serialize the error into a RustBuffer and return + /// `Err(buf)` + fn lower_return(obj: Self) -> Result<Self::ReturnType, RustBuffer>; + + /// If possible, get a serialized error for failed argument lifts + /// + /// By default, we just panic and let `rust_call` handle things. However, for `Result<_, E>` + /// returns, if the anyhow error can be downcast to `E`, then serialize that and return it. + /// This results in the foreign code throwing a "normal" exception, rather than an unexpected + /// exception. + fn handle_failed_lift(arg_name: &str, e: anyhow::Error) -> Self { + panic!("Failed to convert arg '{arg_name}': {e}") + } + + const TYPE_ID_META: MetadataBuffer; +} + +/// Return foreign values to Rust +/// +/// This is usually derived from [Lower], but we special case types like `Result<>` and `()`. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait LiftReturn<UT>: Sized { + /// Lift a Rust value for a callback interface method result + fn lift_callback_return(buf: RustBuffer) -> Self; + + /// Lift a Rust value for a callback interface method error result + /// + /// This is called for "expected errors" -- the callback method returns a Result<> type and the + /// foreign code throws an exception that corresponds to the error type. + fn lift_callback_error(_buf: RustBuffer) -> Self { + panic!("Callback interface method returned unexpected error") + } + + /// Lift a Rust value for an unexpected callback interface error + /// + /// The main reason this is called is when the callback interface throws an error type that + /// doesn't match the Rust trait definition. It's also called for corner cases, like when the + /// foreign code doesn't follow the FFI contract. + /// + /// The default implementation panics unconditionally. Errors used in callback interfaces + /// handle this using the `From<UnexpectedUniFFICallbackError>` impl that the library author + /// must provide. + fn handle_callback_unexpected_error(e: UnexpectedUniFFICallbackError) -> Self { + panic!("Callback interface failure: {e}") + } + + const TYPE_ID_META: MetadataBuffer; +} + +/// Lift references +/// +/// This is usually derived from [Lift] and also implemented for the inner `T` value of smart +/// pointers. For example, if `Lift` is implemented for `Arc<T>`, then we implement this to lift +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +/// `&T` using the Arc. +pub unsafe trait LiftRef<UT> { + type LiftType: Lift<UT> + Borrow<Self>; +} + +pub trait ConvertError<UT>: Sized { + fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result<Self>; +} + +/// Derive FFI traits +/// +/// This can be used to derive: +/// * [Lower] and [Lift] from [FfiConverter] +/// * [LowerReturn] from [Lower] +/// * [LiftReturn] and [LiftRef] from [Lift] +/// +/// Usage: +/// ```ignore +/// +/// // Derive everything from [FfiConverter] for all Uniffi tags +/// ::uniffi::derive_ffi_traits!(blanket Foo) +/// // Derive everything from [FfiConverter] for the local crate::UniFfiTag +/// ::uniffi::derive_ffi_traits!(local Foo) +/// // To derive a specific trait, write out the impl item minus the actual block +/// ::uniffi::derive_ffi_traits!(impl<T, UT> LowerReturn<UT> for Option<T>) +/// ``` +#[macro_export] +#[allow(clippy::crate_in_macro_def)] +macro_rules! derive_ffi_traits { + (blanket $ty:ty) => { + $crate::derive_ffi_traits!(impl<UT> Lower<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> Lift<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> LowerReturn<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> LiftReturn<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> LiftRef<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> ConvertError<UT> for $ty); + }; + + (local $ty:ty) => { + $crate::derive_ffi_traits!(impl Lower<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl Lift<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl LowerReturn<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl LiftReturn<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl LiftRef<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl ConvertError<crate::UniFfiTag> for $ty); + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? Lower<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::Lower<$ut> for $ty $(where $($where)*)* + { + type FfiType = <Self as $crate::FfiConverter<$ut>>::FfiType; + + fn lower(obj: Self) -> Self::FfiType { + <Self as $crate::FfiConverter<$ut>>::lower(obj) + } + + fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) { + <Self as $crate::FfiConverter<$ut>>::write(obj, buf) + } + + const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::FfiConverter<$ut>>::TYPE_ID_META; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? Lift<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::Lift<$ut> for $ty $(where $($where)*)* + { + type FfiType = <Self as $crate::FfiConverter<$ut>>::FfiType; + + fn try_lift(v: Self::FfiType) -> $crate::deps::anyhow::Result<Self> { + <Self as $crate::FfiConverter<$ut>>::try_lift(v) + } + + fn try_read(buf: &mut &[u8]) -> $crate::deps::anyhow::Result<Self> { + <Self as $crate::FfiConverter<$ut>>::try_read(buf) + } + + const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::FfiConverter<$ut>>::TYPE_ID_META; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? LowerReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::LowerReturn<$ut> for $ty $(where $($where)*)* + { + type ReturnType = <Self as $crate::Lower<$ut>>::FfiType; + + fn lower_return(obj: Self) -> $crate::deps::anyhow::Result<Self::ReturnType, $crate::RustBuffer> { + Ok(<Self as $crate::Lower<$ut>>::lower(obj)) + } + + const TYPE_ID_META: $crate::MetadataBuffer =<Self as $crate::Lower<$ut>>::TYPE_ID_META; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::LiftReturn<$ut> for $ty $(where $($where)*)* + { + fn lift_callback_return(buf: $crate::RustBuffer) -> Self { + <Self as $crate::Lift<$ut>>::try_lift_from_rust_buffer(buf) + .expect("Error reading callback interface result") + } + + const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::Lift<$ut>>::TYPE_ID_META; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftRef<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::LiftRef<$ut> for $ty $(where $($where)*)* + { + type LiftType = Self; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? ConvertError<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + impl $(<$($generic),*>)* $crate::ConvertError<$ut> for $ty $(where $($where)*)* + { + fn try_convert_unexpected_callback_error(e: $crate::UnexpectedUniFFICallbackError) -> $crate::deps::anyhow::Result<Self> { + $crate::convert_unexpected_error!(e, $ty) + } + } + }; +} diff --git a/third_party/rust/uniffi_core/src/lib.rs b/third_party/rust/uniffi_core/src/lib.rs new file mode 100644 index 0000000000..c84b403dce --- /dev/null +++ b/third_party/rust/uniffi_core/src/lib.rs @@ -0,0 +1,324 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! # Runtime support code for uniffi +//! +//! This crate provides the small amount of runtime code that is required by the generated uniffi +//! component scaffolding in order to transfer data back and forth across the C-style FFI layer, +//! as well as some utilities for testing the generated bindings. +//! +//! The key concept here is the [`FfiConverter`] trait, which is responsible for converting between +//! a Rust type and a low-level C-style type that can be passed across the FFI: +//! +//! * How to [represent](FfiConverter::FfiType) values of the Rust type in the low-level C-style type +//! system of the FFI layer. +//! * How to ["lower"](FfiConverter::lower) values of the Rust type into an appropriate low-level +//! FFI value. +//! * How to ["lift"](FfiConverter::try_lift) low-level FFI values back into values of the Rust +//! type. +//! * How to [write](FfiConverter::write) values of the Rust type into a buffer, for cases +//! where they are part of a compound data structure that is serialized for transfer. +//! * How to [read](FfiConverter::try_read) values of the Rust type from buffer, for cases +//! where they are received as part of a compound data structure that was serialized for transfer. +//! * How to [return](FfiConverter::lower_return) values of the Rust type from scaffolding +//! functions. +//! +//! This logic encapsulates the Rust-side handling of data transfer. Each foreign-language binding +//! must also implement a matching set of data-handling rules for each data type. +//! +//! In addition to the core `FfiConverter` trait, we provide a handful of struct definitions useful +//! for passing core rust types over the FFI, such as [`RustBuffer`]. + +#![warn(rust_2018_idioms, unused_qualifications)] + +use anyhow::bail; +use bytes::buf::Buf; + +// Make Result<> public to support external impls of FfiConverter +pub use anyhow::Result; + +pub mod ffi; +mod ffi_converter_impls; +mod ffi_converter_traits; +pub mod metadata; + +pub use ffi::*; +pub use ffi_converter_traits::{ + ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, +}; +pub use metadata::*; + +// Re-export the libs that we use in the generated code, +// so the consumer doesn't have to depend on them directly. +pub mod deps { + pub use anyhow; + #[cfg(feature = "tokio")] + pub use async_compat; + pub use bytes; + pub use log; + pub use static_assertions; + // Export this dependency for the 0.25 branch so that we can use it in `setup_scaffolding.rs` + pub use once_cell; +} + +mod panichook; + +const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION"); + +// For the significance of this magic number 10 here, and the reason that +// it can't be a named constant, see the `check_compatible_version` function. +static_assertions::const_assert!(PACKAGE_VERSION.as_bytes().len() < 10); + +/// Check whether the uniffi runtime version is compatible a given uniffi_bindgen version. +/// +/// The result of this check may be used to ensure that generated Rust scaffolding is +/// using a compatible version of the uniffi runtime crate. It's a `const fn` so that it +/// can be used to perform such a check at compile time. +#[allow(clippy::len_zero)] +pub const fn check_compatible_version(bindgen_version: &'static str) -> bool { + // While UniFFI is still under heavy development, we require that + // the runtime support crate be precisely the same version as the + // build-time bindgen crate. + // + // What we want to achieve here is checking two strings for equality. + // Unfortunately Rust doesn't yet support calling the `&str` equals method + // in a const context. We can hack around that by doing a byte-by-byte + // comparison of the underlying bytes. + let package_version = PACKAGE_VERSION.as_bytes(); + let bindgen_version = bindgen_version.as_bytes(); + // What we want to achieve here is a loop over the underlying bytes, + // something like: + // ``` + // if package_version.len() != bindgen_version.len() { + // return false + // } + // for i in 0..package_version.len() { + // if package_version[i] != bindgen_version[i] { + // return false + // } + // } + // return true + // ``` + // Unfortunately stable Rust doesn't allow `if` or `for` in const contexts, + // so code like the above would only work in nightly. We can hack around it by + // statically asserting that the string is shorter than a certain length + // (currently 10 bytes) and then manually unrolling that many iterations of the loop. + // + // Yes, I am aware that this is horrific, but the externally-visible + // behaviour is quite nice for consumers! + package_version.len() == bindgen_version.len() + && (package_version.len() == 0 || package_version[0] == bindgen_version[0]) + && (package_version.len() <= 1 || package_version[1] == bindgen_version[1]) + && (package_version.len() <= 2 || package_version[2] == bindgen_version[2]) + && (package_version.len() <= 3 || package_version[3] == bindgen_version[3]) + && (package_version.len() <= 4 || package_version[4] == bindgen_version[4]) + && (package_version.len() <= 5 || package_version[5] == bindgen_version[5]) + && (package_version.len() <= 6 || package_version[6] == bindgen_version[6]) + && (package_version.len() <= 7 || package_version[7] == bindgen_version[7]) + && (package_version.len() <= 8 || package_version[8] == bindgen_version[8]) + && (package_version.len() <= 9 || package_version[9] == bindgen_version[9]) + && package_version.len() < 10 +} + +/// Assert that the uniffi runtime version matches an expected value. +/// +/// This is a helper hook for the generated Rust scaffolding, to produce a compile-time +/// error if the version of `uniffi_bindgen` used to generate the scaffolding was +/// incompatible with the version of `uniffi` being used at runtime. +#[macro_export] +macro_rules! assert_compatible_version { + ($v:expr $(,)?) => { + uniffi::deps::static_assertions::const_assert!(uniffi::check_compatible_version($v)); + }; +} + +/// Struct to use when we want to lift/lower/serialize types inside the `uniffi` crate. +struct UniFfiTag; + +/// A helper function to ensure we don't read past the end of a buffer. +/// +/// Rust won't actually let us read past the end of a buffer, but the `Buf` trait does not support +/// returning an explicit error in this case, and will instead panic. This is a look-before-you-leap +/// helper function to instead return an explicit error, to help with debugging. +pub fn check_remaining(buf: &[u8], num_bytes: usize) -> Result<()> { + if buf.remaining() < num_bytes { + bail!( + "not enough bytes remaining in buffer ({} < {num_bytes})", + buf.remaining(), + ); + } + Ok(()) +} + +/// Macro to implement lowering/lifting using a `RustBuffer` +/// +/// For complex types where it's too fiddly or too unsafe to convert them into a special-purpose +/// C-compatible value, you can use this trait to implement `lower()` in terms of `write()` and +/// `lift` in terms of `read()`. +/// +/// This macro implements the boilerplate needed to define `lower`, `lift` and `FFIType`. +#[macro_export] +macro_rules! ffi_converter_rust_buffer_lift_and_lower { + ($uniffi_tag:ty) => { + type FfiType = $crate::RustBuffer; + + fn lower(v: Self) -> $crate::RustBuffer { + let mut buf = ::std::vec::Vec::new(); + <Self as $crate::FfiConverter<$uniffi_tag>>::write(v, &mut buf); + $crate::RustBuffer::from_vec(buf) + } + + fn try_lift(buf: $crate::RustBuffer) -> $crate::Result<Self> { + let vec = buf.destroy_into_vec(); + let mut buf = vec.as_slice(); + let value = <Self as $crate::FfiConverter<$uniffi_tag>>::try_read(&mut buf)?; + match $crate::deps::bytes::Buf::remaining(&buf) { + 0 => Ok(value), + n => $crate::deps::anyhow::bail!( + "junk data left in buffer after lifting (count: {n})", + ), + } + } + }; +} + +/// Macro to implement `FfiConverter<T>` for a UniFfiTag using a different UniFfiTag +/// +/// This is used for external types +#[macro_export] +macro_rules! ffi_converter_forward { + // Forward a `FfiConverter` implementation + ($T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { + ::uniffi::do_ffi_converter_forward!( + FfiConverter, + $T, + $T, + $existing_impl_tag, + $new_impl_tag + ); + + $crate::derive_ffi_traits!(local $T); + }; +} + +/// Macro to implement `FfiConverterArc<T>` for a UniFfiTag using a different UniFfiTag +/// +/// This is used for external types +#[macro_export] +macro_rules! ffi_converter_arc_forward { + ($T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { + ::uniffi::do_ffi_converter_forward!( + FfiConverterArc, + ::std::sync::Arc<$T>, + $T, + $existing_impl_tag, + $new_impl_tag + ); + + // Note: no need to call derive_ffi_traits! because there is a blanket impl for all Arc<T> + }; +} + +// Generic code between the two macros above +#[doc(hidden)] +#[macro_export] +macro_rules! do_ffi_converter_forward { + ($trait:ident, $rust_type:ty, $T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { + unsafe impl $crate::$trait<$new_impl_tag> for $T { + type FfiType = <$T as $crate::$trait<$existing_impl_tag>>::FfiType; + + fn lower(obj: $rust_type) -> Self::FfiType { + <$T as $crate::$trait<$existing_impl_tag>>::lower(obj) + } + + fn try_lift(v: Self::FfiType) -> $crate::Result<$rust_type> { + <$T as $crate::$trait<$existing_impl_tag>>::try_lift(v) + } + + fn write(obj: $rust_type, buf: &mut Vec<u8>) { + <$T as $crate::$trait<$existing_impl_tag>>::write(obj, buf) + } + + fn try_read(buf: &mut &[u8]) -> $crate::Result<$rust_type> { + <$T as $crate::$trait<$existing_impl_tag>>::try_read(buf) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = + <$T as $crate::$trait<$existing_impl_tag>>::TYPE_ID_META; + } + }; +} + +#[cfg(test)] +mod test { + use super::{FfiConverter, UniFfiTag}; + use std::time::{Duration, SystemTime}; + + #[test] + fn timestamp_roundtrip_post_epoch() { + let expected = SystemTime::UNIX_EPOCH + Duration::new(100, 100); + let result = + <SystemTime as FfiConverter<UniFfiTag>>::try_lift(<SystemTime as FfiConverter< + UniFfiTag, + >>::lower(expected)) + .expect("Failed to lift!"); + assert_eq!(expected, result) + } + + #[test] + fn timestamp_roundtrip_pre_epoch() { + let expected = SystemTime::UNIX_EPOCH - Duration::new(100, 100); + let result = + <SystemTime as FfiConverter<UniFfiTag>>::try_lift(<SystemTime as FfiConverter< + UniFfiTag, + >>::lower(expected)) + .expect("Failed to lift!"); + assert_eq!( + expected, result, + "Expected results after lowering and lifting to be equal" + ) + } +} + +#[cfg(test)] +pub mod test_util { + use std::{error::Error, fmt}; + + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct TestError(pub String); + + // Use FfiConverter to simplify lifting TestError out of RustBuffer to check it + unsafe impl<UT> FfiConverter<UT> for TestError { + ffi_converter_rust_buffer_lift_and_lower!(UniFfiTag); + + fn write(obj: TestError, buf: &mut Vec<u8>) { + <String as FfiConverter<UniFfiTag>>::write(obj.0, buf); + } + + fn try_read(buf: &mut &[u8]) -> Result<TestError> { + <String as FfiConverter<UniFfiTag>>::try_read(buf).map(TestError) + } + + // Use a dummy value here since we don't actually need TYPE_ID_META + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::new(); + } + + impl fmt::Display for TestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } + } + + impl Error for TestError {} + + impl<T: Into<String>> From<T> for TestError { + fn from(v: T) -> Self { + Self(v.into()) + } + } + + derive_ffi_traits!(blanket TestError); +} diff --git a/third_party/rust/uniffi_core/src/metadata.rs b/third_party/rust/uniffi_core/src/metadata.rs new file mode 100644 index 0000000000..770d2b36d5 --- /dev/null +++ b/third_party/rust/uniffi_core/src/metadata.rs @@ -0,0 +1,244 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Pack UniFFI interface metadata into byte arrays +//! +//! In order to generate foreign bindings, we store interface metadata inside the library file +//! using exported static byte arrays. The foreign bindings code reads that metadata from the +//! library files and generates bindings based on that. +//! +//! The metadata static variables are generated by the proc-macros, which is an issue because the +//! proc-macros don't have knowledge of the entire interface -- they can only see the item they're +//! wrapping. For example, when a proc-macro sees a type name, it doesn't know anything about the +//! actual type: it could be a Record, an Enum, or even a type alias for a `Vec<>`/`Result<>`. +//! +//! This module helps bridge the gap by providing tools that allow the proc-macros to generate code +//! to encode the interface metadata: +//! - A set of const functions to build up metadata buffers with const expressions +//! - The `export_static_metadata_var!` macro, which creates the static variable from a const metadata +//! buffer. +//! - The `FfiConverter::TYPE_ID_META` const which encodes an identifier for that type in a +//! metadata buffer. +//! +//! `uniffi_bindgen::macro_metadata` contains the code to read the metadata from a library file. +//! `fixtures/metadata` has the tests. + +/// Metadata constants, make sure to keep this in sync with copy in `uniffi_meta::reader` +pub mod codes { + // Top-level metadata item codes + pub const FUNC: u8 = 0; + pub const METHOD: u8 = 1; + pub const RECORD: u8 = 2; + pub const ENUM: u8 = 3; + pub const INTERFACE: u8 = 4; + pub const ERROR: u8 = 5; + pub const NAMESPACE: u8 = 6; + pub const CONSTRUCTOR: u8 = 7; + pub const UDL_FILE: u8 = 8; + pub const CALLBACK_INTERFACE: u8 = 9; + pub const TRAIT_METHOD: u8 = 10; + pub const UNIFFI_TRAIT: u8 = 11; + pub const UNKNOWN: u8 = 255; + + // Type codes + pub const TYPE_U8: u8 = 0; + pub const TYPE_U16: u8 = 1; + pub const TYPE_U32: u8 = 2; + pub const TYPE_U64: u8 = 3; + pub const TYPE_I8: u8 = 4; + pub const TYPE_I16: u8 = 5; + pub const TYPE_I32: u8 = 6; + pub const TYPE_I64: u8 = 7; + pub const TYPE_F32: u8 = 8; + pub const TYPE_F64: u8 = 9; + pub const TYPE_BOOL: u8 = 10; + pub const TYPE_STRING: u8 = 11; + pub const TYPE_OPTION: u8 = 12; + pub const TYPE_RECORD: u8 = 13; + pub const TYPE_ENUM: u8 = 14; + // 15 no longer used. + pub const TYPE_INTERFACE: u8 = 16; + pub const TYPE_VEC: u8 = 17; + pub const TYPE_HASH_MAP: u8 = 18; + pub const TYPE_SYSTEM_TIME: u8 = 19; + pub const TYPE_DURATION: u8 = 20; + pub const TYPE_CALLBACK_INTERFACE: u8 = 21; + pub const TYPE_CUSTOM: u8 = 22; + pub const TYPE_RESULT: u8 = 23; + pub const TYPE_FUTURE: u8 = 24; + pub const TYPE_FOREIGN_EXECUTOR: u8 = 25; + pub const TYPE_UNIT: u8 = 255; + + // Literal codes for LiteralMetadata - note that we don't support + // all variants in the "emit/reader" context. + pub const LIT_STR: u8 = 0; + pub const LIT_INT: u8 = 1; + pub const LIT_FLOAT: u8 = 2; + pub const LIT_BOOL: u8 = 3; + pub const LIT_NULL: u8 = 4; +} + +const BUF_SIZE: usize = 4096; + +// This struct is a kludge around the fact that Rust const generic support doesn't quite handle our +// needs. +// +// We'd like to have code like this in `FfiConverter`: +// +// ``` +// const TYPE_ID_META_SIZE: usize; +// const TYPE_ID_META: [u8, Self::TYPE_ID_META_SIZE]; +// ``` +// +// This would define a metadata buffer, correctly size for the data needed. However, associated +// consts as generic params aren't supported yet. +// +// To work around this, we use `const MetadataBuffer` values, which contain fixed-sized buffers +// with enough capacity to store our largest metadata arrays. Since the `MetadataBuffer` values +// are const, they're only stored at compile time and the extra bytes don't end up contributing to +// the final binary size. This was tested on Rust `1.66.0` with `--release` by increasing +// `BUF_SIZE` and checking the compiled library sizes. +#[derive(Debug)] +pub struct MetadataBuffer { + pub bytes: [u8; BUF_SIZE], + pub size: usize, +} + +impl MetadataBuffer { + pub const fn new() -> Self { + Self { + bytes: [0; BUF_SIZE], + size: 0, + } + } + + pub const fn from_code(value: u8) -> Self { + Self::new().concat_value(value) + } + + // Concatenate another buffer to this one. + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat(mut self, other: MetadataBuffer) -> MetadataBuffer { + assert!(self.size + other.size <= BUF_SIZE); + // It would be nice to use `copy_from_slice()`, but that's not allowed in const functions + // as of Rust 1.66. + let mut i = 0; + while i < other.size { + self.bytes[self.size] = other.bytes[i]; + self.size += 1; + i += 1; + } + self + } + + // Concatenate a `u8` value to this buffer + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_value(mut self, value: u8) -> Self { + assert!(self.size < BUF_SIZE); + self.bytes[self.size] = value; + self.size += 1; + self + } + + // Concatenate a `u32` value to this buffer + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_u32(mut self, value: u32) -> Self { + assert!(self.size + 4 <= BUF_SIZE); + // store the value as little-endian + self.bytes[self.size] = value as u8; + self.bytes[self.size + 1] = (value >> 8) as u8; + self.bytes[self.size + 2] = (value >> 16) as u8; + self.bytes[self.size + 3] = (value >> 24) as u8; + self.size += 4; + self + } + + // Concatenate a `bool` value to this buffer + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_bool(self, value: bool) -> Self { + self.concat_value(value as u8) + } + + // Concatenate a string to this buffer. + // + // Strings are encoded as a `u8` length, followed by the utf8 data. + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_str(mut self, string: &str) -> Self { + assert!(string.len() < 256); + assert!(self.size + string.len() < BUF_SIZE); + self.bytes[self.size] = string.len() as u8; + self.size += 1; + let bytes = string.as_bytes(); + let mut i = 0; + while i < bytes.len() { + self.bytes[self.size] = bytes[i]; + self.size += 1; + i += 1; + } + self + } + + // Create an array from this MetadataBuffer + // + // SIZE should always be `self.size`. This is part of the kludge to hold us over until Rust + // gets better const generic support. + pub const fn into_array<const SIZE: usize>(self) -> [u8; SIZE] { + let mut result: [u8; SIZE] = [0; SIZE]; + let mut i = 0; + while i < SIZE { + result[i] = self.bytes[i]; + i += 1; + } + result + } + + // Create a checksum from this MetadataBuffer + // + // This is used by the bindings code to verify that the library they link to is the same one + // that the bindings were generated from. + pub const fn checksum(&self) -> u16 { + calc_checksum(&self.bytes, self.size) + } +} + +impl AsRef<[u8]> for MetadataBuffer { + fn as_ref(&self) -> &[u8] { + &self.bytes[..self.size] + } +} + +// Create a checksum for a MetadataBuffer +// +// This is used by the bindings code to verify that the library they link to is the same one +// that the bindings were generated from. +pub const fn checksum_metadata(buf: &[u8]) -> u16 { + calc_checksum(buf, buf.len()) +} + +const fn calc_checksum(bytes: &[u8], size: usize) -> u16 { + // Taken from the fnv_hash() function from the FNV crate (https://github.com/servo/rust-fnv/blob/master/lib.rs). + // fnv_hash() hasn't been released in a version yet. + const INITIAL_STATE: u64 = 0xcbf29ce484222325; + const PRIME: u64 = 0x100000001b3; + + let mut hash = INITIAL_STATE; + let mut i = 0; + while i < size { + hash ^= bytes[i] as u64; + hash = hash.wrapping_mul(PRIME); + i += 1; + } + // Convert the 64-bit hash to a 16-bit hash by XORing everything together + (hash ^ (hash >> 16) ^ (hash >> 32) ^ (hash >> 48)) as u16 +} diff --git a/third_party/rust/uniffi_core/src/panichook.rs b/third_party/rust/uniffi_core/src/panichook.rs new file mode 100644 index 0000000000..ef0ab86f1f --- /dev/null +++ b/third_party/rust/uniffi_core/src/panichook.rs @@ -0,0 +1,34 @@ +/// Initialize our panic handling hook to optionally log panics +#[cfg(feature = "log_panics")] +pub fn ensure_setup() { + use std::sync::Once; + static INIT_BACKTRACES: Once = Once::new(); + INIT_BACKTRACES.call_once(move || { + #[cfg(all(feature = "log_backtraces", not(target_os = "android")))] + { + std::env::set_var("RUST_BACKTRACE", "1"); + } + // Turn on a panic hook which logs both backtraces and the panic + // "Location" (file/line). We do both in case we've been stripped, + // ). + std::panic::set_hook(Box::new(move |panic_info| { + let (file, line) = if let Some(loc) = panic_info.location() { + (loc.file(), loc.line()) + } else { + // Apparently this won't happen but rust has reserved the + // ability to start returning None from location in some cases + // in the future. + ("<unknown>", 0) + }; + log::error!("### Rust `panic!` hit at file '{file}', line {line}"); + #[cfg(all(feature = "log_backtraces", not(target_os = "android")))] + { + log::error!(" Complete stack trace:\n{:?}", backtrace::Backtrace::new()); + } + })); + }); +} + +/// Initialize our panic handling hook to optionally log panics +#[cfg(not(feature = "log_panics"))] +pub fn ensure_setup() {} |