Repository: dtolnay/cxx
Pull request #1415 (2024-12-16 23:51:53)

Suppress `unused_unsafe` warnings within the generated code.
Sometimes Rust functions declared inside the `#[cxx::bridge]` have to be marked as unsafe (e.g. if they need to use an explicit lifetime). When the actual, wrapped function is _not_ unsafe, then the generated code will result in an `unused_unsafe` warning.  For example:

```
   Compiling cxx-test-suite v0.0.0 (/usr/local/google/home/lukasza/src/github/cxx/tests/ffi)
error: unnecessary `unsafe` block
   --> tests/ffi/lib.rs:276:19
    |
276 |         unsafe fn r_return_str_via_out_param<'a>(shared: &'a Shared, out_param: &mut &'a str);
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^ unnecessary `unsafe` block
    |
note: the lint level is defined here
   --> tests/ffi/lib.rs:20:9
    |
20  | #![deny(warnings)]  // Check that expansion of `cxx::bridge` doesn't trigger warnings.
    |         ^^^^^^^^
    = note: `#[deny(unused_unsafe)]` implied by `#[deny(warnings)]`
```

The warning above comes from the following expansion:

```
unsafe fn __r_return_str_via_out_param<'a>(...) {
    // The `unsafe` block below is unnecessary if the actual, wrapped
    // function is *not* `unsafe`.
    unsafe { super::r_return_str_via_out_param(...) }
}
```

This commit avoids the warning by including an explicit `#[allow(unused_unsafe)]` in the generated code.

anforowicz: This PR is motivated by the desire to address the TODO at https://skia-review.googlesource.com/c/skia/+/928799/4/experimental/rust_png/ffi/FFI.rs
anforowicz: Hmmm... when re-reading the commit / PR description, the 2nd part seems out-of-place (since there is no `foo` in the commit - this expansion comes from a separate, ad-hoc, library crate that I used to understand better what is going on).

Let me do a quick force-push to update the commit descriptions... sorry...

Patch:
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 8256db1b..35597f0f 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -1231,11 +1231,14 @@ fn expand_rust_function_shim_super(
     };
 
     let mut body = quote_spanned!(span=> #call(#(#vars,)*));
+    let mut allow_unused_unsafe = quote!();
     if unsafety.is_some() {
-        body = quote_spanned!(span=> unsafe { #body });
+        body = quote_spanned!(span=>unsafe { #body });
+        allow_unused_unsafe = quote_spanned!(span=> #[allow(unused_unsafe)]);
     }
 
     quote_spanned! {span=>
+        #allow_unused_unsafe
         #unsafety fn #local_name #generics(#(#all_args,)*) #ret {
             #body
         }


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 57d94ee7..d15a48b5 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -17,6 +17,7 @@
 #![allow(unknown_lints)]
 #![warn(rust_2024_compatibility)]
 #![forbid(unsafe_op_in_unsafe_fn)]
+#![deny(warnings)] // Check that expansion of `cxx::bridge` doesn't trigger warnings.
 
 pub mod cast;
 pub mod module;
@@ -272,6 +273,7 @@ pub mod ffi {
         fn r_return_ref(shared: &Shared) -> &usize;
         fn r_return_mut(shared: &mut Shared) -> &mut usize;
         fn r_return_str(shared: &Shared) -> &str;
+        unsafe fn r_return_str_via_out_param<'a>(shared: &'a Shared, out_param: &mut &'a str);
         fn r_return_sliceu8(shared: &Shared) -> &[u8];
         fn r_return_mutsliceu8(slice: &mut [u8]) -> &mut [u8];
         fn r_return_rust_string() -> String;
@@ -491,6 +493,11 @@ fn r_return_str(shared: &ffi::Shared) -> &str {
     "2020"
 }
 
+fn r_return_str_via_out_param<'a>(shared: &'a ffi::Shared, out_param: &mut &'a str) {
+    let _ = shared;
+    *out_param = "2020"
+}
+
 fn r_return_sliceu8(shared: &ffi::Shared) -> &[u8] {
     let _ = shared;
     b"2020"
diff --git a/tests/ffi/module.rs b/tests/ffi/module.rs
index 21a86206..e298c025 100644
--- a/tests/ffi/module.rs
+++ b/tests/ffi/module.rs
@@ -1,3 +1,5 @@
+#![deny(warnings)] // Check that expansion of `cxx::bridge` doesn't trigger warnings.
+
 #[cxx::bridge(namespace = "tests")]
 pub mod ffi {
     struct Job {
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index ca71276f..2292914c 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -866,6 +866,12 @@ extern "C" const char *cxx_run_test() noexcept {
   cstring.reserve(5);
   ASSERT(cstring.capacity() >= 5);
 
+  {
+    rust::Str out_param;
+    r_return_str_via_out_param(Shared{2020}, out_param);
+    ASSERT(out_param == "2020");
+  }
+
   rust::Str cstr = "test";
   rust::Str other_cstr = "foo";
   swap(cstr, other_cstr);