Skip to content

Commit 20aa0ef

Browse files
committed
Auto merge of #146434 - folkertdev:c-variadic-inherent-methods, r=workingjubilee
c-variadic: allow c-variadic inherent and trait methods tracking issue: #44930 Continuing the work of #146342, allow inherent and trait methods to be c-variadic. However, a trait that contains a c-variadic method is no longer dyn-compatible. There is, presumably, some way to make c-variadic methods dyn-compatible. However currently, we don't have confidence that it'll work reliably: when methods from a `dyn` object are cast to a function pointer, a `ReifyShim` is created. If that shim is c-variadic, it would need to forward the C variable argument list. That does appear to work, because the `va_list` is not represented in MIR at all in this case, so the registers from the call site are untouched by the shim and can be read by the actual implementation. That just does not seem like a solid implementation. Also, intuitively, why would c-variadic function, primarily needed for FFI, need to be used with `dyn` objects at all? We can revisit this limitation if a need arises. r? `@workingjubilee`
2 parents 9311767 + 321a76b commit 20aa0ef

17 files changed

+721
-49
lines changed

compiler/rustc_ast_passes/messages.ftl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ ast_passes_body_in_extern = incorrect `{$kind}` inside `extern` block
6464
6565
ast_passes_bound_in_context = bounds on `type`s in {$ctx} have no effect
6666
67-
ast_passes_c_variadic_associated_function = associated functions cannot have a C variable argument list
68-
6967
ast_passes_c_variadic_bad_extern = `...` is not supported for `extern "{$abi}"` functions
7068
.label = `extern "{$abi}"` because of this
7169
.help = only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list

compiler/rustc_ast_passes/src/ast_validation.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ impl<'a> AstValidator<'a> {
696696

697697
match fn_ctxt {
698698
FnCtxt::Foreign => return,
699-
FnCtxt::Free => match sig.header.ext {
699+
FnCtxt::Free | FnCtxt::Assoc(_) => match sig.header.ext {
700700
Extern::Implicit(_) => {
701701
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
702702
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
@@ -726,11 +726,6 @@ impl<'a> AstValidator<'a> {
726726
self.dcx().emit_err(err);
727727
}
728728
},
729-
FnCtxt::Assoc(_) => {
730-
// For now, C variable argument lists are unsupported in associated functions.
731-
let err = errors::CVariadicAssociatedFunction { span: variadic_param.span };
732-
self.dcx().emit_err(err);
733-
}
734729
}
735730
}
736731

compiler/rustc_ast_passes/src/errors.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,6 @@ pub(crate) struct ExternItemAscii {
318318
pub block: Span,
319319
}
320320

321-
#[derive(Diagnostic)]
322-
#[diag(ast_passes_c_variadic_associated_function)]
323-
pub(crate) struct CVariadicAssociatedFunction {
324-
#[primary_span]
325-
pub span: Span,
326-
}
327-
328321
#[derive(Diagnostic)]
329322
#[diag(ast_passes_c_variadic_no_extern)]
330323
#[help]

compiler/rustc_middle/src/traits/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,9 @@ impl DynCompatibilityViolation {
823823
DynCompatibilityViolation::Method(name, MethodViolationCode::AsyncFn, _) => {
824824
format!("method `{name}` is `async`").into()
825825
}
826+
DynCompatibilityViolation::Method(name, MethodViolationCode::CVariadic, _) => {
827+
format!("method `{name}` is C-variadic").into()
828+
}
826829
DynCompatibilityViolation::Method(
827830
name,
828831
MethodViolationCode::WhereClauseReferencesSelf,
@@ -977,6 +980,9 @@ pub enum MethodViolationCode {
977980
/// e.g., `fn foo<A>()`
978981
Generic,
979982

983+
/// e.g., `fn (mut ap: ...)`
984+
CVariadic,
985+
980986
/// the method's receiver (`self` argument) can't be dispatched on
981987
UndispatchableReceiver(Option<Span>),
982988
}

compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ fn virtual_call_violations_for_method<'tcx>(
426426
if let Some(code) = contains_illegal_impl_trait_in_trait(tcx, method.def_id, sig.output()) {
427427
errors.push(code);
428428
}
429+
if sig.skip_binder().c_variadic {
430+
errors.push(MethodViolationCode::CVariadic);
431+
}
429432

430433
// We can't monomorphize things like `fn foo<A>(...)`.
431434
let own_counts = tcx.generics_of(method.def_id).own_counts();
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//@ run-pass
2+
#![feature(c_variadic)]
3+
4+
#[repr(transparent)]
5+
struct S(i32);
6+
7+
impl S {
8+
unsafe extern "C" fn associated_function(mut ap: ...) -> i32 {
9+
unsafe { ap.arg() }
10+
}
11+
12+
unsafe extern "C" fn method_owned(self, mut ap: ...) -> i32 {
13+
self.0 + unsafe { ap.arg::<i32>() }
14+
}
15+
16+
unsafe extern "C" fn method_ref(&self, mut ap: ...) -> i32 {
17+
self.0 + unsafe { ap.arg::<i32>() }
18+
}
19+
20+
unsafe extern "C" fn method_mut(&mut self, mut ap: ...) -> i32 {
21+
self.0 + unsafe { ap.arg::<i32>() }
22+
}
23+
24+
unsafe extern "C" fn fat_pointer(self: Box<Self>, mut ap: ...) -> i32 {
25+
self.0 + unsafe { ap.arg::<i32>() }
26+
}
27+
}
28+
29+
fn main() {
30+
unsafe {
31+
assert_eq!(S::associated_function(32), 32);
32+
assert_eq!(S(100).method_owned(32), 132);
33+
assert_eq!(S(100).method_ref(32), 132);
34+
assert_eq!(S(100).method_mut(32), 132);
35+
assert_eq!(S::fat_pointer(Box::new(S(100)), 32), 132);
36+
37+
type Method<T> = unsafe extern "C" fn(T, ...) -> i32;
38+
39+
assert_eq!((S::associated_function as unsafe extern "C" fn(...) -> i32)(32), 32);
40+
assert_eq!((S::method_owned as Method<_>)(S(100), 32), 132);
41+
assert_eq!((S::method_ref as Method<_>)(&S(100), 32), 132);
42+
assert_eq!((S::method_mut as Method<_>)(&mut S(100), 32), 132);
43+
assert_eq!((S::fat_pointer as Method<_>)(Box::new(S(100)), 32), 132);
44+
}
45+
}

tests/ui/c-variadic/not-async.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
#![feature(c_variadic)]
33
#![crate_type = "lib"]
44

5-
async unsafe extern "C" fn cannot_be_async(x: isize, ...) {}
5+
async unsafe extern "C" fn fn_cannot_be_async(x: isize, ...) {}
66
//~^ ERROR functions cannot be both `async` and C-variadic
77
//~| ERROR hidden type for `impl Future<Output = ()>` captures lifetime that does not appear in bounds
8+
9+
struct S;
10+
11+
impl S {
12+
async unsafe extern "C" fn method_cannot_be_async(x: isize, ...) {}
13+
//~^ ERROR functions cannot be both `async` and C-variadic
14+
//~| ERROR hidden type for `impl Future<Output = ()>` captures lifetime that does not appear in bounds
15+
}
Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
error: functions cannot be both `async` and C-variadic
22
--> $DIR/not-async.rs:5:1
33
|
4-
LL | async unsafe extern "C" fn cannot_be_async(x: isize, ...) {}
5-
| ^^^^^ `async` because of this ^^^ C-variadic because of this
4+
LL | async unsafe extern "C" fn fn_cannot_be_async(x: isize, ...) {}
5+
| ^^^^^ `async` because of this ^^^ C-variadic because of this
6+
7+
error: functions cannot be both `async` and C-variadic
8+
--> $DIR/not-async.rs:12:5
9+
|
10+
LL | async unsafe extern "C" fn method_cannot_be_async(x: isize, ...) {}
11+
| ^^^^^ `async` because of this ^^^ C-variadic because of this
612

713
error[E0700]: hidden type for `impl Future<Output = ()>` captures lifetime that does not appear in bounds
8-
--> $DIR/not-async.rs:5:59
14+
--> $DIR/not-async.rs:5:62
915
|
10-
LL | async unsafe extern "C" fn cannot_be_async(x: isize, ...) {}
11-
| --------------------------------------------------------- ^^
16+
LL | async unsafe extern "C" fn fn_cannot_be_async(x: isize, ...) {}
17+
| ------------------------------------------------------------ ^^
1218
| |
1319
| opaque type defined here
1420
|
15-
= note: hidden type `{async fn body of cannot_be_async()}` captures lifetime `'_`
21+
= note: hidden type `{async fn body of fn_cannot_be_async()}` captures lifetime `'_`
22+
23+
error[E0700]: hidden type for `impl Future<Output = ()>` captures lifetime that does not appear in bounds
24+
--> $DIR/not-async.rs:12:70
25+
|
26+
LL | async unsafe extern "C" fn method_cannot_be_async(x: isize, ...) {}
27+
| ---------------------------------------------------------------- ^^
28+
| |
29+
| opaque type defined here
30+
|
31+
= note: hidden type `{async fn body of S::method_cannot_be_async()}` captures lifetime `'_`
1632

17-
error: aborting due to 2 previous errors
33+
error: aborting due to 4 previous errors
1834

1935
For more information about this error, try `rustc --explain E0700`.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Traits where a method is c-variadic are not dyn compatible.
2+
//
3+
// Creating a function pointer from a method on an `&dyn T` value creates a ReifyShim.
4+
// This shim cannot reliably forward C-variadic arguments. Thus the trait as a whole
5+
// is dyn-incompatible to prevent invalid shims from being created.
6+
#![feature(c_variadic)]
7+
8+
#[repr(transparent)]
9+
struct Struct(u64);
10+
11+
trait Trait {
12+
fn get(&self) -> u64;
13+
14+
unsafe extern "C" fn dyn_method_ref(&self, mut ap: ...) -> u64 {
15+
self.get() + unsafe { ap.arg::<u64>() }
16+
}
17+
}
18+
19+
impl Trait for Struct {
20+
fn get(&self) -> u64 {
21+
self.0
22+
}
23+
}
24+
25+
fn main() {
26+
unsafe {
27+
let dyn_object: &dyn Trait = &Struct(64);
28+
//~^ ERROR the trait `Trait` is not dyn compatible
29+
assert_eq!(dyn_object.dyn_method_ref(100), 164);
30+
assert_eq!(
31+
(Trait::dyn_method_ref as unsafe extern "C" fn(_, ...) -> u64)(dyn_object, 100),
32+
164
33+
);
34+
}
35+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error[E0038]: the trait `Trait` is not dyn compatible
2+
--> $DIR/not-dyn-compatible.rs:27:30
3+
|
4+
LL | let dyn_object: &dyn Trait = &Struct(64);
5+
| ^^^^^ `Trait` is not dyn compatible
6+
|
7+
note: for a trait to be dyn compatible it needs to allow building a vtable
8+
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
9+
--> $DIR/not-dyn-compatible.rs:14:26
10+
|
11+
LL | trait Trait {
12+
| ----- this trait is not dyn compatible...
13+
...
14+
LL | unsafe extern "C" fn dyn_method_ref(&self, mut ap: ...) -> u64 {
15+
| ^^^^^^^^^^^^^^ ...because method `dyn_method_ref` is C-variadic
16+
= help: consider moving `dyn_method_ref` to another trait
17+
= help: only type `Struct` implements `Trait`; consider using it directly instead.
18+
19+
error: aborting due to 1 previous error
20+
21+
For more information about this error, try `rustc --explain E0038`.

0 commit comments

Comments
 (0)