summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Wright <adamdwright@google.com>2024-05-07 09:32:20 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-05-07 09:32:32 +0000
commit04ad7de3a2d3d5bc8848416683f214dd165a0439 (patch)
tree24ed579c417f213d9a8e49c93e4980c0d8a13e21
parent17844644b496d41d717d7286efe73683b61c624f (diff)
downloaduniffi_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.json2
-rw-r--r--Android.bp4
-rw-r--r--Cargo.lock191
-rw-r--r--Cargo.toml4
-rw-r--r--METADATA8
-rw-r--r--README.md6
-rw-r--r--src/ffi/callbackinterface.rs126
-rw-r--r--src/ffi/ffidefault.rs13
-rw-r--r--src/ffi/foreigncallbacks.rs93
-rw-r--r--src/ffi/foreignfuture.rs241
-rw-r--r--src/ffi/handle.rs46
-rw-r--r--src/ffi/mod.rs4
-rw-r--r--src/ffi/rustbuffer.rs48
-rw-r--r--src/ffi/rustcalls.rs7
-rw-r--r--src/ffi/rustfuture/future.rs10
-rw-r--r--src/ffi/rustfuture/mod.rs91
-rw-r--r--src/ffi/rustfuture/scheduler.rs4
-rw-r--r--src/ffi/rustfuture/tests.rs6
-rw-r--r--src/ffi_converter_impls.rs16
-rw-r--r--src/ffi_converter_traits.rs151
-rw-r--r--src/lib.rs6
-rw-r--r--src/metadata.rs12
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
diff --git a/Android.bp b/Android.bp
index 09559b2..c21fac2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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",
+]
diff --git a/Cargo.toml b/Cargo.toml
index ce36a21..0b74865 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/METADATA b/METADATA
index d840da5..131ba68 100644
--- a/METADATA
+++ b/METADATA
@@ -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
}
}
diff --git a/README.md b/README.md
index 64ac348..bb72360 100644
--- a/README.md
+++ b/README.md
@@ -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()) }
- }
}
diff --git a/src/lib.rs b/src/lib.rs
index ad626e1..9003b08 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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.