diff options
Diffstat (limited to 'third_party/rust/uniffi/src/ffi/foreigncallbacks.rs')
-rw-r--r-- | third_party/rust/uniffi/src/ffi/foreigncallbacks.rs | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/third_party/rust/uniffi/src/ffi/foreigncallbacks.rs b/third_party/rust/uniffi/src/ffi/foreigncallbacks.rs new file mode 100644 index 0000000000..092b635255 --- /dev/null +++ b/third_party/rust/uniffi/src/ffi/foreigncallbacks.rs @@ -0,0 +1,229 @@ +/* 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 super::RustBuffer; +use std::fmt; +use std::os::raw::c_int; +use std::sync::atomic::{AtomicUsize, Ordering}; + +/// 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 index is 1 indexed. Note that the list of methods is generated by at 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` is a serialized buffer of arguments to the function. UniFFI will deserialize it before +/// passing individual arguments 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. +/// * A callback returns: +/// - `-2` An error occured that was serialized to buf_ptr +/// - `-1` An unexpected error ocurred +/// - `0` is a deprecated way to signal that if the call succeeded, but did not modify buf_ptr +/// - `1` If the call succeeded. For non-void functions the return value should be serialized +/// to buf_ptr. +/// Note: The output buffer might still contain 0 bytes of data. +pub type ForeignCallback = unsafe extern "C" fn( + handle: u64, + method: u32, + args: RustBuffer, + buf_ptr: *mut RustBuffer, +) -> c_int; + +/// 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; + +// 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, ForeignCallback); +static_assertions::assert_eq_size!(usize, Option<ForeignCallback>); + +/// Struct to hold a foreign callback. +pub struct ForeignCallbackInternals { + callback_ptr: AtomicUsize, +} + +const EMPTY_PTR: usize = 0; + +impl ForeignCallbackInternals { + pub const fn new() -> Self { + ForeignCallbackInternals { + callback_ptr: AtomicUsize::new(EMPTY_PTR), + } + } + + pub fn set_callback(&self, callback: ForeignCallback) { + let as_usize = callback as usize; + let old_ptr = self.callback_ptr.compare_exchange( + EMPTY_PTR, + as_usize, + Ordering::SeqCst, + Ordering::SeqCst, + ); + match old_ptr { + // We get the previous value back. If this is anything except EMPTY_PTR, + // then this has been set before we get here. + Ok(EMPTY_PTR) => (), + _ => + // This is an internal bug, the other side of the FFI should ensure + // it sets this only once. + { + panic!("Bug: call set_callback multiple times. This is likely a uniffi bug") + } + }; + } + + pub fn get_callback(&self) -> Option<ForeignCallback> { + let ptr_value = self.callback_ptr.load(Ordering::SeqCst); + unsafe { std::mem::transmute::<usize, Option<ForeignCallback>>(ptr_value) } + } +} + +/// 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 {} |