diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/uniffi | |
parent | Initial commit. (diff) | |
download | firefox-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')
-rw-r--r-- | third_party/rust/uniffi/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/uniffi/Cargo.toml | 63 | ||||
-rw-r--r-- | third_party/rust/uniffi/release.toml | 12 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/ffi/ffidefault.rs | 52 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/ffi/foreignbytes.rs | 118 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/ffi/foreigncallbacks.rs | 229 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/ffi/mod.rs | 15 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/ffi/rustbuffer.rs | 353 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/ffi/rustcalls.rs | 279 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/lib.rs | 670 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/panichook.rs | 34 | ||||
-rw-r--r-- | third_party/rust/uniffi/src/testing.rs | 149 | ||||
-rw-r--r-- | third_party/rust/uniffi/tests/ui/proc_macro_arc.rs | 25 | ||||
-rw-r--r-- | third_party/rust/uniffi/tests/ui/proc_macro_arc.stderr | 22 | ||||
-rw-r--r-- | third_party/rust/uniffi/tests/ui/version_mismatch.rs | 4 | ||||
-rw-r--r-- | third_party/rust/uniffi/tests/ui/version_mismatch.stderr | 7 |
16 files changed, 2033 insertions, 0 deletions
diff --git a/third_party/rust/uniffi/.cargo-checksum.json b/third_party/rust/uniffi/.cargo-checksum.json new file mode 100644 index 0000000000..5e72691a13 --- /dev/null +++ b/third_party/rust/uniffi/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"40a5376941ee93f7d3d013862731eaced1121c852bd4df4affe1cd65bf9b68be","release.toml":"a6602545cd6eb46e44d89ce946d7954957ac00f1c955de54c736fa2cb560b1df","src/ffi/ffidefault.rs":"c7ab752fffed17c3fabb60e818ad1d093482f95dd0bdeae6871287695c583e48","src/ffi/foreignbytes.rs":"37061e2da7135576abccb86fe27b4fefc054586a040f2ca81fe9858d5649e887","src/ffi/foreigncallbacks.rs":"c0974920313ac81dd4bb28a51dd9e9ef50c8ea6d965f0bfdde58c661a4ca719f","src/ffi/mod.rs":"3fb3b74607066e0052fc91168e9473dbf82dbae562f85c33774a7f5f6b350616","src/ffi/rustbuffer.rs":"b773637d9e4651b80cd16f7a02ed75846d02ce0a9e32b718ce644cdba3a83cdd","src/ffi/rustcalls.rs":"4a85a90b0d46974ab9e80e403a1cddaa252337b227e1a672bb6fdbbca5a66259","src/lib.rs":"83614739f8c0c939b217755cfde169a85608a52ea197f63f7850e5765e2c5f97","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851","src/testing.rs":"f287d682a8f27465838b2aba91993c635a4dcd281d802dc12c7c75794324123f","tests/ui/proc_macro_arc.rs":"d766dffee3fe6a93522d40f44a7f15592db141fd674034fa5f016e06f510e87b","tests/ui/proc_macro_arc.stderr":"9e7d098abdd47f249ff62fe0a2ee0f8b96282ec5a3be0c48a778baab4624180f","tests/ui/version_mismatch.rs":"16ea359e5853517ee0d0704c015ae8c825533109fbefd715130d0f4a51f15898","tests/ui/version_mismatch.stderr":"aadbd8f3847f5663022d8dd75d6afa3b25dfc8abccd30b386a681f98587d4ceb"},"package":"b983553c0d1ad73547c65fa0c399aa800bee4a70ad330198e1c7a523212da5ee"}
\ No newline at end of file diff --git a/third_party/rust/uniffi/Cargo.toml b/third_party/rust/uniffi/Cargo.toml new file mode 100644 index 0000000000..2bbc886423 --- /dev/null +++ b/third_party/rust/uniffi/Cargo.toml @@ -0,0 +1,63 @@ +# 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" +version = "0.21.1" +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.bytes] +version = "1.0" + +[dependencies.camino] +version = "1.0.8" + +[dependencies.cargo_metadata] +version = "0.14" + +[dependencies.log] +version = "0.4" + +[dependencies.once_cell] +version = "1.12" + +[dependencies.paste] +version = "1.0" + +[dependencies.static_assertions] +version = "1.1.0" + +[dependencies.uniffi_bindgen] +version = "=0.21.1" +optional = true + +[dependencies.uniffi_macros] +version = "=0.21.1" + +[dev-dependencies.trybuild] +version = "1" + +[features] +builtin-bindgen = ["uniffi_bindgen"] +default = [] diff --git a/third_party/rust/uniffi/release.toml b/third_party/rust/uniffi/release.toml new file mode 100644 index 0000000000..8fa324bdbf --- /dev/null +++ b/third_party/rust/uniffi/release.toml @@ -0,0 +1,12 @@ +# Note that this `release.toml` exists to capture things that must only be +# done once per workspace - but all other config exists in [../release.toml](../release.toml). + +tag = true + +# This is how we manage the sections in CHANGELOG.md +pre-release-replacements = [ + {file="../CHANGELOG.md", search="\\[\\[UnreleasedVersion\\]\\]", replace="v{{version}}", exactly=2}, + {file="../CHANGELOG.md", search="\\[\\[ReleaseDate\\]\\]", replace="{{date}}", exactly=1}, + {file="../CHANGELOG.md", search="\\.\\.\\.HEAD\\)", replace="...{{tag_name}})", exactly=1}, + {file="../CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n\n## [[UnreleasedVersion]] - (_[[ReleaseDate]]_)\n\n[All changes in [[UnreleasedVersion]]](https://github.com/mozilla/uniffi-rs/compare/v{{version}}...HEAD).", exactly=1}, +] 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) +} diff --git a/third_party/rust/uniffi/tests/ui/proc_macro_arc.rs b/third_party/rust/uniffi/tests/ui/proc_macro_arc.rs new file mode 100644 index 0000000000..bdffc020e0 --- /dev/null +++ b/third_party/rust/uniffi/tests/ui/proc_macro_arc.rs @@ -0,0 +1,25 @@ +use std::sync::Arc; + +fn main() {} + +pub struct Foo; + +#[uniffi::export] +fn make_foo() -> Arc<Foo> { + Arc::new(Foo) +} + +mod child { + use std::sync::Arc; + + enum Foo {} + + #[uniffi::export] + fn take_foo(foo: Arc<Foo>) { + match &*foo {} + } +} + +mod uniffi_types { + pub use super::Foo; +} diff --git a/third_party/rust/uniffi/tests/ui/proc_macro_arc.stderr b/third_party/rust/uniffi/tests/ui/proc_macro_arc.stderr new file mode 100644 index 0000000000..8bddbc1493 --- /dev/null +++ b/third_party/rust/uniffi/tests/ui/proc_macro_arc.stderr @@ -0,0 +1,22 @@ +error[E0271]: type mismatch resolving `<Arc<child::Foo> as child::_::_::{closure#0}::TypeEq>::This == Arc<Foo>` + --> tests/ui/proc_macro_arc.rs:18:22 + | +18 | fn take_foo(foo: Arc<Foo>) { + | ^^^ type mismatch resolving `<Arc<child::Foo> as child::_::_::{closure#0}::TypeEq>::This == Arc<Foo>` + | +note: expected this to be `Arc<Foo>` + --> tests/ui/proc_macro_arc.rs:18:22 + | +18 | fn take_foo(foo: Arc<Foo>) { + | ^^^ + = note: expected struct `Arc<Foo>` + found struct `Arc<child::Foo>` +note: required by a bound in `child::_::_::{closure#0}::assert_type_eq_all` + --> tests/ui/proc_macro_arc.rs:18:22 + | +18 | fn take_foo(foo: Arc<Foo>) { + | ^^^ + | | + | required by a bound in this + | required by this bound in `child::_::_::{closure#0}::assert_type_eq_all` + = note: this error originates in the macro `::uniffi::deps::static_assertions::assert_type_eq_all` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/third_party/rust/uniffi/tests/ui/version_mismatch.rs b/third_party/rust/uniffi/tests/ui/version_mismatch.rs new file mode 100644 index 0000000000..6a7edb891d --- /dev/null +++ b/third_party/rust/uniffi/tests/ui/version_mismatch.rs @@ -0,0 +1,4 @@ +// This should fail with a version mismatch. +uniffi::assert_compatible_version!("0.0.1"); // An error message would go here. + +fn main() {} diff --git a/third_party/rust/uniffi/tests/ui/version_mismatch.stderr b/third_party/rust/uniffi/tests/ui/version_mismatch.stderr new file mode 100644 index 0000000000..dde4a7a040 --- /dev/null +++ b/third_party/rust/uniffi/tests/ui/version_mismatch.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> tests/ui/version_mismatch.rs:2:1 + | +2 | uniffi::assert_compatible_version!("0.0.1"); // An error message would go here. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `0_usize - 1_usize`, which would overflow + | + = note: this error originates in the macro `uniffi::deps::static_assertions::const_assert` (in Nightly builds, run with -Z macro-backtrace for more info) |