diff options
author | Adam Wright <adamdwright@google.com> | 2024-05-07 09:32:20 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-05-07 09:32:32 +0000 |
commit | 04ad7de3a2d3d5bc8848416683f214dd165a0439 (patch) | |
tree | 24ed579c417f213d9a8e49c93e4980c0d8a13e21 | |
parent | 17844644b496d41d717d7286efe73683b61c624f (diff) | |
download | uniffi_core-04ad7de3a2d3d5bc8848416683f214dd165a0439.tar.gz |
Revert "Upgrade uniffi_core to 0.27.1"
Revert submission 3076224-uniffi
Reason for revert: Droidmonitor break b/339155709
Reverted changes: /q/submissionid:3076224-uniffi
Change-Id: Id2f8178a2ab1cea0fcce6af0f9b569633e27be1f
-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, 484 insertions, 605 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index d381183..870d019 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "0ecafdc06799205caf1432b93787a9c1f810a168" + "sha1": "d5332be35ef497255f7ce49debfd917f6a1009c7" }, "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.27.1", + cargo_pkg_version: "0.26.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.27.1", + cargo_pkg_version: "0.26.1", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c52aea6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,191 @@ +# 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.27.1" +version = "0.26.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.6" +version = "0.1.5" 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.27.1.crate" + value: "https://static.crates.io/crates/uniffi_core/uniffi_core-0.26.1.crate" primary_source: true } - version: "0.27.1" + version: "0.26.1" license_type: RECIPROCAL last_upgrade_date { year: 2024 - month: 5 - day: 6 + month: 4 + day: 10 } } @@ -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 used extensively by Mozilla in Firefox mobile and desktop browsers; +UniFFI is currently 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. -UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +UniFII 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,8 +62,6 @@ 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 e7a4faa..41c85dc 100644 --- a/src/ffi/callbackinterface.rs +++ b/src/ffi/callbackinterface.rs @@ -91,20 +91,122 @@ //! //! Uniffi generates a protocol or interface in client code in the foreign language must implement. //! -//! 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. +//! 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. //! -//! 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). +//! 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. //! -//! 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 /// @@ -115,10 +217,8 @@ pub struct UnexpectedUniFFICallbackError { } impl UnexpectedUniFFICallbackError { - pub fn new(reason: impl fmt::Display) -> Self { - Self { - reason: reason.to_string(), - } + pub fn from_reason(reason: String) -> Self { + Self { reason } } } diff --git a/src/ffi/ffidefault.rs b/src/ffi/ffidefault.rs index a992ab7..d3ca943 100644 --- a/src/ffi/ffidefault.rs +++ b/src/ffi/ffidefault.rs @@ -39,12 +39,6 @@ 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() @@ -57,13 +51,6 @@ 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 326ff12..68d9a0d 100644 --- a/src/ffi/foreigncallbacks.rs +++ b/src/ffi/foreigncallbacks.rs @@ -8,32 +8,73 @@ //! code loads the exported library. For each callback type, we also define a "cell" type for //! storing the callback. -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() +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.") + } + } } - } + }; } -unsafe impl<T> Send for UniffiForeignPointerCell<T> {} -unsafe impl<T> Sync for UniffiForeignPointerCell<T> {} +impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell); diff --git a/src/ffi/foreignfuture.rs b/src/ffi/foreignfuture.rs deleted file mode 100644 index be6a214..0000000 --- a/src/ffi/foreignfuture.rs +++ /dev/null @@ -1,241 +0,0 @@ -/* 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 deleted file mode 100644 index 8ee2f46..0000000 --- a/src/ffi/handle.rs +++ /dev/null @@ -1,46 +0,0 @@ -/* 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 acaf2b0..24b9bba 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -8,8 +8,6 @@ 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; @@ -18,8 +16,6 @@ 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 8b29729..cfcc542 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 `u64` to keep the foreign binding code simple. - capacity: u64, + /// 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 `u64` to keep the foreign binding code simple. - len: u64, + /// 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, } @@ -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: u64, capacity: u64) -> Self { + pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self { Self { capacity, len, @@ -126,8 +126,12 @@ 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: u64) -> Self { - Self::from_vec(vec![0u8; size as usize]) + 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`. @@ -140,8 +144,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 = 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 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) } } @@ -204,8 +208,10 @@ 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: u64, call_status: &mut RustCallStatus) -> RustBuffer { - rust_call(call_status, || Ok(RustBuffer::new_with_size(size))) +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)) + }) } /// This helper copies bytes owned by the foreign-language code into a new byte buffer owned @@ -256,7 +262,7 @@ pub fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) /// corrupting the allocator state. pub fn uniffi_rustbuffer_reserve( buf: RustBuffer, - additional: u64, + additional: i32, call_status: &mut RustCallStatus, ) -> RustBuffer { rust_call(call_status, || { @@ -322,6 +328,24 @@ 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 16b0c76..51204ef 100644 --- a/src/ffi/rustcalls.rs +++ b/src/ffi/rustcalls.rs @@ -56,13 +56,6 @@ 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 93c34e7..b104b20 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 [Handle] +//! 1. Call the scaffolding function to get a [RustFutureHandle] //! 2a. In a loop: //! - Call [rust_future_poll] //! - Suspend the function until the [rust_future_poll] continuation function is called @@ -223,7 +223,7 @@ where }) } - pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) { + pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { let ready = self.is_cancelled() || { let mut locked = self.future.lock().unwrap(); let waker: std::task::Waker = Arc::clone(&self).into(); @@ -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>: Send + Sync { - fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64); +pub trait RustFutureFfi<ReturnType> { + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()); fn ffi_cancel(&self); fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; fn ffi_free(self: Arc<Self>); @@ -302,7 +302,7 @@ where T: LowerReturn<UT> + Send + 'static, UT: Send + 'static, { - fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) { + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { self.poll(callback, data) } diff --git a/src/ffi/rustfuture/mod.rs b/src/ffi/rustfuture/mod.rs index 3d3505e..4aaf013 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::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus}; +use crate::{LowerReturn, RustCallStatus}; /// Result code for [rust_future_poll]. This is passed to the continuation function. #[repr(i8)] @@ -28,15 +28,19 @@ 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: u64, RustFuturePoll); +pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll); + +/// Opaque handle for a Rust future that's stored by the foreign language code +#[repr(transparent)] +pub struct RustFutureHandle(*const ()); // === Public FFI API === -/// Create a new [Handle] for a Rust future +/// Create a new [RustFutureHandle] /// /// For each exported async function, UniFFI will create a scaffolding function that uses this to -/// create the [Handle] to pass to the foreign code. -pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> Handle +/// create the [RustFutureHandle] to pass to the foreign code. +pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> RustFutureHandle where // F is the future type returned by the exported async function. It needs to be Send + `static // since it will move between threads for an indeterminate amount of time as the foreign @@ -48,12 +52,15 @@ 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>, { - <dyn RustFutureFfi<T::ReturnType> as HandleAlloc<UT>>::new_handle( - RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>> - ) + // Create a RustFuture and coerce to `Arc<dyn RustFutureFfi>`, which is what we use to + // implement the FFI + let future_ffi = RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>; + // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it + // to the foreign code. + let boxed_ffi = Box::new(future_ffi); + // We can now create a RustFutureHandle + RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ()) } /// Poll a Rust future @@ -64,15 +71,14 @@ where /// /// # Safety /// -/// The [Handle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_poll<ReturnType, UT>( - handle: Handle, +/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll<ReturnType>( + handle: RustFutureHandle, callback: RustFutureContinuationCallback, - data: u64, -) where - dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, -{ - <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_poll(callback, data) + data: *const (), +) { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.clone().ffi_poll(callback, data) } /// Cancel a Rust future @@ -84,12 +90,10 @@ pub unsafe fn rust_future_poll<ReturnType, UT>( /// /// # Safety /// -/// 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() +/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.clone().ffi_cancel() } /// Complete a Rust future @@ -99,17 +103,15 @@ where /// /// # Safety /// -/// - The [Handle] must not previously have been passed to [rust_future_free] +/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free] /// - The `T` param must correctly correspond to the [rust_future_new] call. It must /// be `<Output as LowerReturn<UT>>::ReturnType` -pub unsafe fn rust_future_complete<ReturnType, UT>( - handle: Handle, +pub unsafe fn rust_future_complete<ReturnType>( + handle: RustFutureHandle, out_status: &mut RustCallStatus, -) -> ReturnType -where - dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, -{ - <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_complete(out_status) +) -> ReturnType { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.ffi_complete(out_status) } /// Free a Rust future, dropping the strong reference and releasing all references held by the @@ -117,25 +119,8 @@ where /// /// # Safety /// -/// 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() +/// 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() } - -// 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 629ee0c..aae5a0c 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, u64), + Set(RustFutureContinuationCallback, *const ()), } 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: u64) { + pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) { 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 886ee27..1f68085 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 handle = Arc::into_raw(cell.clone()) as u64; - rust_future.clone().ffi_poll(poll_continuation, handle); + let cell_ptr = Arc::into_raw(cell.clone()) as *const (); + rust_future.clone().ffi_poll(poll_continuation, cell_ptr); cell } -extern "C" fn poll_continuation(data: u64, code: RustFuturePoll) { +extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) { let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) }; cell.set(code).expect("Error setting OnceCell"); } diff --git a/src/ffi_converter_impls.rs b/src/ffi_converter_impls.rs index aec0931..5be5f04 100644 --- a/src/ffi_converter_impls.rs +++ b/src/ffi_converter_impls.rs @@ -456,11 +456,7 @@ unsafe impl<UT> LowerReturn<UT> for () { } unsafe impl<UT> LiftReturn<UT> for () { - type ReturnType = (); - - fn try_lift_successful_return(_: ()) -> Result<Self> { - Ok(()) - } + fn lift_callback_return(_buf: RustBuffer) -> Self {} const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); } @@ -497,15 +493,13 @@ where unsafe impl<UT, R, E> LiftReturn<UT> for Result<R, E> where R: LiftReturn<UT>, - E: Lift<UT, FfiType = RustBuffer> + ConvertError<UT>, + E: Lift<UT> + ConvertError<UT>, { - 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_return(buf: RustBuffer) -> Self { + Ok(R::lift_callback_return(buf)) } - fn lift_error(buf: RustBuffer) -> Self { + fn lift_callback_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 4e7b9e0..3b5914e 100644 --- a/src/ffi_converter_traits.rs +++ b/src/ffi_converter_traits.rs @@ -51,10 +51,7 @@ use std::{borrow::Borrow, sync::Arc}; use anyhow::bail; use bytes::Buf; -use crate::{ - FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, - UnexpectedUniFFICallbackError, -}; +use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError}; /// Generalized FFI conversions /// @@ -305,41 +302,14 @@ 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 { - /// 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 result + fn lift_callback_return(buf: RustBuffer) -> Self; /// Lift a Rust value for a callback interface method error result /// /// This is called for "expected errors" -- the callback method returns a Result<> type and the /// foreign code throws an exception that corresponds to the error type. - fn lift_error(_buf: RustBuffer) -> Self { + fn lift_callback_error(_buf: RustBuffer) -> Self { panic!("Callback interface method returned unexpected error") } @@ -381,66 +351,6 @@ 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: @@ -529,10 +439,9 @@ 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)*)* { - 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) + fn lift_callback_return(buf: $crate::RustBuffer) -> Self { + <Self as $crate::Lift<$ut>>::try_lift_from_rust_buffer(buf) + .expect("Error reading callback interface result") } const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::Lift<$ut>>::TYPE_ID_META; @@ -554,50 +463,4 @@ 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,8 +45,7 @@ pub mod metadata; pub use ffi::*; pub use ffi_converter_traits::{ - ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower, - LowerReturn, + ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, }; pub use metadata::*; @@ -58,13 +57,12 @@ 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 = "0.27.1"; +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. diff --git a/src/metadata.rs b/src/metadata.rs index dc61a1b..934d09c 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -71,20 +71,16 @@ pub mod codes { pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25; pub const TYPE_UNIT: u8 = 255; - // Literal codes for LiteralMetadata + // Literal codes for LiteralMetadata - note that we don't support + // all variants in the "emit/reader" context. pub const LIT_STR: u8 = 0; pub const LIT_INT: u8 = 1; pub const LIT_FLOAT: u8 = 2; pub const LIT_BOOL: u8 = 3; - pub const LIT_NONE: u8 = 4; - pub const LIT_SOME: u8 = 5; - pub const LIT_EMPTY_SEQ: u8 = 6; + pub const LIT_NULL: u8 = 4; } -// 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; +const BUF_SIZE: usize = 4096; // This struct is a kludge around the fact that Rust const generic support doesn't quite handle our // needs. |