add feature(c_variadic_naked_functions)

This commit is contained in:
Folkert de Vries 2025-11-09 14:22:45 +01:00
parent 568c6ed8c9
commit ebd173f512
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
10 changed files with 234 additions and 32 deletions

View file

@ -68,6 +68,10 @@ ast_passes_c_variadic_bad_extern = `...` is not supported for `extern "{$abi}"`
.label = `extern "{$abi}"` because of this
.help = only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list
ast_passes_c_variadic_bad_naked_extern = `...` is not supported for `extern "{$abi}"` naked functions
.label = `extern "{$abi}"` because of this
.help = C-variadic function must have a compatible calling convention
ast_passes_c_variadic_must_be_unsafe =
functions with a C variable argument list must be unsafe
.suggestion = add the `unsafe` keyword to this definition

View file

@ -21,7 +21,7 @@ use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use itertools::{Either, Itertools};
use rustc_abi::{CanonAbi, ExternAbi, InterruptKind};
use rustc_abi::{CVariadicStatus, CanonAbi, ExternAbi, InterruptKind};
use rustc_ast::visit::{AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor, walk_list};
use rustc_ast::*;
use rustc_ast_pretty::pprust::{self, State};
@ -35,6 +35,7 @@ use rustc_session::lint::builtin::{
DEPRECATED_WHERE_CLAUSE_LOCATION, MISSING_ABI, MISSING_UNSAFE_ON_EXTERN,
PATTERNS_IN_FNS_WITHOUT_BODY,
};
use rustc_session::parse::feature_err;
use rustc_span::{Ident, Span, kw, sym};
use rustc_target::spec::{AbiMap, AbiMapping};
use thin_vec::thin_vec;
@ -659,7 +660,7 @@ impl<'a> AstValidator<'a> {
/// C-variadics must be:
/// - Non-const
/// - Either foreign, or free and `unsafe extern "C"` semantically
fn check_c_variadic_type(&self, fk: FnKind<'a>) {
fn check_c_variadic_type(&self, fk: FnKind<'a>, attrs: &'a AttrVec) {
// `...` is already rejected when it is not the final parameter.
let variadic_param = match fk.decl().inputs.last() {
Some(param) if matches!(param.ty.kind, TyKind::CVarArgs) => param,
@ -691,36 +692,92 @@ impl<'a> AstValidator<'a> {
match fn_ctxt {
FnCtxt::Foreign => return,
FnCtxt::Free | FnCtxt::Assoc(_) => match sig.header.ext {
Extern::Implicit(_) => {
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
span: variadic_param.span,
unsafe_span: sig.safety_span(),
});
FnCtxt::Free | FnCtxt::Assoc(_) => {
match sig.header.ext {
Extern::Implicit(_) => {
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
span: variadic_param.span,
unsafe_span: sig.safety_span(),
});
}
}
Extern::Explicit(StrLit { symbol_unescaped, .. }, _) => {
// Just bail if the ABI is not even recognized.
let Ok(abi) = ExternAbi::from_str(symbol_unescaped.as_str()) else {
return;
};
self.check_c_variadic_abi(abi, attrs, variadic_param.span, sig);
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
span: variadic_param.span,
unsafe_span: sig.safety_span(),
});
}
}
Extern::None => {
let err = errors::CVariadicNoExtern { span: variadic_param.span };
self.dcx().emit_err(err);
}
}
Extern::Explicit(StrLit { symbol_unescaped, .. }, _) => {
if !matches!(symbol_unescaped, sym::C | sym::C_dash_unwind) {
self.dcx().emit_err(errors::CVariadicBadExtern {
span: variadic_param.span,
abi: symbol_unescaped,
extern_span: sig.extern_span(),
});
}
}
}
fn check_c_variadic_abi(
&self,
abi: ExternAbi,
attrs: &'a AttrVec,
dotdotdot_span: Span,
sig: &FnSig,
) {
// For naked functions we accept any ABI that is accepted on c-variadic
// foreign functions, if the c_variadic_naked_functions feature is enabled.
if attr::contains_name(attrs, sym::naked) {
match abi.supports_c_variadic() {
CVariadicStatus::Stable if let ExternAbi::C { .. } = abi => {
// With `c_variadic` naked c-variadic `extern "C"` functions are allowed.
}
CVariadicStatus::Stable => {
// For e.g. aapcs or sysv64 `c_variadic_naked_functions` must also be enabled.
if !self.features.enabled(sym::c_variadic_naked_functions) {
let msg = format!("Naked c-variadic `extern {abi}` functions are unstable");
feature_err(&self.sess, sym::c_variadic_naked_functions, sig.span, msg)
.emit();
}
}
CVariadicStatus::Unstable { feature } => {
// Some ABIs need additional features.
if !self.features.enabled(sym::c_variadic_naked_functions) {
let msg = format!("Naked c-variadic `extern {abi}` functions are unstable");
feature_err(&self.sess, sym::c_variadic_naked_functions, sig.span, msg)
.emit();
}
if !matches!(sig.header.safety, Safety::Unsafe(_)) {
self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
span: variadic_param.span,
unsafe_span: sig.safety_span(),
});
if !self.features.enabled(feature) {
let msg = format!(
"C-variadic functions with the {abi} calling convention are unstable"
);
feature_err(&self.sess, feature, sig.span, msg).emit();
}
}
Extern::None => {
let err = errors::CVariadicNoExtern { span: variadic_param.span };
self.dcx().emit_err(err);
CVariadicStatus::NotSupported => {
// Some ABIs, e.g. `extern "Rust"`, never support c-variadic functions.
self.dcx().emit_err(errors::CVariadicBadNakedExtern {
span: dotdotdot_span,
abi: abi.as_str(),
extern_span: sig.extern_span(),
});
}
},
}
} else if !matches!(abi, ExternAbi::C { .. }) {
self.dcx().emit_err(errors::CVariadicBadExtern {
span: dotdotdot_span,
abi: abi.as_str(),
extern_span: sig.extern_span(),
});
}
}
@ -1478,7 +1535,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
visit::walk_param_bound(self, bound)
}
fn visit_fn(&mut self, fk: FnKind<'a>, _attrs: &AttrVec, span: Span, id: NodeId) {
fn visit_fn(&mut self, fk: FnKind<'a>, attrs: &AttrVec, span: Span, id: NodeId) {
// Only associated `fn`s can have `self` parameters.
let self_semantic = match fk.ctxt() {
Some(FnCtxt::Assoc(_)) => SelfSemantic::Yes,
@ -1497,7 +1554,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
self.check_extern_fn_signature(abi, ctxt, &fun.ident, &fun.sig);
}
self.check_c_variadic_type(fk);
self.check_c_variadic_type(fk, attrs);
// Functions cannot both be `const async` or `const gen`
if let Some(&FnHeader {

View file

@ -347,7 +347,18 @@ pub(crate) struct CVariadicMustBeUnsafe {
pub(crate) struct CVariadicBadExtern {
#[primary_span]
pub span: Span,
pub abi: Symbol,
pub abi: &'static str,
#[label]
pub extern_span: Span,
}
#[derive(Diagnostic)]
#[diag(ast_passes_c_variadic_bad_naked_extern)]
#[help]
pub(crate) struct CVariadicBadNakedExtern {
#[primary_span]
pub span: Span,
pub abi: &'static str,
#[label]
pub extern_span: Span,
}

View file

@ -410,6 +410,9 @@ declare_features! (
(unstable, avx10_target_feature, "1.88.0", Some(138843)),
/// Allows using C-variadics.
(unstable, c_variadic, "1.34.0", Some(44930)),
/// Allows defining c-variadic naked functions with any extern ABI that is allowed
/// on c-variadic foreign functions.
(unstable, c_variadic_naked_functions, "CURRENT_RUSTC_VERSION", Some(148767)),
/// Allows the use of `#[cfg(contract_checks)` to check if contract checks are enabled.
(unstable, cfg_contract_checks, "1.86.0", Some(128044)),
/// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour.

View file

@ -611,6 +611,7 @@ symbols! {
c_str_literals,
c_unwind,
c_variadic,
c_variadic_naked_functions,
c_void,
call,
call_mut,

View file

@ -0,0 +1,44 @@
//@ add-minicore
//@ compile-flags: --target x86_64-unknown-linux-gnu
//@ needs-llvm-components: x86
//@ ignore-backends: gcc
#![feature(no_core, lang_items, rustc_attrs)]
#![feature(c_variadic, c_variadic_naked_functions, abi_x86_interrupt, naked_functions_rustic_abi)]
#![crate_type = "rlib"]
#![no_core]
extern crate minicore;
use minicore::*;
#[repr(C)]
#[lang = "va_list"]
pub struct VaList;
#[unsafe(naked)]
unsafe extern "sysv64" fn c_variadic_sysv64(_: ...) {
naked_asm!("ret")
}
#[unsafe(naked)]
unsafe extern "C" fn c_variadic_c(_: ...) {
naked_asm!("ret")
}
#[unsafe(naked)]
unsafe extern "Rust" fn c_variadic_rust(_: ...) {
//~^ ERROR `...` is not supported for `extern "Rust"` naked functions
naked_asm!("ret")
}
#[unsafe(naked)]
unsafe extern "x86-interrupt" fn c_variadic_x86_interrupt(_: ...) {
//~^ ERROR `...` is not supported for `extern "x86-interrupt"` naked functions
naked_asm!("ret")
}
#[unsafe(naked)]
unsafe extern "nonsense" fn c_variadic_x86_nonsense(_: ...) {
//~^ ERROR invalid ABI: found `nonsense`
naked_asm!("ret")
}

View file

@ -0,0 +1,31 @@
error: `...` is not supported for `extern "Rust"` naked functions
--> $DIR/naked-invalid.rs:29:41
|
LL | unsafe extern "Rust" fn c_variadic_rust(_: ...) {
| ------------- ^^^^^^
| |
| `extern "Rust"` because of this
|
= help: C-variadic function must have a compatible calling convention
error: `...` is not supported for `extern "x86-interrupt"` naked functions
--> $DIR/naked-invalid.rs:35:59
|
LL | unsafe extern "x86-interrupt" fn c_variadic_x86_interrupt(_: ...) {
| ---------------------- ^^^^^^
| |
| `extern "x86-interrupt"` because of this
|
= help: C-variadic function must have a compatible calling convention
error[E0703]: invalid ABI: found `nonsense`
--> $DIR/naked-invalid.rs:41:15
|
LL | unsafe extern "nonsense" fn c_variadic_x86_nonsense(_: ...) {
| ^^^^^^^^^^ invalid ABI
|
= note: invoke `rustc --print=calling-conventions` for a full list of supported calling conventions
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0703`.

View file

@ -1,14 +1,14 @@
//@ run-pass
//@ only-x86_64
//@ only-linux
#![feature(c_variadic)]
#![feature(c_variadic, c_variadic_naked_functions)]
#[repr(C)]
#[derive(Debug, PartialEq)]
struct Data(i32, f64);
#[unsafe(naked)]
unsafe extern "C" fn c_variadic(_: ...) -> Data {
unsafe extern "sysv64" fn c_variadic_sysv64(_: ...) -> Data {
// This assembly was generated with GCC, because clang/LLVM is unable to
// optimize out the spilling of all registers to the stack.
core::arch::naked_asm!(
@ -32,9 +32,20 @@ unsafe extern "C" fn c_variadic(_: ...) -> Data {
)
}
#[unsafe(naked)]
unsafe extern "C" fn c_variadic_c(_: ...) -> Data {
core::arch::naked_asm!(
"jmp {}",
sym c_variadic_sysv64,
)
}
fn main() {
unsafe {
assert_eq!(c_variadic(1, 2.0), Data(1, 2.0));
assert_eq!(c_variadic(123, 4.56), Data(123, 4.56));
assert_eq!(c_variadic_sysv64(1, 2.0), Data(1, 2.0));
assert_eq!(c_variadic_sysv64(123, 4.56), Data(123, 4.56));
assert_eq!(c_variadic_c(1, 2.0), Data(1, 2.0));
assert_eq!(c_variadic_c(123, 4.56), Data(123, 4.56));
}
}

View file

@ -0,0 +1,27 @@
//@ add-minicore
//@ compile-flags: --target x86_64-unknown-linux-gnu
//@ needs-llvm-components: x86
//@ ignore-backends: gcc
#![feature(no_core, lang_items, rustc_attrs)]
#![feature(c_variadic, abi_x86_interrupt, naked_functions_rustic_abi)]
#![crate_type = "rlib"]
#![no_core]
extern crate minicore;
use minicore::*;
#[repr(C)]
#[lang = "va_list"]
pub struct VaList;
#[unsafe(naked)]
unsafe extern "sysv64" fn c_variadic_sysv64(_: ...) {
//~^ ERROR Naked c-variadic `extern "sysv64"` functions are unstable
naked_asm!("ret")
}
#[unsafe(naked)]
unsafe extern "C" fn c_variadic_c(_: ...) {
naked_asm!("ret")
}

View file

@ -0,0 +1,13 @@
error[E0658]: Naked c-variadic `extern "sysv64"` functions are unstable
--> $DIR/feature-gate-c_variadic-naked-functions.rs:19:1
|
LL | unsafe extern "sysv64" fn c_variadic_sysv64(_: ...) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #148767 <https://github.com/rust-lang/rust/issues/148767> for more information
= help: add `#![feature(c_variadic_naked_functions)]` 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 1 previous error
For more information about this error, try `rustc --explain E0658`.