c-variadic: allow trait methods to be c-variadic

but a C-variadic method makes a trait dyn-incompatible. That is because
methods from dyn traits, when cast to a function pointer, create a shim.
That shim can't really forward the c-variadic arguments.
This commit is contained in:
Folkert de Vries
2025-09-10 17:53:41 +02:00
parent fd48528d18
commit 01e83adc88
13 changed files with 160 additions and 91 deletions

View File

@@ -64,8 +64,6 @@ ast_passes_body_in_extern = incorrect `{$kind}` inside `extern` block
ast_passes_bound_in_context = bounds on `type`s in {$ctx} have no effect
ast_passes_c_variadic_associated_function = associated functions cannot have a C variable argument list
ast_passes_c_variadic_bad_extern = `...` is not supported for `extern "{$abi}"` functions
.label = `extern "{$abi}"` because of this
.help = only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list

View File

@@ -696,8 +696,7 @@ impl<'a> AstValidator<'a> {
match fn_ctxt {
FnCtxt::Foreign => return,
FnCtxt::Free | FnCtxt::Assoc(AssocCtxt::Impl { of_trait: false }) => {
match sig.header.ext {
FnCtxt::Free | FnCtxt::Assoc(_) => match sig.header.ext {
Extern::Implicit(_) => {
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
@@ -726,13 +725,7 @@ impl<'a> AstValidator<'a> {
let err = errors::CVariadicNoExtern { span: variadic_param.span };
self.dcx().emit_err(err);
}
}
}
FnCtxt::Assoc(_) => {
// For now, C variable argument lists are unsupported in associated functions.
let err = errors::CVariadicAssociatedFunction { span: variadic_param.span };
self.dcx().emit_err(err);
}
},
}
}

View File

@@ -318,13 +318,6 @@ pub(crate) struct ExternItemAscii {
pub block: Span,
}
#[derive(Diagnostic)]
#[diag(ast_passes_c_variadic_associated_function)]
pub(crate) struct CVariadicAssociatedFunction {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(ast_passes_c_variadic_no_extern)]
#[help]

View File

@@ -823,6 +823,9 @@ impl DynCompatibilityViolation {
DynCompatibilityViolation::Method(name, MethodViolationCode::AsyncFn, _) => {
format!("method `{name}` is `async`").into()
}
DynCompatibilityViolation::Method(name, MethodViolationCode::CVariadic, _) => {
format!("method `{name}` is C-variadic").into()
}
DynCompatibilityViolation::Method(
name,
MethodViolationCode::WhereClauseReferencesSelf,
@@ -977,6 +980,9 @@ pub enum MethodViolationCode {
/// e.g., `fn foo<A>()`
Generic,
/// e.g., `fn (mut ap: ...)`
CVariadic,
/// the method's receiver (`self` argument) can't be dispatched on
UndispatchableReceiver(Option<Span>),
}

View File

@@ -426,6 +426,9 @@ fn virtual_call_violations_for_method<'tcx>(
if let Some(code) = contains_illegal_impl_trait_in_trait(tcx, method.def_id, sig.output()) {
errors.push(code);
}
if sig.skip_binder().c_variadic {
errors.push(MethodViolationCode::CVariadic);
}
// We can't monomorphize things like `fn foo<A>(...)`.
let own_counts = tcx.generics_of(method.def_id).own_counts();

View File

@@ -0,0 +1,35 @@
// Traits where a method is c-variadic are not dyn compatible.
//
// Creating a function pointer from a method on an `&dyn T` value creates a ReifyShim.
// This shim cannot reliably forward C-variadic arguments. Thus the trait as a whole
// is dyn-incompatible to prevent invalid shims from being created.
#![feature(c_variadic)]
#[repr(transparent)]
struct Struct(u64);
trait Trait {
fn get(&self) -> u64;
unsafe extern "C" fn dyn_method_ref(&self, mut ap: ...) -> u64 {
self.get() + unsafe { ap.arg::<u64>() }
}
}
impl Trait for Struct {
fn get(&self) -> u64 {
self.0
}
}
fn main() {
unsafe {
let dyn_object: &dyn Trait = &Struct(64);
//~^ ERROR the trait `Trait` is not dyn compatible
assert_eq!(dyn_object.dyn_method_ref(100), 164);
assert_eq!(
(Trait::dyn_method_ref as unsafe extern "C" fn(_, ...) -> u64)(dyn_object, 100),
164
);
}
}

View File

@@ -0,0 +1,21 @@
error[E0038]: the trait `Trait` is not dyn compatible
--> $DIR/not-dyn-compatible.rs:27:30
|
LL | let dyn_object: &dyn Trait = &Struct(64);
| ^^^^^ `Trait` is not dyn compatible
|
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
--> $DIR/not-dyn-compatible.rs:14:26
|
LL | trait Trait {
| ----- this trait is not dyn compatible...
...
LL | unsafe extern "C" fn dyn_method_ref(&self, mut ap: ...) -> u64 {
| ^^^^^^^^^^^^^^ ...because method `dyn_method_ref` is C-variadic
= help: consider moving `dyn_method_ref` to another trait
= help: only type `Struct` implements `Trait`; consider using it directly instead.
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0038`.

View File

@@ -1,40 +1,73 @@
// For now C-variadic arguments in trait methods are rejected, though we aim to lift this
// restriction in the future. In particular we need to think about the interaction with
// `dyn Trait` and the `ReifyShim`s that it may generate for methods.
//@ run-pass
#![feature(c_variadic)]
#![crate_type = "lib"]
struct S;
impl S {
#[repr(transparent)]
struct Struct(i32);
impl Struct {
unsafe extern "C" fn associated_function(mut ap: ...) -> i32 {
unsafe { ap.arg() }
}
unsafe extern "C" fn method(&self, mut ap: ...) -> i32 {
unsafe { ap.arg() }
self.0 + unsafe { ap.arg::<i32>() }
}
}
trait T {
trait Trait: Sized {
fn get(&self) -> i32;
unsafe extern "C" fn trait_associated_function(mut ap: ...) -> i32 {
//~^ ERROR: associated functions cannot have a C variable argument list
unsafe { ap.arg() }
}
unsafe extern "C" fn trait_method(&self, mut ap: ...) -> i32 {
//~^ ERROR: associated functions cannot have a C variable argument list
unsafe { ap.arg() }
unsafe extern "C" fn trait_method_owned(self, mut ap: ...) -> i32 {
self.get() + unsafe { ap.arg::<i32>() }
}
unsafe extern "C" fn trait_method_ref(&self, mut ap: ...) -> i32 {
self.get() + unsafe { ap.arg::<i32>() }
}
unsafe extern "C" fn trait_method_mut(&mut self, mut ap: ...) -> i32 {
self.get() + unsafe { ap.arg::<i32>() }
}
unsafe extern "C" fn trait_fat_pointer(self: Box<Self>, mut ap: ...) -> i32 {
self.get() + unsafe { ap.arg::<i32>() }
}
}
impl T for S {}
impl Trait for Struct {
fn get(&self) -> i32 {
self.0
}
}
fn main() {
unsafe {
assert_eq!(S::associated_function(32), 32);
assert_eq!(S.method(32), 32);
assert_eq!(Struct::associated_function(32), 32);
assert_eq!(Struct(100).method(32), 132);
assert_eq!(S::trait_associated_function(32), 32);
assert_eq!(S.trait_method(32), 32);
assert_eq!(Struct::trait_associated_function(32), 32);
assert_eq!(Struct(100).trait_method_owned(32), 132);
assert_eq!(Struct(100).trait_method_ref(32), 132);
assert_eq!(Struct(100).trait_method_mut(32), 132);
assert_eq!(Struct::trait_fat_pointer(Box::new(Struct(100)), 32), 132);
assert_eq!(<Struct as Trait>::trait_associated_function(32), 32);
assert_eq!(Trait::trait_method_owned(Struct(100), 32), 132);
assert_eq!(Trait::trait_method_ref(&Struct(100), 32), 132);
assert_eq!(Trait::trait_method_mut(&mut Struct(100), 32), 132);
assert_eq!(Trait::trait_fat_pointer(Box::new(Struct(100)), 32), 132);
type Associated = unsafe extern "C" fn(...) -> i32;
type Method<T> = unsafe extern "C" fn(T, ...) -> i32;
assert_eq!((Struct::trait_associated_function as Associated)(32), 32);
assert_eq!((Struct::trait_method_owned as Method<_>)(Struct(100), 32), 132);
assert_eq!((Struct::trait_method_ref as Method<_>)(&Struct(100), 32), 132);
assert_eq!((Struct::trait_method_mut as Method<_>)(&mut Struct(100), 32), 132);
assert_eq!((Struct::trait_fat_pointer as Method<_>)(Box::new(Struct(100)), 32), 132);
}
}

View File

@@ -1,14 +0,0 @@
error: associated functions cannot have a C variable argument list
--> $DIR/trait-method.rs:19:52
|
LL | unsafe extern "C" fn trait_associated_function(mut ap: ...) -> i32 {
| ^^^^^^^^^^^
error: associated functions cannot have a C variable argument list
--> $DIR/trait-method.rs:24:46
|
LL | unsafe extern "C" fn trait_method(&self, mut ap: ...) -> i32 {
| ^^^^^^^^^^^
error: aborting due to 2 previous errors

View File

@@ -6,5 +6,4 @@ pub unsafe extern "C" fn test(_: i32, ap: ...) {}
trait Trait {
unsafe extern "C" fn trait_test(_: i32, ap: ...) {}
//~^ ERROR C-variadic functions are unstable
//~| ERROR associated functions cannot have a C variable argument list
}

View File

@@ -1,9 +1,3 @@
error: associated functions cannot have a C variable argument list
--> $DIR/feature-gate-c_variadic.rs:7:45
|
LL | unsafe extern "C" fn trait_test(_: i32, ap: ...) {}
| ^^^^^^^
error[E0658]: C-variadic functions are unstable
--> $DIR/feature-gate-c_variadic.rs:3:1
|
@@ -24,6 +18,6 @@ LL | unsafe extern "C" fn trait_test(_: i32, ap: ...) {}
= help: add `#![feature(c_variadic)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 3 previous errors
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.

View File

@@ -70,13 +70,13 @@ impl X {
trait T {
fn t_f1(x: isize, ...) {}
//~^ ERROR associated functions cannot have a C variable argument list
//~^ ERROR `...` is not supported for non-extern functions
fn t_f2(x: isize, ...);
//~^ ERROR associated functions cannot have a C variable argument list
//~^ ERROR `...` is not supported for non-extern functions
fn t_f3(...) {}
//~^ ERROR associated functions cannot have a C variable argument list
//~^ ERROR `...` is not supported for non-extern functions
fn t_f4(...);
//~^ ERROR associated functions cannot have a C variable argument list
//~^ ERROR `...` is not supported for non-extern functions
fn t_f5(..., x: isize) {}
//~^ ERROR `...` must be the last argument of a C-variadic function
fn t_f6(..., x: isize);

View File

@@ -192,29 +192,37 @@ LL | const fn i_f5(x: isize, ...) {}
|
= help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list
error: associated functions cannot have a C variable argument list
error: `...` is not supported for non-extern functions
--> $DIR/variadic-ffi-semantic-restrictions.rs:72:23
|
LL | fn t_f1(x: isize, ...) {}
| ^^^
|
= help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list
error: associated functions cannot have a C variable argument list
error: `...` is not supported for non-extern functions
--> $DIR/variadic-ffi-semantic-restrictions.rs:74:23
|
LL | fn t_f2(x: isize, ...);
| ^^^
|
= help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list
error: associated functions cannot have a C variable argument list
error: `...` is not supported for non-extern functions
--> $DIR/variadic-ffi-semantic-restrictions.rs:76:13
|
LL | fn t_f3(...) {}
| ^^^
|
= help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list
error: associated functions cannot have a C variable argument list
error: `...` is not supported for non-extern functions
--> $DIR/variadic-ffi-semantic-restrictions.rs:78:13
|
LL | fn t_f4(...);
| ^^^
|
= help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list
error: `...` must be the last argument of a C-variadic function
--> $DIR/variadic-ffi-semantic-restrictions.rs:80:13