Skip to content

Commit 3db709c

Browse files
authored
Merge pull request #439 from dtolnay/sig
Enforce accurate unsafety declaration on extern Rust sigs
2 parents c550154 + 0e7e37f commit 3db709c

File tree

7 files changed

+109
-20
lines changed

7 files changed

+109
-20
lines changed

macro/src/expand.rs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -563,14 +563,10 @@ fn expand_rust_function_shim_impl(
563563
});
564564
let vars = receiver_var.into_iter().chain(arg_vars);
565565

566+
let wrap_super = invoke.map(|invoke| expand_rust_function_shim_super(sig, &local_name, invoke));
567+
566568
let mut call = match invoke {
567-
Some(ident) => match &sig.receiver {
568-
None => quote!(super::#ident),
569-
Some(receiver) => {
570-
let receiver_type = &receiver.ty;
571-
quote!(#receiver_type::#ident)
572-
}
573-
},
569+
Some(_) => quote!(#local_name),
574570
None => quote!(::std::mem::transmute::<*const (), #sig>(__extern)),
575571
};
576572
call.extend(quote! { (#(#vars),*) });
@@ -648,11 +644,61 @@ fn expand_rust_function_shim_impl(
648644
#[export_name = #link_name]
649645
unsafe extern "C" fn #local_name(#(#all_args,)* #outparam #pointer) #ret {
650646
let __fn = concat!(module_path!(), #catch_unwind_label);
647+
#wrap_super
651648
#expr
652649
}
653650
}
654651
}
655652

653+
// A wrapper like `fn f(x: Arg) { super::f(x) }` just to ensure we have the
654+
// accurate unsafety declaration and no problematic elided lifetimes.
655+
fn expand_rust_function_shim_super(
656+
sig: &Signature,
657+
local_name: &Ident,
658+
invoke: &Ident,
659+
) -> TokenStream {
660+
let unsafety = sig.unsafety;
661+
662+
let receiver_var = sig
663+
.receiver
664+
.as_ref()
665+
.map(|receiver| Ident::new("__self", receiver.var.span));
666+
let receiver = sig.receiver.iter().map(|receiver| {
667+
let receiver_type = receiver.ty();
668+
quote!(#receiver_var: #receiver_type)
669+
});
670+
let args = sig.args.iter().map(|arg| quote!(#arg));
671+
let all_args = receiver.chain(args);
672+
673+
let ret = if sig.throws {
674+
let ok = match &sig.ret {
675+
Some(ret) => quote!(#ret),
676+
None => quote!(()),
677+
};
678+
quote!(-> ::std::result::Result<#ok, impl ::std::fmt::Display>)
679+
} else {
680+
expand_return_type(&sig.ret)
681+
};
682+
683+
let arg_vars = sig.args.iter().map(|arg| &arg.ident);
684+
let vars = receiver_var.iter().chain(arg_vars);
685+
686+
let span = invoke.span();
687+
let call = match &sig.receiver {
688+
None => quote_spanned!(span=> super::#invoke),
689+
Some(receiver) => {
690+
let receiver_type = &receiver.ty;
691+
quote_spanned!(span=> #receiver_type::#invoke)
692+
}
693+
};
694+
695+
quote_spanned! {span=>
696+
#unsafety fn #local_name(#(#all_args,)*) #ret {
697+
#call(#(#vars,)*)
698+
}
699+
}
700+
}
701+
656702
fn expand_type_alias(alias: &TypeAlias) -> TokenStream {
657703
let doc = &alias.doc;
658704
let ident = &alias.name.rust;

tests/ffi/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(
22
clippy::boxed_local,
3+
clippy::just_underscores_and_digits,
34
clippy::ptr_arg,
45
clippy::trivially_copy_pass_by_ref
56
)]

tests/ui/missing_unsafe.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[cxx::bridge]
2+
mod ffi {
3+
extern "Rust" {
4+
fn f(x: i32);
5+
}
6+
}
7+
8+
unsafe fn f(_x: i32) {}
9+
10+
fn main() {}

tests/ui/missing_unsafe.stderr

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
2+
--> $DIR/missing_unsafe.rs:4:12
3+
|
4+
4 | fn f(x: i32);
5+
| ^ call to unsafe function
6+
|
7+
= note: consult the function's documentation for information on how to avoid undefined behavior

tests/ui/result_no_display.stderr

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
error[E0277]: `NonError` doesn't implement `std::fmt::Display`
2-
--> $DIR/result_no_display.rs:1:1
3-
|
4-
1 | #[cxx::bridge]
5-
| ^^^^^^^^^^^^^^ `NonError` cannot be formatted with the default formatter
6-
|
7-
::: $WORKSPACE/src/result.rs
8-
|
9-
| E: Display,
10-
| ------- required by this bound in `cxx::private::r#try`
11-
|
12-
= help: the trait `std::fmt::Display` is not implemented for `NonError`
13-
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
14-
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
2+
--> $DIR/result_no_display.rs:1:1
3+
|
4+
1 | #[cxx::bridge]
5+
| ^^^^^^^^^^^^^^ `NonError` cannot be formatted with the default formatter
6+
|
7+
= help: the trait `std::fmt::Display` is not implemented for `NonError`
8+
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

tests/ui/unsupported_elided.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::marker::PhantomData;
2+
3+
#[cxx::bridge]
4+
mod ffi {
5+
extern "Rust" {
6+
type T;
7+
8+
fn f(t: &T) -> &str;
9+
}
10+
}
11+
12+
pub struct T<'a> {
13+
_lifetime: PhantomData<&'a ()>,
14+
}
15+
16+
fn f<'a>(_t: &T<'a>) -> &'a str {
17+
""
18+
}
19+
20+
fn main() {}

tests/ui/unsupported_elided.stderr

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0106]: missing lifetime specifier
2+
--> $DIR/unsupported_elided.rs:8:24
3+
|
4+
8 | fn f(t: &T) -> &str;
5+
| -- ^ expected named lifetime parameter
6+
|
7+
= help: this function's return type contains a borrowed value, but the signature does not say which one of `t`'s 2 lifetimes it is borrowed from
8+
help: consider introducing a named lifetime parameter
9+
|
10+
8 | fn f<'a>(t: &'a T) -> &'a str;
11+
| ^^^^ ^^^^^ ^^^

0 commit comments

Comments
 (0)