diff options
author | Martin Geisler <mgeisler@google.com> | 2024-05-06 15:55:30 +0200 |
---|---|---|
committer | Martin Geisler <mgeisler@google.com> | 2024-05-07 00:00:11 +0200 |
commit | 17844644b496d41d717d7286efe73683b61c624f (patch) | |
tree | 21c9c4677607f438f84484520f4d2883f74841c6 | |
parent | 69c166aea2541b6fdf8e185eff3dc78e0be6e0ce (diff) | |
download | uniffi_core-17844644b496d41d717d7286efe73683b61c624f.tar.gz |
Upgrade uniffi_core to 0.27.1
This project was upgraded with external_updater.
Usage: tools/external_updater/updater.sh update external/rust/crates/uniffi_core
For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
Bug: 330712502
Test: TreeHugger
Change-Id: I9f63574e51f2be62a1eb952b3eb84681463b2f6b
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | Android.bp | 4 | ||||
-rw-r--r-- | Cargo.lock | 191 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | METADATA | 8 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | src/ffi/callbackinterface.rs | 126 | ||||
-rw-r--r-- | src/ffi/ffidefault.rs | 13 | ||||
-rw-r--r-- | src/ffi/foreigncallbacks.rs | 93 | ||||
-rw-r--r-- | src/ffi/foreignfuture.rs | 241 | ||||
-rw-r--r-- | src/ffi/handle.rs | 46 | ||||
-rw-r--r-- | src/ffi/mod.rs | 4 | ||||
-rw-r--r-- | src/ffi/rustbuffer.rs | 48 | ||||
-rw-r--r-- | src/ffi/rustcalls.rs | 7 | ||||
-rw-r--r-- | src/ffi/rustfuture/future.rs | 10 | ||||
-rw-r--r-- | src/ffi/rustfuture/mod.rs | 91 | ||||
-rw-r--r-- | src/ffi/rustfuture/scheduler.rs | 4 | ||||
-rw-r--r-- | src/ffi/rustfuture/tests.rs | 6 | ||||
-rw-r--r-- | src/ffi_converter_impls.rs | 16 | ||||
-rw-r--r-- | src/ffi_converter_traits.rs | 151 | ||||
-rw-r--r-- | src/lib.rs | 6 | ||||
-rw-r--r-- | src/metadata.rs | 12 |
22 files changed, 605 insertions, 484 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 870d019..d381183 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "d5332be35ef497255f7ce49debfd917f6a1009c7" + "sha1": "0ecafdc06799205caf1432b93787a9c1f810a168" }, "path_in_vcs": "uniffi_core" }
\ No newline at end of file @@ -20,7 +20,7 @@ rust_library { host_supported: true, crate_name: "uniffi_core", cargo_env_compat: true, - cargo_pkg_version: "0.26.1", + cargo_pkg_version: "0.27.1", srcs: ["src/lib.rs"], edition: "2021", features: ["default"], @@ -48,7 +48,7 @@ rust_test { host_supported: true, crate_name: "uniffi_core", cargo_env_compat: true, - cargo_pkg_version: "0.26.1", + cargo_pkg_version: "0.27.1", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index c52aea6..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,191 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "anyhow" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" - -[[package]] -name = "async-compat" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68a707c1feb095d8c07f8a65b9f506b117d30af431cab89374357de7c11461b" -dependencies = [ - "futures-core", - "futures-io", - "once_cell", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "backtrace" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "bytes" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" - -[[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" - -[[package]] -name = "cc" -version = "1.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "memchr" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" - -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "oneshot-uniffi" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c548d5c78976f6955d72d0ced18c48ca07030f7a1d4024529fedd7c1c01b29c" - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "tokio" -version = "1.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" -dependencies = [ - "backtrace", - "pin-project-lite", -] - -[[package]] -name = "uniffi_core" -version = "0.26.1" -dependencies = [ - "anyhow", - "async-compat", - "bytes", - "camino", - "log", - "once_cell", - "oneshot-uniffi", - "paste", - "static_assertions", -] @@ -12,7 +12,7 @@ [package] edition = "2021" name = "uniffi_core" -version = "0.26.1" +version = "0.27.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" @@ -45,7 +45,7 @@ version = "0.4" version = "1.10.0" [dependencies.oneshot] -version = "0.1.5" +version = "0.1.6" features = ["async"] package = "oneshot-uniffi" @@ -7,14 +7,14 @@ third_party { } identifier { type: "Archive" - value: "https://static.crates.io/crates/uniffi_core/uniffi_core-0.26.1.crate" + value: "https://static.crates.io/crates/uniffi_core/uniffi_core-0.27.1.crate" primary_source: true } - version: "0.26.1" + version: "0.27.1" license_type: RECIPROCAL last_upgrade_date { year: 2024 - month: 4 - day: 10 + month: 5 + day: 6 } } @@ -14,12 +14,12 @@ you can use UniFFI to help you: You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). -UniFFI is currently extensively by Mozilla in Firefox mobile and desktop browsers; +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; written once in Rust, auto-generated bindings allow that functionality to be called from both Kotlin (for Android apps) and Swift (for iOS apps). It also has a growing community of users shipping various cool things to many users. -UniFII comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. Additional foreign language bindings can be developed externally and we welcome contributions to list them here. See [Third-party foreign language bindings](#third-party-foreign-language-bindings). @@ -62,6 +62,8 @@ There are a few third-party resources that make it easier to work with UniFFI: * [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). * [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. (Please open a PR if you think other resources should be listed!) diff --git a/src/ffi/callbackinterface.rs b/src/ffi/callbackinterface.rs index 41c85dc..e7a4faa 100644 --- a/src/ffi/callbackinterface.rs +++ b/src/ffi/callbackinterface.rs @@ -91,122 +91,20 @@ //! //! 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. +//! For each callback interface, UniFFI defines a VTable. +//! This is a `repr(C)` struct where each field is a `repr(C)` callback function pointer. +//! There is one field for each method, plus an extra field for the `uniffi_free` method. +//! The foreign code registers one VTable per callback interface with Rust. //! -//! 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. +//! VTable methods have a similar signature to Rust scaffolding functions. +//! The one difference is that values are returned via an out pointer to work around a Python bug (https://bugs.python.org/issue5710). //! +//! The foreign object that implements the interface is represented by an opaque handle. +//! UniFFI generates a struct that implements the trait by calling VTable methods, passing the handle as the first parameter. +//! When the struct is dropped, the `uniffi_free` method is called. -use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer}; use std::fmt; -/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it, -/// and it can be deleted from the handle map. -pub const IDX_CALLBACK_FREE: u32 = 0; - -/// Result of a foreign callback invocation -#[repr(i32)] -#[derive(Debug, PartialEq, Eq)] -pub enum CallbackResult { - /// Successful call. - /// The return value is serialized to `buf_ptr`. - Success = 0, - /// Expected error. - /// This is returned when a foreign method throws an exception that corresponds to the Rust Err half of a Result. - /// The error value is serialized to `buf_ptr`. - Error = 1, - /// Unexpected error. - /// An error message string is serialized to `buf_ptr`. - UnexpectedError = 2, -} - -impl TryFrom<i32> for CallbackResult { - // On errors we return the unconverted value - type Error = i32; - - fn try_from(value: i32) -> Result<Self, i32> { - match value { - 0 => Ok(Self::Success), - 1 => Ok(Self::Error), - 2 => Ok(Self::UnexpectedError), - n => Err(n), - } - } -} - -/// Struct to hold a foreign callback. -pub struct ForeignCallbackInternals { - callback_cell: ForeignCallbackCell, -} - -impl ForeignCallbackInternals { - pub const fn new() -> Self { - ForeignCallbackInternals { - callback_cell: ForeignCallbackCell::new(), - } - } - - pub fn set_callback(&self, callback: ForeignCallback) { - self.callback_cell.set(callback); - } - - /// Invoke a callback interface method on the foreign side and return the result - pub fn invoke_callback<R, UniFfiTag>(&self, handle: u64, method: u32, args: RustBuffer) -> R - where - R: LiftReturn<UniFfiTag>, - { - let mut ret_rbuf = RustBuffer::new(); - let callback = self.callback_cell.get(); - let raw_result = unsafe { - callback( - handle, - method, - args.data_pointer(), - args.len() as i32, - &mut ret_rbuf, - ) - }; - RustBuffer::destroy(args); - let result = CallbackResult::try_from(raw_result) - .unwrap_or_else(|code| panic!("Callback failed with unexpected return code: {code}")); - match result { - CallbackResult::Success => R::lift_callback_return(ret_rbuf), - CallbackResult::Error => R::lift_callback_error(ret_rbuf), - CallbackResult::UnexpectedError => { - let reason = if !ret_rbuf.is_empty() { - match <String as Lift<UniFfiTag>>::try_lift(ret_rbuf) { - Ok(s) => s, - Err(e) => { - log::error!("{{ trait_name }} Error reading ret_buf: {e}"); - String::from("[Error reading reason]") - } - } - } else { - RustBuffer::destroy(ret_rbuf); - String::from("[Unknown Reason]") - }; - R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason }) - } - } - } -} - /// Used when internal/unexpected error happened when calling a foreign callback, for example when /// a unknown exception is raised /// @@ -217,8 +115,10 @@ pub struct UnexpectedUniFFICallbackError { } impl UnexpectedUniFFICallbackError { - pub fn from_reason(reason: String) -> Self { - Self { reason } + pub fn new(reason: impl fmt::Display) -> Self { + Self { + reason: reason.to_string(), + } } } diff --git a/src/ffi/ffidefault.rs b/src/ffi/ffidefault.rs index d3ca943..a992ab7 100644 --- a/src/ffi/ffidefault.rs +++ b/src/ffi/ffidefault.rs @@ -39,6 +39,12 @@ impl FfiDefault for () { fn ffi_default() {} } +impl FfiDefault for crate::Handle { + fn ffi_default() -> Self { + Self::default() + } +} + impl FfiDefault for *const std::ffi::c_void { fn ffi_default() -> Self { std::ptr::null() @@ -51,6 +57,13 @@ impl FfiDefault for crate::RustBuffer { } } +impl FfiDefault for crate::ForeignFuture { + fn ffi_default() -> Self { + extern "C" fn free(_handle: u64) {} + crate::ForeignFuture { handle: 0, free } + } +} + impl<T> FfiDefault for Option<T> { fn ffi_default() -> Self { None diff --git a/src/ffi/foreigncallbacks.rs b/src/ffi/foreigncallbacks.rs index 68d9a0d..326ff12 100644 --- a/src/ffi/foreigncallbacks.rs +++ b/src/ffi/foreigncallbacks.rs @@ -8,73 +8,32 @@ //! code loads the exported library. For each callback type, we also define a "cell" type for //! storing the callback. -use std::sync::atomic::{AtomicUsize, Ordering}; - -use crate::RustBuffer; - -/// ForeignCallback is the Rust representation of a foreign language function. -/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface, -/// at library start up time. -/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language. -/// -/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object -/// that implements the callback interface/trait. -/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from -/// the IDL. The list is 1 indexed. Note that the list of methods is generated by UniFFI from the IDL and used in all -/// bindings, so we can rely on the method list being stable within the same run of UniFFI. -/// * `args_data` and `args_len` represents a serialized buffer of arguments to the function. The scaffolding code -/// writes the callback arguments to this buffer, in order, using `FfiConverter.write()`. The bindings code reads the -/// arguments from the buffer and passes them to the user's callback. -/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a -/// buffer to write the result into. -/// * Callbacks return one of the `CallbackResult` values -/// Note: The output buffer might still contain 0 bytes of data. -pub type ForeignCallback = unsafe extern "C" fn( - handle: u64, - method: u32, - args_data: *const u8, - args_len: i32, - buf_ptr: *mut RustBuffer, -) -> i32; - -/// Store a [ForeignCallback] pointer -pub(crate) struct ForeignCallbackCell(AtomicUsize); - -/// Macro to define foreign callback types as well as the callback cell. -macro_rules! impl_foreign_callback_cell { - ($callback_type:ident, $cell_type:ident) => { - // Overly-paranoid sanity checking to ensure that these types are - // convertible between each-other. `transmute` actually should check this for - // us too, but this helps document the invariants we rely on in this code. - // - // Note that these are guaranteed by - // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html - // and thus this is a little paranoid. - static_assertions::assert_eq_size!(usize, $callback_type); - static_assertions::assert_eq_size!(usize, Option<$callback_type>); - - impl $cell_type { - pub const fn new() -> Self { - Self(AtomicUsize::new(0)) - } - - pub fn set(&self, callback: $callback_type) { - // Store the pointer using Ordering::Relaxed. This is sufficient since callback - // should be set at startup, before there's any chance of using them. - self.0.store(callback as usize, Ordering::Relaxed); - } - - pub fn get(&self) -> $callback_type { - let ptr_value = self.0.load(Ordering::Relaxed); - unsafe { - // SAFETY: self.0 was set in `set` from our function pointer type, so - // it's safe to transmute it back here. - ::std::mem::transmute::<usize, Option<$callback_type>>(ptr_value) - .expect("Bug: callback not set. This is likely a uniffi bug.") - } - } +use std::{ + ptr::{null_mut, NonNull}, + sync::atomic::{AtomicPtr, Ordering}, +}; + +// Cell type that stores any NonNull<T> +#[doc(hidden)] +pub struct UniffiForeignPointerCell<T>(AtomicPtr<T>); + +impl<T> UniffiForeignPointerCell<T> { + pub const fn new() -> Self { + Self(AtomicPtr::new(null_mut())) + } + + pub fn set(&self, callback: NonNull<T>) { + self.0.store(callback.as_ptr(), Ordering::Relaxed); + } + + pub fn get(&self) -> &T { + unsafe { + NonNull::new(self.0.load(Ordering::Relaxed)) + .expect("Foreign pointer not set. This is likely a uniffi bug.") + .as_mut() } - }; + } } -impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell); +unsafe impl<T> Send for UniffiForeignPointerCell<T> {} +unsafe impl<T> Sync for UniffiForeignPointerCell<T> {} diff --git a/src/ffi/foreignfuture.rs b/src/ffi/foreignfuture.rs new file mode 100644 index 0000000..be6a214 --- /dev/null +++ b/src/ffi/foreignfuture.rs @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! This module defines a Rust Future that wraps an async foreign function call. +//! +//! The general idea is to create a [oneshot::Channel], hand the sender to the foreign side, and +//! await the receiver side on the Rust side. +//! +//! The foreign side should: +//! * Input a [ForeignFutureCallback] and a `u64` handle in their scaffolding function. +//! This is the sender, converted to a raw pointer, and an extern "C" function that sends the result. +//! * Return a [ForeignFuture], which represents the foreign task object corresponding to the async function. +//! * Call the [ForeignFutureCallback] when the async function completes with: +//! * The `u64` handle initially passed in +//! * The `ForeignFutureResult` for the call +//! * Wait for the [ForeignFutureHandle::free] function to be called to free the task object. +//! If this is called before the task completes, then the task will be cancelled. + +use crate::{LiftReturn, RustCallStatus, UnexpectedUniFFICallbackError}; + +/// Handle for a foreign future +pub type ForeignFutureHandle = u64; + +/// Handle for a callback data associated with a foreign future. +pub type ForeignFutureCallbackData = *mut (); + +/// Callback that's passed to a foreign async functions. +/// +/// See `LiftReturn` trait for how this is implemented. +pub type ForeignFutureCallback<FfiType> = + extern "C" fn(oneshot_handle: u64, ForeignFutureResult<FfiType>); + +/// C struct that represents the result of a foreign future +#[repr(C)] +pub struct ForeignFutureResult<T> { + // Note: for void returns, T is `()`, which isn't directly representable with C since it's a ZST. + // Foreign code should treat that case as if there was no `return_value` field. + return_value: T, + call_status: RustCallStatus, +} + +/// Perform a call to a foreign async method + +/// C struct that represents the foreign future. +/// +/// This is what's returned by the async scaffolding functions. +#[repr(C)] +pub struct ForeignFuture { + pub handle: ForeignFutureHandle, + pub free: extern "C" fn(handle: ForeignFutureHandle), +} + +impl Drop for ForeignFuture { + fn drop(&mut self) { + (self.free)(self.handle) + } +} + +unsafe impl Send for ForeignFuture {} + +pub async fn foreign_async_call<F, T, UT>(call_scaffolding_function: F) -> T +where + F: FnOnce(ForeignFutureCallback<T::ReturnType>, u64) -> ForeignFuture, + T: LiftReturn<UT>, +{ + let (sender, receiver) = oneshot::channel::<ForeignFutureResult<T::ReturnType>>(); + // Keep the ForeignFuture around, even though we don't ever use it. + // The important thing is that the ForeignFuture will be dropped when this Future is. + let _foreign_future = + call_scaffolding_function(foreign_future_complete::<T, UT>, sender.into_raw() as u64); + match receiver.await { + Ok(result) => T::lift_foreign_return(result.return_value, result.call_status), + Err(e) => { + // This shouldn't happen in practice, but we can do our best to recover + T::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(format!( + "Error awaiting foreign future: {e}" + ))) + } + } +} + +pub extern "C" fn foreign_future_complete<T: LiftReturn<UT>, UT>( + oneshot_handle: u64, + result: ForeignFutureResult<T::ReturnType>, +) { + let channel = unsafe { oneshot::Sender::from_raw(oneshot_handle as *mut ()) }; + // Ignore errors in send. + // + // Error means the receiver was already dropped which will happen when the future is cancelled. + let _ = channel.send(result); +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{Lower, RustBuffer}; + use once_cell::sync::OnceCell; + use std::{ + future::Future, + pin::Pin, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, + }, + task::{Context, Poll, Wake}, + }; + + struct MockForeignFuture { + freed: Arc<AtomicU32>, + callback_info: Arc<OnceCell<(ForeignFutureCallback<RustBuffer>, u64)>>, + rust_future: Option<Pin<Box<dyn Future<Output = String>>>>, + } + + impl MockForeignFuture { + fn new() -> Self { + let callback_info = Arc::new(OnceCell::new()); + let freed = Arc::new(AtomicU32::new(0)); + + let rust_future: Pin<Box<dyn Future<Output = String>>> = { + let callback_info = callback_info.clone(); + let freed = freed.clone(); + Box::pin(foreign_async_call::<_, String, crate::UniFfiTag>( + move |callback, data| { + callback_info.set((callback, data)).unwrap(); + ForeignFuture { + handle: Arc::into_raw(freed) as *mut () as u64, + free: Self::free, + } + }, + )) + }; + let rust_future = Some(rust_future); + let mut mock_foreign_future = Self { + freed, + callback_info, + rust_future, + }; + // Poll the future once, to start it up. This ensures that `callback_info` is set. + let _ = mock_foreign_future.poll(); + mock_foreign_future + } + + fn poll(&mut self) -> Poll<String> { + let waker = Arc::new(NoopWaker).into(); + let mut context = Context::from_waker(&waker); + self.rust_future + .as_mut() + .unwrap() + .as_mut() + .poll(&mut context) + } + + fn complete_success(&self, value: String) { + let (callback, data) = self.callback_info.get().unwrap(); + callback( + *data, + ForeignFutureResult { + return_value: <String as Lower<crate::UniFfiTag>>::lower(value), + call_status: RustCallStatus::new(), + }, + ); + } + + fn complete_error(&self, error_message: String) { + let (callback, data) = self.callback_info.get().unwrap(); + callback( + *data, + ForeignFutureResult { + return_value: RustBuffer::default(), + call_status: RustCallStatus::error(error_message), + }, + ); + } + + fn drop_future(&mut self) { + self.rust_future = None + } + + fn free_count(&self) -> u32 { + self.freed.load(Ordering::Relaxed) + } + + extern "C" fn free(handle: u64) { + let flag = unsafe { Arc::from_raw(handle as *mut AtomicU32) }; + flag.fetch_add(1, Ordering::Relaxed); + } + } + + struct NoopWaker; + + impl Wake for NoopWaker { + fn wake(self: Arc<Self>) {} + } + + #[test] + fn test_foreign_future() { + let mut mock_foreign_future = MockForeignFuture::new(); + assert_eq!(mock_foreign_future.poll(), Poll::Pending); + mock_foreign_future.complete_success("It worked!".to_owned()); + assert_eq!( + mock_foreign_future.poll(), + Poll::Ready("It worked!".to_owned()) + ); + // Since the future is complete, it should free the foreign future + assert_eq!(mock_foreign_future.free_count(), 1); + } + + #[test] + #[should_panic] + fn test_foreign_future_error() { + let mut mock_foreign_future = MockForeignFuture::new(); + assert_eq!(mock_foreign_future.poll(), Poll::Pending); + mock_foreign_future.complete_error("It Failed!".to_owned()); + let _ = mock_foreign_future.poll(); + } + + #[test] + fn test_drop_after_complete() { + let mut mock_foreign_future = MockForeignFuture::new(); + mock_foreign_future.complete_success("It worked!".to_owned()); + assert_eq!(mock_foreign_future.free_count(), 0); + assert_eq!( + mock_foreign_future.poll(), + Poll::Ready("It worked!".to_owned()) + ); + // Dropping the future after it's complete should not panic, and not cause a double-free + mock_foreign_future.drop_future(); + assert_eq!(mock_foreign_future.free_count(), 1); + } + + #[test] + fn test_drop_before_complete() { + let mut mock_foreign_future = MockForeignFuture::new(); + mock_foreign_future.complete_success("It worked!".to_owned()); + // Dropping the future before it's complete should cancel the future + assert_eq!(mock_foreign_future.free_count(), 0); + mock_foreign_future.drop_future(); + assert_eq!(mock_foreign_future.free_count(), 1); + } +} diff --git a/src/ffi/handle.rs b/src/ffi/handle.rs new file mode 100644 index 0000000..8ee2f46 --- /dev/null +++ b/src/ffi/handle.rs @@ -0,0 +1,46 @@ +/* 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/. */ + +/// Object handle +/// +/// Handles opaque `u64` values used to pass objects across the FFI, both for objects implemented in +/// Rust and ones implemented in the foreign language. +/// +/// Rust handles are generated by leaking a raw pointer +/// Foreign handles are generated with a handle map that only generates odd values. +/// For all currently supported architectures and hopefully any ones we add in the future: +/// * 0 is an invalid value. +/// * The lowest bit will always be set for foreign handles and never set for Rust ones (since the +/// leaked pointer will be aligned). +/// +/// Rust handles are mainly managed is through the [crate::HandleAlloc] trait. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Handle(u64); + +impl Handle { + pub fn from_pointer<T>(ptr: *const T) -> Self { + Self(ptr as u64) + } + + pub fn as_pointer<T>(&self) -> *const T { + self.0 as *const T + } + + pub fn from_raw(raw: u64) -> Option<Self> { + if raw == 0 { + None + } else { + Some(Self(raw)) + } + } + + pub fn from_raw_unchecked(raw: u64) -> Self { + Self(raw) + } + + pub fn as_raw(&self) -> u64 { + self.0 + } +} diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 24b9bba..acaf2b0 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -8,6 +8,8 @@ pub mod callbackinterface; pub mod ffidefault; pub mod foreignbytes; pub mod foreigncallbacks; +pub mod foreignfuture; +pub mod handle; pub mod rustbuffer; pub mod rustcalls; pub mod rustfuture; @@ -16,6 +18,8 @@ pub use callbackinterface::*; pub use ffidefault::FfiDefault; pub use foreignbytes::*; pub use foreigncallbacks::*; +pub use foreignfuture::*; +pub use handle::*; pub use rustbuffer::*; pub use rustcalls::*; pub use rustfuture::*; diff --git a/src/ffi/rustbuffer.rs b/src/ffi/rustbuffer.rs index cfcc542..8b29729 100644 --- a/src/ffi/rustbuffer.rs +++ b/src/ffi/rustbuffer.rs @@ -52,11 +52,11 @@ use crate::ffi::{rust_call, ForeignBytes, RustCallStatus}; #[derive(Debug)] pub struct RustBuffer { /// The allocated capacity of the underlying `Vec<u8>`. - /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. - capacity: i32, + /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple. + capacity: u64, /// 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, + /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple. + len: u64, /// The pointer to the allocated buffer of the `Vec<u8>`. data: *mut u8, } @@ -84,7 +84,7 @@ impl RustBuffer { /// # 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 { + pub unsafe fn from_raw_parts(data: *mut u8, len: u64, capacity: u64) -> Self { Self { capacity, len, @@ -126,12 +126,8 @@ impl RustBuffer { /// /// 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]) + pub fn new_with_size(size: u64) -> Self { + Self::from_vec(vec![0u8; size as usize]) } /// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`. @@ -144,8 +140,8 @@ impl RustBuffer { /// 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 capacity = u64::try_from(v.capacity()).expect("buffer capacity cannot fit into a u64."); + let len = u64::try_from(v.len()).expect("buffer length cannot fit into a u64."); let mut v = std::mem::ManuallyDrop::new(v); unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) } } @@ -208,10 +204,8 @@ impl Default for RustBuffer { /// 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. -pub fn uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer { - rust_call(call_status, || { - Ok(RustBuffer::new_with_size(size.max(0) as usize)) - }) +pub fn uniffi_rustbuffer_alloc(size: u64, call_status: &mut RustCallStatus) -> RustBuffer { + rust_call(call_status, || Ok(RustBuffer::new_with_size(size))) } /// This helper copies bytes owned by the foreign-language code into a new byte buffer owned @@ -262,7 +256,7 @@ pub fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) /// corrupting the allocator state. pub fn uniffi_rustbuffer_reserve( buf: RustBuffer, - additional: i32, + additional: u64, call_status: &mut RustCallStatus, ) -> RustBuffer { rust_call(call_status, || { @@ -328,24 +322,6 @@ mod test { #[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]; diff --git a/src/ffi/rustcalls.rs b/src/ffi/rustcalls.rs index 51204ef..16b0c76 100644 --- a/src/ffi/rustcalls.rs +++ b/src/ffi/rustcalls.rs @@ -56,6 +56,13 @@ pub struct RustCallStatus { } impl RustCallStatus { + pub fn new() -> Self { + Self { + code: RustCallStatusCode::Success, + error_buf: MaybeUninit::new(RustBuffer::new()), + } + } + pub fn cancelled() -> Self { Self { code: RustCallStatusCode::Cancelled, diff --git a/src/ffi/rustfuture/future.rs b/src/ffi/rustfuture/future.rs index b104b20..93c34e7 100644 --- a/src/ffi/rustfuture/future.rs +++ b/src/ffi/rustfuture/future.rs @@ -12,7 +12,7 @@ //! //! 0. At startup, register a [RustFutureContinuationCallback] by calling //! rust_future_continuation_callback_set. -//! 1. Call the scaffolding function to get a [RustFutureHandle] +//! 1. Call the scaffolding function to get a [Handle] //! 2a. In a loop: //! - Call [rust_future_poll] //! - Suspend the function until the [rust_future_poll] continuation function is called @@ -223,7 +223,7 @@ where }) } - pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { + pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) { let ready = self.is_cancelled() || { let mut locked = self.future.lock().unwrap(); let waker: std::task::Waker = Arc::clone(&self).into(); @@ -288,8 +288,8 @@ where /// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and /// only create those functions for each of the 13 possible FFI return types. #[doc(hidden)] -pub trait RustFutureFfi<ReturnType> { - fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()); +pub trait RustFutureFfi<ReturnType>: Send + Sync { + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64); fn ffi_cancel(&self); fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; fn ffi_free(self: Arc<Self>); @@ -302,7 +302,7 @@ where T: LowerReturn<UT> + Send + 'static, UT: Send + 'static, { - fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) { self.poll(callback, data) } diff --git a/src/ffi/rustfuture/mod.rs b/src/ffi/rustfuture/mod.rs index 4aaf013..3d3505e 100644 --- a/src/ffi/rustfuture/mod.rs +++ b/src/ffi/rustfuture/mod.rs @@ -12,7 +12,7 @@ use scheduler::*; #[cfg(test)] mod tests; -use crate::{LowerReturn, RustCallStatus}; +use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus}; /// Result code for [rust_future_poll]. This is passed to the continuation function. #[repr(i8)] @@ -28,19 +28,15 @@ pub enum RustFuturePoll { /// /// The Rust side of things calls this when the foreign side should call [rust_future_poll] again /// to continue progress on the future. -pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll); - -/// Opaque handle for a Rust future that's stored by the foreign language code -#[repr(transparent)] -pub struct RustFutureHandle(*const ()); +pub type RustFutureContinuationCallback = extern "C" fn(callback_data: u64, RustFuturePoll); // === Public FFI API === -/// Create a new [RustFutureHandle] +/// Create a new [Handle] for a Rust future /// /// For each exported async function, UniFFI will create a scaffolding function that uses this to -/// create the [RustFutureHandle] to pass to the foreign code. -pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> RustFutureHandle +/// create the [Handle] to pass to the foreign code. +pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> Handle where // F is the future type returned by the exported async function. It needs to be Send + `static // since it will move between threads for an indeterminate amount of time as the foreign @@ -52,15 +48,12 @@ where T: LowerReturn<UT> + Send + 'static, // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. UT: Send + 'static, + // Needed to allocate a handle + dyn RustFutureFfi<T::ReturnType>: HandleAlloc<UT>, { - // Create a RustFuture and coerce to `Arc<dyn RustFutureFfi>`, which is what we use to - // implement the FFI - let future_ffi = RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>; - // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it - // to the foreign code. - let boxed_ffi = Box::new(future_ffi); - // We can now create a RustFutureHandle - RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ()) + <dyn RustFutureFfi<T::ReturnType> as HandleAlloc<UT>>::new_handle( + RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>> + ) } /// Poll a Rust future @@ -71,14 +64,15 @@ where /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_poll<ReturnType>( - handle: RustFutureHandle, +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll<ReturnType, UT>( + handle: Handle, callback: RustFutureContinuationCallback, - data: *const (), -) { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.clone().ffi_poll(callback, data) + data: u64, +) where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_poll(callback, data) } /// Cancel a Rust future @@ -90,10 +84,12 @@ pub unsafe fn rust_future_poll<ReturnType>( /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.clone().ffi_cancel() +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel<ReturnType, UT>(handle: Handle) +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_cancel() } /// Complete a Rust future @@ -103,15 +99,17 @@ pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) { /// /// # Safety /// -/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free] +/// - The [Handle] must not previously have been passed to [rust_future_free] /// - The `T` param must correctly correspond to the [rust_future_new] call. It must /// be `<Output as LowerReturn<UT>>::ReturnType` -pub unsafe fn rust_future_complete<ReturnType>( - handle: RustFutureHandle, +pub unsafe fn rust_future_complete<ReturnType, UT>( + handle: Handle, out_status: &mut RustCallStatus, -) -> ReturnType { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.ffi_complete(out_status) +) -> ReturnType +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_complete(out_status) } /// Free a Rust future, dropping the strong reference and releasing all references held by the @@ -119,8 +117,25 @@ pub unsafe fn rust_future_complete<ReturnType>( /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_free<ReturnType>(handle: RustFutureHandle) { - let future = Box::from_raw(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.ffi_free() +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_free<ReturnType, UT>(handle: Handle) +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::consume_handle(handle).ffi_free() } + +// Derive HandleAlloc for dyn RustFutureFfi<T> for all FFI return types +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u8>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i8>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u16>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i16>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<*const std::ffi::c_void>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<crate::RustBuffer>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<()>); diff --git a/src/ffi/rustfuture/scheduler.rs b/src/ffi/rustfuture/scheduler.rs index aae5a0c..629ee0c 100644 --- a/src/ffi/rustfuture/scheduler.rs +++ b/src/ffi/rustfuture/scheduler.rs @@ -30,7 +30,7 @@ pub(super) enum Scheduler { /// continuation being called with `RustFuturePoll::Ready`. Cancelled, /// Continuation set, the next time `wake()` is called is called, we should invoke it. - Set(RustFutureContinuationCallback, *const ()), + Set(RustFutureContinuationCallback, u64), } impl Scheduler { @@ -40,7 +40,7 @@ impl Scheduler { /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or /// `Cancelled` state, call the continuation immediately with the data. - pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) { + pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: u64) { match self { Self::Empty => *self = Self::Set(callback, data), Self::Set(old_callback, old_data) => { diff --git a/src/ffi/rustfuture/tests.rs b/src/ffi/rustfuture/tests.rs index 1f68085..886ee27 100644 --- a/src/ffi/rustfuture/tests.rs +++ b/src/ffi/rustfuture/tests.rs @@ -67,12 +67,12 @@ fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) { /// Poll a Rust future and get an OnceCell that's set when the continuation is called fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> { let cell = Arc::new(OnceCell::new()); - let cell_ptr = Arc::into_raw(cell.clone()) as *const (); - rust_future.clone().ffi_poll(poll_continuation, cell_ptr); + let handle = Arc::into_raw(cell.clone()) as u64; + rust_future.clone().ffi_poll(poll_continuation, handle); cell } -extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) { +extern "C" fn poll_continuation(data: u64, code: RustFuturePoll) { let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) }; cell.set(code).expect("Error setting OnceCell"); } diff --git a/src/ffi_converter_impls.rs b/src/ffi_converter_impls.rs index 5be5f04..aec0931 100644 --- a/src/ffi_converter_impls.rs +++ b/src/ffi_converter_impls.rs @@ -456,7 +456,11 @@ unsafe impl<UT> LowerReturn<UT> for () { } unsafe impl<UT> LiftReturn<UT> for () { - fn lift_callback_return(_buf: RustBuffer) -> Self {} + type ReturnType = (); + + fn try_lift_successful_return(_: ()) -> Result<Self> { + Ok(()) + } const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); } @@ -493,13 +497,15 @@ where unsafe impl<UT, R, E> LiftReturn<UT> for Result<R, E> where R: LiftReturn<UT>, - E: Lift<UT> + ConvertError<UT>, + E: Lift<UT, FfiType = RustBuffer> + ConvertError<UT>, { - fn lift_callback_return(buf: RustBuffer) -> Self { - Ok(R::lift_callback_return(buf)) + type ReturnType = R::ReturnType; + + fn try_lift_successful_return(v: R::ReturnType) -> Result<Self> { + R::try_lift_successful_return(v).map(Ok) } - fn lift_callback_error(buf: RustBuffer) -> Self { + fn lift_error(buf: RustBuffer) -> Self { match E::try_lift_from_rust_buffer(buf) { Ok(lifted_error) => Err(lifted_error), Err(anyhow_error) => { diff --git a/src/ffi_converter_traits.rs b/src/ffi_converter_traits.rs index 3b5914e..4e7b9e0 100644 --- a/src/ffi_converter_traits.rs +++ b/src/ffi_converter_traits.rs @@ -51,7 +51,10 @@ use std::{borrow::Borrow, sync::Arc}; use anyhow::bail; use bytes::Buf; -use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError}; +use crate::{ + FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, + UnexpectedUniFFICallbackError, +}; /// Generalized FFI conversions /// @@ -302,14 +305,41 @@ pub unsafe trait LowerReturn<UT>: Sized { /// These traits should not be used directly, only in generated code, and the generated code should /// have fixture tests to test that everything works correctly together. pub unsafe trait LiftReturn<UT>: Sized { - /// Lift a Rust value for a callback interface method result - fn lift_callback_return(buf: RustBuffer) -> Self; + /// FFI return type for trait interfaces + type ReturnType; + + /// Lift a successfully returned value from a trait interface + fn try_lift_successful_return(v: Self::ReturnType) -> Result<Self>; + + /// Lift a foreign returned value from a trait interface + /// + /// When we call a foreign-implemented trait interface method, we pass a &mut RustCallStatus + /// and get [Self::ReturnType] returned. This method takes both of those and lifts `Self` from + /// it. + fn lift_foreign_return(ffi_return: Self::ReturnType, call_status: RustCallStatus) -> Self { + match call_status.code { + RustCallStatusCode::Success => Self::try_lift_successful_return(ffi_return) + .unwrap_or_else(|e| { + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) + }), + RustCallStatusCode::Error => { + Self::lift_error(unsafe { call_status.error_buf.assume_init() }) + } + _ => { + let e = <String as FfiConverter<crate::UniFfiTag>>::try_lift(unsafe { + call_status.error_buf.assume_init() + }) + .unwrap_or_else(|e| format!("(Error lifting message: {e}")); + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) + } + } + } /// Lift a Rust value for a callback interface method error result /// /// This is called for "expected errors" -- the callback method returns a Result<> type and the /// foreign code throws an exception that corresponds to the error type. - fn lift_callback_error(_buf: RustBuffer) -> Self { + fn lift_error(_buf: RustBuffer) -> Self { panic!("Callback interface method returned unexpected error") } @@ -351,6 +381,66 @@ pub trait ConvertError<UT>: Sized { fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result<Self>; } +/// Manage handles for `Arc<Self>` instances +/// +/// Handles are used to manage objects that are passed across the FFI. They general usage is: +/// +/// * Rust creates an `Arc<>` +/// * Rust uses `new_handle` to create a handle that represents the Arc reference +/// * Rust passes the handle to the foreign code as a `u64` +/// * The foreign code passes the handle back to `Rust` to refer to the object: +/// * Handle are usually passed as borrowed values. When an FFI function inputs a handle as an +/// argument, the foreign code simply passes a copy of the `u64` to Rust, which calls `get_arc` +/// to get a new `Arc<>` clone for it. +/// * Handles are returned as owned values. When an FFI function returns a handle, the foreign +/// code either stops using the handle after returning it or calls `clone_handle` and returns +/// the clone. +/// * Eventually the foreign code may destroy their handle by passing it into a "free" FFI +/// function. This functions input an owned handle and consume it. +/// +/// The foreign code also defines their own handles. These represent foreign objects that are +/// passed to Rust. Using foreign handles is essentially the same as above, but in reverse. +/// +/// Handles must always be `Send` and the objects they reference must always be `Sync`. +/// This means that it must be safe to send handles to other threads and use them there. +/// +/// Note: this only needs to be derived for unsized types, there's a blanket impl for `T: Sized`. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +/// `&T` using the Arc. +pub unsafe trait HandleAlloc<UT>: Send + Sync { + /// Create a new handle for an Arc value + /// + /// Use this to lower an Arc into a handle value before passing it across the FFI. + /// The newly-created handle will have reference count = 1. + fn new_handle(value: Arc<Self>) -> Handle; + + /// Clone a handle + /// + /// This creates a new handle from an existing one. + /// It's used when the foreign code wants to pass back an owned handle and still keep a copy + /// for themselves. + fn clone_handle(handle: Handle) -> Handle; + + /// Get a clone of the `Arc<>` using a "borrowed" handle. + /// + /// Take care that the handle can not be destroyed between when it's passed and when + /// `get_arc()` is called. #1797 is a cautionary tale. + fn get_arc(handle: Handle) -> Arc<Self> { + Self::consume_handle(Self::clone_handle(handle)) + } + + /// Consume a handle, getting back the initial `Arc<>` + fn consume_handle(handle: Handle) -> Arc<Self>; +} + /// Derive FFI traits /// /// This can be used to derive: @@ -439,9 +529,10 @@ macro_rules! derive_ffi_traits { (impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { unsafe impl $(<$($generic),*>)* $crate::LiftReturn<$ut> for $ty $(where $($where)*)* { - fn lift_callback_return(buf: $crate::RustBuffer) -> Self { - <Self as $crate::Lift<$ut>>::try_lift_from_rust_buffer(buf) - .expect("Error reading callback interface result") + type ReturnType = <Self as $crate::Lift<$ut>>::FfiType; + + fn try_lift_successful_return(v: Self::ReturnType) -> $crate::Result<Self> { + <Self as $crate::Lift<$ut>>::try_lift(v) } const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::Lift<$ut>>::TYPE_ID_META; @@ -463,4 +554,50 @@ macro_rules! derive_ffi_traits { } } }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? HandleAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + // Derived HandleAlloc implementation. + // + // This is only needed for !Sized types like `dyn Trait`, below is a blanket implementation + // for any sized type. + unsafe impl $(<$($generic),*>)* $crate::HandleAlloc<$ut> for $ty $(where $($where)*)* + { + // To implement HandleAlloc for an unsized type, wrap it with a second Arc which + // converts the wide pointer into a normal pointer. + + fn new_handle(value: ::std::sync::Arc<Self>) -> $crate::Handle { + $crate::Handle::from_pointer(::std::sync::Arc::into_raw(::std::sync::Arc::new(value))) + } + + fn clone_handle(handle: $crate::Handle) -> $crate::Handle { + unsafe { + ::std::sync::Arc::<::std::sync::Arc<Self>>::increment_strong_count(handle.as_pointer::<::std::sync::Arc<Self>>()); + } + handle + } + + fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc<Self> { + unsafe { + ::std::sync::Arc::<Self>::clone( + &std::sync::Arc::<::std::sync::Arc::<Self>>::from_raw(handle.as_pointer::<::std::sync::Arc<Self>>()) + ) + } + } + } + }; +} + +unsafe impl<T: Send + Sync, UT> HandleAlloc<UT> for T { + fn new_handle(value: Arc<Self>) -> Handle { + Handle::from_pointer(Arc::into_raw(value)) + } + + fn clone_handle(handle: Handle) -> Handle { + unsafe { Arc::increment_strong_count(handle.as_pointer::<T>()) }; + handle + } + + fn consume_handle(handle: Handle) -> Arc<Self> { + unsafe { Arc::from_raw(handle.as_pointer()) } + } } @@ -45,7 +45,8 @@ pub mod metadata; pub use ffi::*; pub use ffi_converter_traits::{ - ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, + ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower, + LowerReturn, }; pub use metadata::*; @@ -57,12 +58,13 @@ pub mod deps { pub use async_compat; pub use bytes; pub use log; + pub use oneshot; pub use static_assertions; } mod panichook; -const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION"); +const PACKAGE_VERSION: &str = "0.27.1"; // 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. diff --git a/src/metadata.rs b/src/metadata.rs index 934d09c..dc61a1b 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -71,16 +71,20 @@ pub mod codes { pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25; pub const TYPE_UNIT: u8 = 255; - // Literal codes for LiteralMetadata - note that we don't support - // all variants in the "emit/reader" context. + // Literal codes for LiteralMetadata pub const LIT_STR: u8 = 0; pub const LIT_INT: u8 = 1; pub const LIT_FLOAT: u8 = 2; pub const LIT_BOOL: u8 = 3; - pub const LIT_NULL: u8 = 4; + pub const LIT_NONE: u8 = 4; + pub const LIT_SOME: u8 = 5; + pub const LIT_EMPTY_SEQ: u8 = 6; } -const BUF_SIZE: usize = 4096; +// For large errors (e.g. enums) a buffer size of ~4k - ~8k +// is not enough. See issues on Github: #1968 and #2041 and +// for an example see fixture/large-error +const BUF_SIZE: usize = 16384; // This struct is a kludge around the fact that Rust const generic support doesn't quite handle our // needs. |