Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 0.5.2

- fix a narrow race in async cancellation. If `cancel()` on a `CompletableFuture` returned by an async function landed in the few instructions between the pipeline's `isCancelled()` check and its own `rust_future_free`, or between a wake and the re-poll it triggers, the Rust future was freed twice or polled after being freed. Seen once in CI as a glibc `tcache_thread_shutdown()` abort. Every use of the handle now runs under the future's monitor and stops once it has been freed. No measurable change to async call overhead.
- fix generated Java failing to compile when a Rust field or parameter name matched a name the generator used in the same scope, such as an enum variant field named `value` ([#63](https://github.com/IronCoreLabs/uniffi-bindgen-java/issues/63)). Enum and error variant field names, and function, method, constructor and callback-interface parameter names can no longer collide with anything the Java generator emits. Names uniffi-rs itself reserves on the Rust side, such as a field named `buf` or a callback-interface parameter named `uniffi_handle`, are rejected by the Rust derive before bindings are generated and remain unavailable.

## 0.5.1

Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ uniffi-fixture-coverall = { git = "https://github.com/mozilla/uniffi-rs.git", br
uniffi-fixture-enum-types = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "release-v0.32.x" }
uniffi-fixture-ext-types = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "release-v0.32.x" }
uniffi-fixture-futures = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "release-v0.32.x" }
uniffi-fixture-name-collisions = { path = "fixtures/name-collisions" }
uniffi-fixture-primitive-arrays = { path = "fixtures/primitive-arrays" }
uniffi-fixture-proc-macro = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "release-v0.32.x" }
uniffi-fixture-rename = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "release-v0.32.x" }
Expand Down
13 changes: 13 additions & 0 deletions fixtures/name-collisions/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "uniffi-fixture-name-collisions"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib", "lib"]
name = "uniffi_fixture_name_collisions"

[dependencies]
uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", branch = "release-v0.32.x" }
async-trait = "0.1"
thiserror = "2"
194 changes: 194 additions & 0 deletions fixtures/name-collisions/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/* 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/. */

//! User-chosen identifiers that equal names the Java templates introduce in the same scope.
//!
//! Absent here, because uniffi-rs's own Rust derives and FFI callback signatures already reject
//! them: a field named `buf`; callback-interface parameters named `call_status`,
//! `uniffi_call_status`, `uniffi_handle`, `uniffi_out_return`, `uniffi_future_callback`,
//! `uniffi_callback_data` or `uniffi_out_dropped_callback`.

use std::sync::Arc;

uniffi::setup_scaffolding!("name_collisions");

/// `value` is the converter's parameter; `v1`/`v2` are its positional pattern bindings.
#[derive(Debug, Clone, PartialEq, uniffi::Enum)]
pub enum Shadowing {
Unit,
Named { value: String, v1: i32, v2: bool },
Tuple(String, u64),
}

#[uniffi::export]
fn roundtrip_enum(value: Shadowing) -> Shadowing {
value
}

#[uniffi::export]
fn enum_named_value(buf: Shadowing) -> Option<String> {
match buf {
Shadowing::Named { value, .. } => Some(value),
_ => None,
}
}

/// `x` is the error converter's type-pattern binding.
#[derive(Debug, Clone, PartialEq, thiserror::Error, uniffi::Error)]
pub enum ShadowError {
#[error("named {value} {x}")]
Named { value: String, x: i32 },
}

#[uniffi::export]
fn throw_named(value: String, x: i32) -> Result<(), ShadowError> {
Err(ShadowError::Named { value, x })
}

/// Parameters named after the locals of the sync call path and the `UniffiLib` wrappers.
#[uniffi::export]
fn sync_fn(
uniffi_out_err: String,
status: String,
allocator: String,
it: String,
e: String,
) -> String {
format!("{uniffi_out_err}|{status}|{allocator}|{it}|{e}")
}

/// Parameters named after the locals of the async call path.
#[uniffi::export]
async fn async_fn(uniffi_executor: String, it: String, uniffi_result: String) -> String {
format!("{uniffi_executor}|{it}|{uniffi_result}")
}

#[derive(uniffi::Object)]
pub struct Holder {
tag: String,
}

#[uniffi::export]
impl Holder {
#[uniffi::constructor]
fn new(uniffi_handle: String) -> Arc<Self> {
Arc::new(Self { tag: uniffi_handle })
}

fn sync_method(&self, uniffi_handle: String, uniffi_out_err: String) -> String {
format!("{}|{uniffi_handle}|{uniffi_out_err}", self.tag)
}

async fn async_method(
&self,
uniffi_handle: String,
uniffi_executor: String,
it: String,
) -> String {
format!("{}|{uniffi_handle}|{uniffi_executor}|{it}", self.tag)
}
}

/// Parameters named after every local the callback-interface implementation class binds.
#[uniffi::export(with_foreign)]
#[async_trait::async_trait]
pub trait Shadow: Send + Sync {
fn sync_value(
&self,
uniffi_obj: String,
make_call: String,
write_return: String,
uniffi_value: String,
out_return: String,
lowered: String,
status: String,
) -> String;

fn sync_primitive(&self, uniffi_value: i32, out_return: i32) -> i32;

fn sync_void(&self, nothing: String, status: String);

fn sync_throws(&self, e: String, status: String) -> Result<String, ShadowError>;

async fn async_value(
&self,
uniffi_completion_descriptor: String,
uniffi_handle_success: String,
return_value: String,
uniffi_result: String,
global_callback: String,
mh: String,
t: String,
uniffi_handle_error: String,
status: String,
lowered: String,
) -> String;

async fn async_primitive(&self, return_value: i32, uniffi_result: i32) -> i32;

async fn async_void(&self, nothing: String, return_value: String);

async fn async_throws(&self, e: String, t: String) -> Result<String, ShadowError>;
}

#[uniffi::export]
fn call_sync_value(shadow: Arc<dyn Shadow>, prefix: String) -> String {
shadow.sync_value(
format!("{prefix}obj"),
format!("{prefix}call"),
format!("{prefix}ret"),
format!("{prefix}val"),
format!("{prefix}out"),
format!("{prefix}low"),
format!("{prefix}status"),
)
}

#[uniffi::export]
fn call_sync_primitive(shadow: Arc<dyn Shadow>, a: i32, b: i32) -> i32 {
shadow.sync_primitive(a, b)
}

#[uniffi::export]
fn call_sync_void(shadow: Arc<dyn Shadow>, nothing: String) {
shadow.sync_void(nothing, "status".to_string())
}

#[uniffi::export]
fn call_sync_throws(shadow: Arc<dyn Shadow>, e: String) -> Result<String, ShadowError> {
shadow.sync_throws(e, "status".to_string())
}

#[uniffi::export]
async fn call_async_value(shadow: Arc<dyn Shadow>, prefix: String) -> String {
shadow
.async_value(
format!("{prefix}desc"),
format!("{prefix}success"),
format!("{prefix}ret"),
format!("{prefix}result"),
format!("{prefix}global"),
format!("{prefix}mh"),
format!("{prefix}t"),
format!("{prefix}error"),
format!("{prefix}status"),
format!("{prefix}low"),
)
.await
}

#[uniffi::export]
async fn call_async_primitive(shadow: Arc<dyn Shadow>, a: i32, b: i32) -> i32 {
shadow.async_primitive(a, b).await
}

#[uniffi::export]
async fn call_async_void(shadow: Arc<dyn Shadow>, nothing: String) {
shadow.async_void(nothing, "ret".to_string()).await
}

#[uniffi::export]
async fn call_async_throws(shadow: Arc<dyn Shadow>, e: String) -> Result<String, ShadowError> {
shadow.async_throws(e, "t".to_string()).await
}
26 changes: 26 additions & 0 deletions src/gen_java/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,9 @@ impl JavaCodeOracle {
}

/// Get the idiomatic Java rendering of a variable name.
///
/// Output is `[a-z][A-Za-z0-9]*`, or `_` followed by a Java keyword. Templates rely on this:
/// a generated local named `_x` with `x` not a keyword can never equal a user's argument name.
pub fn var_name(&self, nm: &str) -> String {
fixup_keyword(self.var_name_raw(nm))
}
Expand Down Expand Up @@ -2172,6 +2175,29 @@ mod tests {
RecordMetadata, TraitKind, TraitMethodMetadata, Type, VariantMetadata,
};

#[test]
fn var_name_never_yields_underscore_prefixed_non_keyword() {
for input in [
"_status",
"__uniffi_handle",
"_",
"uniffi_handle",
"_uniffiHandle",
"make_call",
"_allocator",
"it",
] {
let name = JavaCodeOracle.var_name(input);
let rest = name.strip_prefix('_');
assert!(
rest.is_none_or(|rest| KEYWORDS.contains(rest)),
"{input:?} rendered as {name:?}, which a template-owned `_` local could equal"
);
}
assert_eq!(JavaCodeOracle.var_name("int"), "_int");
assert_eq!(JavaCodeOracle.var_name("uniffi_handle"), "uniffiHandle");
}

#[test]
fn error_variant_holding_an_object_is_closeable() {
let mut group = test_group();
Expand Down
Loading
Loading