summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/uniffi/src
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi/src')
-rw-r--r--third_party/rust/uniffi/src/ffi/ffidefault.rs52
-rw-r--r--third_party/rust/uniffi/src/ffi/foreignbytes.rs118
-rw-r--r--third_party/rust/uniffi/src/ffi/foreigncallbacks.rs229
-rw-r--r--third_party/rust/uniffi/src/ffi/mod.rs15
-rw-r--r--third_party/rust/uniffi/src/ffi/rustbuffer.rs353
-rw-r--r--third_party/rust/uniffi/src/ffi/rustcalls.rs279
-rw-r--r--third_party/rust/uniffi/src/lib.rs670
-rw-r--r--third_party/rust/uniffi/src/panichook.rs34
-rw-r--r--third_party/rust/uniffi/src/testing.rs149
9 files changed, 1899 insertions, 0 deletions
diff --git a/third_party/rust/uniffi/src/ffi/ffidefault.rs b/third_party/rust/uniffi/src/ffi/ffidefault.rs
new file mode 100644
index 0000000000..f247312be8
--- /dev/null
+++ b/third_party/rust/uniffi/src/ffi/ffidefault.rs
@@ -0,0 +1,52 @@
+/* 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! {
+ 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) }
+ }
+}
diff --git a/third_party/rust/uniffi/src/ffi/foreignbytes.rs b/third_party/rust/uniffi/src/ffi/foreignbytes.rs
new file mode 100644
index 0000000000..5ec93118ad
--- /dev/null
+++ b/third_party/rust/uniffi/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 = vec![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 = vec![0u8, 1, 2];
+ let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), -1) };
+ fbuf.as_slice();
+ }
+}
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 {}
diff --git a/third_party/rust/uniffi/src/ffi/mod.rs b/third_party/rust/uniffi/src/ffi/mod.rs
new file mode 100644
index 0000000000..73ee721435
--- /dev/null
+++ b/third_party/rust/uniffi/src/ffi/mod.rs
@@ -0,0 +1,15 @@
+/* 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/. */
+
+pub mod ffidefault;
+pub mod foreignbytes;
+pub mod foreigncallbacks;
+pub mod rustbuffer;
+pub mod rustcalls;
+
+use ffidefault::FfiDefault;
+pub use foreignbytes::*;
+pub use foreigncallbacks::*;
+pub use rustbuffer::*;
+pub use rustcalls::*;
diff --git a/third_party/rust/uniffi/src/ffi/rustbuffer.rs b/third_party/rust/uniffi/src/ffi/rustbuffer.rs
new file mode 100644
index 0000000000..63af586fb6
--- /dev/null
+++ b/third_party/rust/uniffi/src/ffi/rustbuffer.rs
@@ -0,0 +1,353 @@
+/* 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::{call_with_output, 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)]
+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,
+}
+
+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")
+ }
+
+ /// 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.
+#[no_mangle]
+pub extern "C" fn uniffi_rustbuffer_alloc(
+ size: i32,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(call_status, || {
+ 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.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_from_bytes(
+ bytes: ForeignBytes,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(call_status, || {
+ let bytes = bytes.as_slice();
+ 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.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
+ call_with_output(call_status, || RustBuffer::destroy(buf))
+}
+
+/// 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.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_reserve(
+ buf: RustBuffer,
+ additional: i32,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(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);
+ 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/src/ffi/rustcalls.rs b/third_party/rust/uniffi/src/ffi/rustcalls.rs
new file mode 100644
index 0000000000..a22f776d74
--- /dev/null
+++ b/third_party/rust/uniffi/src/ffi/rustcalls.rs
@@ -0,0 +1,279 @@
+/* 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 `Result<>` types into either a return value or an error
+
+use super::FfiDefault;
+use crate::{FfiConverter, RustBuffer, RustBufferFfiConverter};
+use anyhow::Result;
+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 `CALL_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 `CALL_ERROR` then `error_buf` will be updated to contain
+/// the serialized error object. 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;
+/// };
+/// ```
+///
+/// #### The `code` field.
+///
+/// - `CALL_SUCCESS` (0) for successful calls
+/// - `CALL_ERROR` (1) for calls that returned an `Err` value
+/// - `CALL_PANIC` (2) for calls that panicked
+///
+/// #### The `error_buf` field.
+///
+/// - For `CALL_ERROR` this is a `RustBuffer` with the serialized error. The consumer code is
+/// responsible for freeing this `RustBuffer`.
+#[repr(C)]
+pub struct RustCallStatus {
+ pub code: i8,
+ // 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 Default for RustCallStatus {
+ fn default() -> Self {
+ Self {
+ code: 0,
+ error_buf: MaybeUninit::uninit(),
+ }
+ }
+}
+
+#[allow(dead_code)]
+const CALL_SUCCESS: i8 = 0; // CALL_SUCCESS is set by the calling code
+const CALL_ERROR: i8 = 1;
+const CALL_PANIC: i8 = 2;
+
+// A trait for errors that can be thrown to the FFI code
+//
+// This gets implemented in uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
+pub trait FfiError: RustBufferFfiConverter {}
+
+// Generalized rust call handling function
+fn make_call<F, R>(out_status: &mut RustCallStatus, callback: F) -> R
+where
+ F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>,
+ R: FfiDefault,
+{
+ 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 CALL_SUCCESS
+ Ok(Ok(v)) => v,
+ // Callback returned an Err.
+ Ok(Err(buf)) => {
+ out_status.code = CALL_ERROR;
+ unsafe {
+ // Unsafe because we're setting the `MaybeUninit` value, see above for safety
+ // invariants.
+ out_status.error_buf.as_mut_ptr().write(buf);
+ }
+ R::ffi_default()
+ }
+ // Callback panicked
+ Err(cause) => {
+ out_status.code = CALL_PANIC;
+ // 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::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.
+ R::ffi_default()
+ }
+ }
+}
+
+/// Wrap a rust function call and return the result directly
+///
+/// `callback` is responsible for making the call to the Rust function. It must convert any return
+/// value into a type that implements `IntoFfi` (typically handled with `FfiConverter::lower()`).
+///
+/// - If the function succeeds then the function's return value will be returned to the outer code
+/// - If the function panics:
+/// - `out_status.code` will be set to `CALL_PANIC`
+/// - the return value is undefined
+pub fn call_with_output<F, R>(out_status: &mut RustCallStatus, callback: F) -> R
+where
+ F: panic::UnwindSafe + FnOnce() -> R,
+ R: FfiDefault,
+{
+ make_call(out_status, || Ok(callback()))
+}
+
+/// Wrap a rust function call that returns a `Result<_, RustBuffer>`
+///
+/// `callback` is responsible for making the call to the Rust function.
+/// - `callback` must convert any return value into a type that implements `IntoFfi`
+/// - `callback` must convert any `Error` the into a `RustBuffer` to be returned over the FFI
+/// - (Both of these are typically handled with `FfiConverter::lower()`)
+///
+/// - If the function returns an `Ok` value it will be unwrapped and returned
+/// - If the function returns an `Err`:
+/// - `out_status.code` will be set to `CALL_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`
+/// - the return value is undefined
+/// - If the function panics:
+/// - `out_status.code` will be set to `CALL_PANIC`
+/// - the return value is undefined
+pub fn call_with_result<F, R>(out_status: &mut RustCallStatus, callback: F) -> R
+where
+ F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>,
+ R: FfiDefault,
+{
+ make_call(out_status, callback)
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{FfiConverter, RustBufferFfiConverter};
+
+ fn function(a: u8) -> i8 {
+ match a {
+ 0 => 100,
+ x => panic!("Unexpected value: {x}"),
+ }
+ }
+
+ fn create_call_status() -> RustCallStatus {
+ RustCallStatus {
+ code: 0,
+ error_buf: MaybeUninit::new(RustBuffer::new()),
+ }
+ }
+
+ #[test]
+ fn test_call_with_output() {
+ let mut status = create_call_status();
+ let return_value = call_with_output(&mut status, || function(0));
+ assert_eq!(status.code, CALL_SUCCESS);
+ assert_eq!(return_value, 100);
+
+ call_with_output(&mut status, || function(1));
+ assert_eq!(status.code, CALL_PANIC);
+ unsafe {
+ assert_eq!(
+ String::try_lift(status.error_buf.assume_init()).unwrap(),
+ "Unexpected value: 1"
+ );
+ }
+ }
+
+ #[derive(Debug, PartialEq)]
+ struct TestError(String);
+
+ // Use RustBufferFfiConverter to simplify lifting TestError out of RustBuffer to check it
+ impl RustBufferFfiConverter for TestError {
+ type RustType = Self;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ <String as FfiConverter>::write(obj.0, buf);
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self> {
+ String::try_read(buf).map(TestError)
+ }
+ }
+
+ impl FfiError for TestError {}
+
+ fn function_with_result(a: u8) -> Result<i8, TestError> {
+ match a {
+ 0 => Ok(100),
+ 1 => Err(TestError("Error".to_owned())),
+ x => panic!("Unexpected value: {x}"),
+ }
+ }
+
+ #[test]
+ fn test_call_with_result() {
+ let mut status = create_call_status();
+ let return_value = call_with_result(&mut status, || {
+ function_with_result(0).map_err(TestError::lower)
+ });
+ assert_eq!(status.code, CALL_SUCCESS);
+ assert_eq!(return_value, 100);
+
+ call_with_result(&mut status, || {
+ function_with_result(1).map_err(TestError::lower)
+ });
+ assert_eq!(status.code, CALL_ERROR);
+ unsafe {
+ assert_eq!(
+ TestError::try_lift(status.error_buf.assume_init()).unwrap(),
+ TestError("Error".to_owned())
+ );
+ }
+
+ let mut status = create_call_status();
+ call_with_result(&mut status, || {
+ function_with_result(2).map_err(TestError::lower)
+ });
+ assert_eq!(status.code, CALL_PANIC);
+ unsafe {
+ assert_eq!(
+ String::try_lift(status.error_buf.assume_init()).unwrap(),
+ "Unexpected value: 2"
+ );
+ }
+ }
+}
diff --git a/third_party/rust/uniffi/src/lib.rs b/third_party/rust/uniffi/src/lib.rs
new file mode 100644
index 0000000000..df420760d5
--- /dev/null
+++ b/third_party/rust/uniffi/src/lib.rs
@@ -0,0 +1,670 @@
+/* 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.
+//!
+//! 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, BufMut};
+use paste::paste;
+use std::{
+ collections::HashMap,
+ convert::TryFrom,
+ time::{Duration, SystemTime},
+};
+
+// Make Result<> public to support external impls of FfiConverter
+pub use anyhow::Result;
+
+pub mod ffi;
+pub use ffi::*;
+
+// It would be nice if this module was behind a cfg(test) guard, but it
+// doesn't work between crates so let's hope LLVM tree-shaking works well.
+pub mod testing;
+
+// 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;
+ pub use bytes;
+ pub use log;
+ pub use static_assertions;
+}
+
+pub use uniffi_macros::{export, Object, Record};
+
+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));
+ };
+}
+
+/// Trait defining how to transfer values via the FFI layer.
+///
+/// The `FfiConverter` trait defines how to pass values of a particular type back-and-forth over
+/// the uniffi generated FFI layer, both as standalone argument or return values, and as
+/// part of serialized compound data structures.
+///
+/// (This trait is like the `IntoFfi` trait from `ffi_support`, but local to this crate
+/// so that we can add some alternative implementations for different builtin types,
+/// and so that we can add support for receiving as well as returning).
+///
+/// ## Safety
+///
+/// This is an unsafe trait (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.
+///
+/// In general, you should not need to implement this trait by hand, and should instead rely on
+/// implementations generated from your component UDL via the `uniffi-bindgen scaffolding` command.
+
+pub unsafe trait FfiConverter: Sized {
+ /// The type used in Rust code.
+ ///
+ /// For primitive / standard types, we implement `FfiConverter` on the type itself with `RustType=Self`.
+ /// For user-defined types we create a unit struct and implement it there. This sidesteps
+ /// Rust's orphan rules (ADR-0006).
+ type RustType;
+
+ /// 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.
+ type FfiType;
+
+ /// 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 someting that can be passed over the FFI
+ /// and reconstructed on the other side.
+ ///
+ /// Note that this method takes an owned `Self::RustType`; 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::RustType) -> 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::RustType>;
+
+ /// 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 `Self::RustType` because it's transfering ownership
+ /// to the foreign language code via the RustBuffer.
+ fn write(obj: Self::RustType, 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::RustType>;
+}
+
+/// 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(())
+}
+
+/// 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_via_ffi_for_num_primitive {
+ ($($T:ty,)+) => { impl_via_ffi_for_num_primitive!($($T),+); };
+ ($($T:ty),*) => {
+ $(
+ paste! {
+ unsafe impl FfiConverter for $T {
+ type RustType = Self;
+ type FfiType = Self;
+
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ obj
+ }
+
+ fn try_lift(v: Self::FfiType) -> Result<Self> {
+ Ok(v)
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.[<put_ $T>](obj);
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self> {
+ check_remaining(buf, std::mem::size_of::<$T>())?;
+ Ok(buf.[<get_ $T>]())
+ }
+ }
+ }
+ )*
+ };
+}
+
+impl_via_ffi_for_num_primitive! {
+ i8, u8, i16, u16, i32, u32, i64, u64, f32, 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 FfiConverter for bool {
+ type RustType = Self;
+ type FfiType = i8;
+
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ i8::from(obj)
+ }
+
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType> {
+ Ok(match v {
+ 0 => false,
+ 1 => true,
+ _ => bail!("unexpected byte for Boolean"),
+ })
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.put_i8(<bool as FfiConverter>::lower(obj));
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 1)?;
+ <bool as FfiConverter>::try_lift(buf.get_i8())
+ }
+}
+
+/// 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 FfiConverter for String {
+ type RustType = Self;
+ 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: Self::RustType) -> 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<Self::RustType> {
+ 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: Self::RustType, 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<Self::RustType> {
+ 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)
+ }
+}
+
+/// A helper trait 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()`.
+pub trait RustBufferFfiConverter: Sized {
+ type RustType;
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>);
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType>;
+}
+
+unsafe impl<T: RustBufferFfiConverter> FfiConverter for T {
+ type RustType = T::RustType;
+ type FfiType = RustBuffer;
+
+ fn lower(obj: Self::RustType) -> RustBuffer {
+ let mut buf = Vec::new();
+ <T as RustBufferFfiConverter>::write(obj, &mut buf);
+ RustBuffer::from_vec(buf)
+ }
+
+ fn try_lift(v: RustBuffer) -> Result<Self::RustType> {
+ let vec = v.destroy_into_vec();
+ let mut buf = vec.as_slice();
+ let value = T::try_read(&mut buf)?;
+ if buf.remaining() != 0 {
+ bail!("junk data left in buffer after lifting")
+ }
+ Ok(value)
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ T::write(obj, buf)
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ T::try_read(buf)
+ }
+}
+
+/// 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.
+impl RustBufferFfiConverter for SystemTime {
+ type RustType = Self;
+
+ fn write(obj: Self::RustType, 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<Self::RustType> {
+ 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)
+ }
+ }
+}
+
+/// 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.
+impl RustBufferFfiConverter for Duration {
+ type RustType = Self;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.put_u64(obj.as_secs());
+ buf.put_u32(obj.subsec_nanos());
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 12)?;
+ Ok(Duration::new(buf.get_u64(), buf.get_u32()))
+ }
+}
+
+/// 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.
+impl<T: FfiConverter> RustBufferFfiConverter for Option<T> {
+ type RustType = Option<T::RustType>;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ match obj {
+ None => buf.put_i8(0),
+ Some(v) => {
+ buf.put_i8(1);
+ <T as FfiConverter>::write(v, buf);
+ }
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 1)?;
+ Ok(match buf.get_i8() {
+ 0 => None,
+ 1 => Some(<T as FfiConverter>::try_read(buf)?),
+ _ => bail!("unexpected tag byte for Option"),
+ })
+ }
+}
+
+/// 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.
+impl<T: FfiConverter> RustBufferFfiConverter for Vec<T> {
+ type RustType = Vec<T::RustType>;
+
+ fn write(obj: Self::RustType, 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 FfiConverter>::write(item, buf);
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ 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 FfiConverter>::try_read(buf)?)
+ }
+ Ok(vec)
+ }
+}
+
+/// Support for associative arrays via the FFI.
+/// Note that because of webidl limitations,
+/// the key must always be of the String type.
+///
+/// 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).
+impl<K: FfiConverter, V: FfiConverter> RustBufferFfiConverter for HashMap<K, V>
+where
+ K::RustType: std::hash::Hash + Eq,
+{
+ type RustType = HashMap<K::RustType, V::RustType>;
+
+ fn write(obj: Self::RustType, 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 FfiConverter>::write(key, buf);
+ <V as FfiConverter>::write(value, buf);
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ 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 FfiConverter>::try_read(buf)?;
+ let value = <V as FfiConverter>::try_read(buf)?;
+ map.insert(key, value);
+ }
+ Ok(map)
+ }
+}
+
+/// Support for passing reference-counted shared objects via the FFI.
+///
+/// To avoid dealing with complex lifetime semantics over the FFI, any data passed
+/// by reference must be encapsulated in an `Arc`, and must be safe to share
+/// across threads.
+unsafe impl<T: Sync + Send> FfiConverter for std::sync::Arc<T> {
+ type RustType = Self;
+ // Don't use a pointer to <T> as that requires a `pub <T>`
+ type FfiType = *const std::os::raw::c_void;
+
+ /// When lowering, we have an owned `Arc<T>` and we transfer that ownership
+ /// to the foreign-language code, "leaking" it out of Rust's ownership system
+ /// as a raw pointer. This works safely because we have unique ownership of `self`.
+ /// The foreign-language code is responsible for freeing this by calling the
+ /// `ffi_object_free` FFI function provided by the corresponding UniFFI type.
+ ///
+ /// Safety: when freeing the resulting pointer, the foreign-language code must
+ /// call the destructor function specific to the type `T`. Calling the destructor
+ /// function for other types may lead to undefined behaviour.
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ std::sync::Arc::into_raw(obj) as Self::FfiType
+ }
+
+ /// When lifting, we receive a "borrow" of the `Arc<T>` that is owned by
+ /// the foreign-language code, and make a clone of it for our own use.
+ ///
+ /// Safety: the provided value must be a pointer previously obtained by calling
+ /// the `lower()` or `write()` method of this impl.
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType> {
+ let v = v as *const T;
+ // We musn't drop the `Arc<T>` that is owned by the foreign-language code.
+ let foreign_arc = std::mem::ManuallyDrop::new(unsafe { Self::from_raw(v) });
+ // Take a clone for our own use.
+ Ok(std::sync::Arc::clone(&*foreign_arc))
+ }
+
+ /// When writing as a field of a complex structure, make a clone and transfer ownership
+ /// of it to the foreign-language code by writing its pointer into the buffer.
+ /// The foreign-language code is responsible for freeing this by calling the
+ /// `ffi_object_free` FFI function provided by the corresponding UniFFI type.
+ ///
+ /// Safety: when freeing the resulting pointer, the foreign-language code must
+ /// call the destructor function specific to the type `T`. Calling the destructor
+ /// function for other types may lead to undefined behaviour.
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ static_assertions::const_assert!(std::mem::size_of::<*const std::ffi::c_void>() <= 8);
+ buf.put_u64(Self::lower(obj) as u64);
+ }
+
+ /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc<T>`
+ /// that is owned by the foreign-language code, and make a clone for our own use.
+ ///
+ /// Safety: the buffer must contain a pointer previously obtained by calling
+ /// the `lower()` or `write()` method of this impl.
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ static_assertions::const_assert!(std::mem::size_of::<*const std::ffi::c_void>() <= 8);
+ check_remaining(buf, 8)?;
+ Self::try_lift(buf.get_u64() as Self::FfiType)
+ }
+}
+
+pub fn lower_anyhow_error_or_panic<ErrConverter>(
+ err: anyhow::Error,
+ arg_name: &str,
+) -> ErrConverter::FfiType
+where
+ ErrConverter: FfiConverter,
+ ErrConverter::RustType: 'static + Sync + Send + std::fmt::Debug + std::fmt::Display,
+{
+ match err.downcast::<ErrConverter::RustType>() {
+ Ok(actual_error) => ErrConverter::lower(actual_error),
+ Err(ohno) => panic!("Failed to convert arg '{arg_name}': {ohno}"),
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn trybuild_ui_tests() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/ui/*.rs");
+ }
+
+ #[test]
+ fn timestamp_roundtrip_post_epoch() {
+ let expected = SystemTime::UNIX_EPOCH + Duration::new(100, 100);
+ let result = SystemTime::try_lift(SystemTime::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::try_lift(SystemTime::lower(expected)).expect("Failed to lift!");
+ assert_eq!(
+ expected, result,
+ "Expected results after lowering and lifting to be equal"
+ )
+ }
+}
diff --git a/third_party/rust/uniffi/src/panichook.rs b/third_party/rust/uniffi/src/panichook.rs
new file mode 100644
index 0000000000..ef0ab86f1f
--- /dev/null
+++ b/third_party/rust/uniffi/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() {}
diff --git a/third_party/rust/uniffi/src/testing.rs b/third_party/rust/uniffi/src/testing.rs
new file mode 100644
index 0000000000..ada10645ea
--- /dev/null
+++ b/third_party/rust/uniffi/src/testing.rs
@@ -0,0 +1,149 @@
+/* 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 functionality for testing generated bindings.
+//!
+//! This module helps you run a foreign language script as a testcase to exercise the
+//! bindings generated from your rust code. You probably don't want to use it directly,
+//! and should instead use the `build_foreign_language_testcases!` macro provided by
+//! the `uniffi_macros` crate.
+
+use anyhow::{bail, Result};
+use camino::{Utf8Path, Utf8PathBuf};
+use cargo_metadata::Message;
+use once_cell::sync::Lazy;
+use std::{
+ collections::HashMap,
+ process::{Command, Stdio},
+ sync::Mutex,
+};
+
+// These statics are used for a bit of simple caching and concurrency control.
+// They map uniffi component crate directories to data about build steps that have already
+// been executed by this process.
+static COMPILED_COMPONENTS: Lazy<Mutex<HashMap<Utf8PathBuf, Utf8PathBuf>>> =
+ Lazy::new(|| Mutex::new(HashMap::new()));
+// Since uniffi-bindgen does the actual generating/compiling of bindings and script files,
+// we ensure that only one call happens at once (making tests pretty much serialized sorry :/).
+static UNIFFI_BINDGEN: Lazy<Mutex<i32>> = Lazy::new(|| Mutex::new(0));
+
+/// Execute the given foreign-language script as part of a rust test suite.
+///
+/// This function takes the top-level directory of a uniffi component crate, and the path to
+/// a foreign-language test file that exercises that component's bindings. It ensures that the
+/// component is compiled and available for use and then executes the foreign language script,
+/// returning successfully iff the script exits successfully.
+pub fn run_foreign_language_testcase(
+ pkg_dir: &str,
+ udl_files: &[&str],
+ test_file: &str,
+) -> Result<()> {
+ let cdylib_file = ensure_compiled_cdylib(pkg_dir)?;
+ let _lock = UNIFFI_BINDGEN.lock();
+ run_uniffi_bindgen_test(cdylib_file.as_str(), udl_files, test_file)?;
+ Ok(())
+}
+
+/// Ensure that a uniffi component crate is compiled and ready for use.
+///
+/// This function takes the top-level directory of a uniffi component crate, ensures that the
+/// component's cdylib is compiled and available for use in generating bindings and running
+/// foreign language code.
+///
+/// Internally, this function does a bit of caching and concurrency management to avoid rebuilding
+/// the component for multiple testcases.
+pub fn ensure_compiled_cdylib(pkg_dir: &str) -> Result<Utf8PathBuf> {
+ let pkg_dir = Utf8Path::new(pkg_dir);
+
+ // Have we already compiled this component?
+ let mut compiled_components = COMPILED_COMPONENTS.lock().unwrap();
+ if let Some(cdylib_file) = compiled_components.get(pkg_dir) {
+ return Ok(cdylib_file.to_owned());
+ }
+ // Nope, looks like we'll have to compile it afresh.
+ let mut cmd = Command::new("cargo");
+ cmd.arg("build").arg("--message-format=json").arg("--lib");
+ cmd.current_dir(pkg_dir);
+ cmd.stdout(Stdio::piped());
+ let mut child = cmd.spawn()?;
+ let output = std::io::BufReader::new(child.stdout.take().unwrap());
+ // Build the crate, looking for any cdylibs that it might produce.
+ let cdylibs = Message::parse_stream(output)
+ .filter_map(|message| match message {
+ Err(e) => Some(Err(e.into())),
+ Ok(Message::CompilerArtifact(artifact)) => {
+ if artifact.target.kind.iter().any(|item| item == "cdylib") {
+ Some(Ok(artifact))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ })
+ .collect::<Result<Vec<_>>>()?;
+ if !child.wait()?.success() {
+ bail!("Failed to execute `cargo build`");
+ }
+ // If we didn't just build exactly one cdylib, we're going to use the one most likely to be produced by `pkg_dir`,
+ // or we will have a bad time.
+ let cdylib = match cdylibs.len() {
+ 0 => bail!("Crate did not produce any cdylibs, it must not be a uniffi component"),
+ 1 => &cdylibs[0],
+ _ => {
+ match cdylibs
+ .iter()
+ .find(|cdylib| cdylib.target.src_path.starts_with(pkg_dir))
+ {
+ Some(cdylib) => {
+ log::warn!(
+ "Crate produced multiple cdylibs, using the one produced by {pkg_dir}",
+ );
+ cdylib
+ }
+ None => {
+ bail!(
+ "Crate produced multiple cdylibs, none of which is produced by {pkg_dir}",
+ );
+ }
+ }
+ }
+ };
+ let cdylib_files: Vec<_> = cdylib
+ .filenames
+ .iter()
+ .filter(|nm| matches!(nm.extension(), Some(std::env::consts::DLL_EXTENSION)))
+ .collect();
+ if cdylib_files.len() != 1 {
+ bail!("Failed to build exactly one cdylib file, it must not be a uniffi component");
+ }
+ let cdylib_file = cdylib_files[0];
+ // Cache the result for subsequent tests.
+ compiled_components.insert(pkg_dir.to_owned(), cdylib_file.to_owned());
+ Ok(cdylib_file.to_owned())
+}
+
+/// Execute the `uniffi-bindgen test` command.
+///
+/// The default behaviour, suitable for most consumers, is to shell out to the `uniffi-bindgen`
+/// command found on the system.
+///
+/// If the "builtin-bindgen" feature is enabled then this will instead take a direct dependency
+/// on the `uniffi_bindgen` crate and execute its methods in-process. This is useful for folks
+/// who are working on uniffi itself and want to test out their changes to the bindings generator.
+#[cfg(not(feature = "builtin-bindgen"))]
+fn run_uniffi_bindgen_test(cdylib_file: &str, udl_files: &[&str], test_file: &str) -> Result<()> {
+ let udl_files = udl_files.join("\n");
+ let status = Command::new("uniffi-bindgen")
+ .args(&["test", cdylib_file, &udl_files, test_file])
+ .status()?;
+ if !status.success() {
+ bail!("Error while running tests: {status}");
+ }
+ Ok(())
+}
+
+#[cfg(feature = "builtin-bindgen")]
+fn run_uniffi_bindgen_test(cdylib_file: &str, udl_files: &[&str], test_file: &str) -> Result<()> {
+ uniffi_bindgen::run_tests(cdylib_file, udl_files, &[test_file], None)
+}