about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFolkert de Vries <folkert@folkertdev.nl>2025-09-08 18:45:41 +0200
committerFolkert de Vries <folkert@folkertdev.nl>2025-09-09 21:30:38 +0200
commit0c96200f268df10d4f3ac102f3161c67e5e67221 (patch)
treebfb8cc16075c6a8f1e8a0e53368fae4d2b89c4bb
parent5de9bc73e76f60ee27c37f20d0a9530e5a957df7 (diff)
downloadrust-0c96200f268df10d4f3ac102f3161c67e5e67221.tar.gz
rust-0c96200f268df10d4f3ac102f3161c67e5e67221.zip
c-variadic: reject non-unsafe functions
-rw-r--r--compiler/rustc_ast/src/ast.rs23
-rw-r--r--compiler/rustc_ast_passes/messages.ftl4
-rw-r--r--compiler/rustc_ast_passes/src/ast_validation.rs23
-rw-r--r--compiler/rustc_ast_passes/src/errors.rs15
-rw-r--r--tests/ui/parser/variadic-ffi-semantic-restrictions.rs12
-rw-r--r--tests/ui/parser/variadic-ffi-semantic-restrictions.stderr42
6 files changed, 98 insertions, 21 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 2733a2603d6..915bae9e5f5 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -2309,6 +2309,22 @@ impl FnSig {
 
         self.span.shrink_to_lo()
     }
+
+    /// The span of the header's safety, or where to insert it if empty.
+    pub fn safety_span(&self) -> Span {
+        match self.header.safety {
+            Safety::Unsafe(span) | Safety::Safe(span) => span,
+            Safety::Default => {
+                // Insert after the `coroutine_kind` if available.
+                if let Some(extern_span) = self.header.ext.span() {
+                    return extern_span.shrink_to_lo();
+                }
+
+                // Insert right at the front of the signature.
+                self.header_span().shrink_to_hi()
+            }
+        }
+    }
 }
 
 /// A constraint on an associated item.
@@ -3553,6 +3569,13 @@ impl Extern {
             None => Extern::Implicit(span),
         }
     }
+
+    pub fn span(self) -> Option<Span> {
+        match self {
+            Extern::None => None,
+            Extern::Implicit(span) | Extern::Explicit(_, span) => Some(span),
+        }
+    }
 }
 
 /// A function header.
diff --git a/compiler/rustc_ast_passes/messages.ftl b/compiler/rustc_ast_passes/messages.ftl
index 958797a6089..1aa38a1beda 100644
--- a/compiler/rustc_ast_passes/messages.ftl
+++ b/compiler/rustc_ast_passes/messages.ftl
@@ -68,6 +68,10 @@ 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_must_be_unsafe =
+    functions with a C variable argument list must be unsafe
+    .suggestion = add the `unsafe` keyword to this definition
+
 ast_passes_c_variadic_no_extern = `...` is not supported for non-extern functions
 
 ast_passes_const_and_c_variadic = functions cannot be both `const` and C-variadic
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index f425acda358..7b32778ddf0 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -698,20 +698,25 @@ impl<'a> AstValidator<'a> {
             FnCtxt::Foreign => return,
             FnCtxt::Free => match sig.header.ext {
                 Extern::Implicit(_) => {
-                    if matches!(sig.header.safety, Safety::Unsafe(_)) {
-                        return;
+                    // Implicitly defaults to C.
+                    if !matches!(sig.header.safety, Safety::Unsafe(_)) {
+                        self.dcx().emit_err(errors::CVariadicMustBeUnsafe {
+                            span: variadic_param.span,
+                            unsafe_span: sig.safety_span(),
+                        });
                     }
-
-                    self.dcx().emit_err(errors::BadCVariadic { span: variadic_param.span });
                 }
                 Extern::Explicit(StrLit { symbol_unescaped, .. }, _) => {
-                    if matches!(symbol_unescaped, sym::C | sym::C_dash_unwind) {
-                        if matches!(sig.header.safety, Safety::Unsafe(_)) {
-                            return;
-                        }
+                    if !matches!(symbol_unescaped, sym::C | sym::C_dash_unwind) {
+                        self.dcx().emit_err(errors::BadCVariadic { span: variadic_param.span });
                     }
 
-                    self.dcx().emit_err(errors::BadCVariadic { span: variadic_param.span });
+                    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 };
diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs
index 6b57e5ef376..946a22d2e75 100644
--- a/compiler/rustc_ast_passes/src/errors.rs
+++ b/compiler/rustc_ast_passes/src/errors.rs
@@ -333,6 +333,21 @@ pub(crate) struct CVariadicNoExtern {
 }
 
 #[derive(Diagnostic)]
+#[diag(ast_passes_c_variadic_must_be_unsafe)]
+pub(crate) struct CVariadicMustBeUnsafe {
+    #[primary_span]
+    pub span: Span,
+
+    #[suggestion(
+        ast_passes_suggestion,
+        applicability = "maybe-incorrect",
+        code = "unsafe ",
+        style = "verbose"
+    )]
+    pub unsafe_span: Span,
+}
+
+#[derive(Diagnostic)]
 #[diag(ast_passes_bad_c_variadic)]
 pub(crate) struct BadCVariadic {
     #[primary_span]
diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs
index fe5421ab2c0..ff491322efc 100644
--- a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs
+++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs
@@ -10,19 +10,19 @@ fn f1_2(...) {}
 //~^ ERROR `...` is not supported for non-extern functions
 
 extern "C" fn f2_1(x: isize, ...) {}
-//~^ ERROR defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+//~^ ERROR functions with a C variable argument list must be unsafe
 
 extern "C" fn f2_2(...) {}
-//~^ ERROR defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+//~^ ERROR functions with a C variable argument list must be unsafe
 
 extern "C" fn f2_3(..., x: isize) {}
 //~^ ERROR `...` must be the last argument of a C-variadic function
 
 extern "C" fn f3_1(x: isize, ...) {}
-//~^ ERROR defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+//~^ ERROR functions with a C variable argument list must be unsafe
 
 extern "C" fn f3_2(...) {}
-//~^ ERROR defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+//~^ ERROR functions with a C variable argument list must be unsafe
 
 extern "C" fn f3_3(..., x: isize) {}
 //~^ ERROR `...` must be the last argument of a C-variadic function
@@ -33,12 +33,12 @@ const unsafe extern "C" fn f4_1(x: isize, ...) {}
 
 const extern "C" fn f4_2(x: isize, ...) {}
 //~^ ERROR functions cannot be both `const` and C-variadic
-//~| ERROR defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+//~| ERROR functions with a C variable argument list must be unsafe
 //~| ERROR destructor of `VaListImpl<'_>` cannot be evaluated at compile-time
 
 const extern "C" fn f4_3(..., x: isize, ...) {}
 //~^ ERROR functions cannot be both `const` and C-variadic
-//~| ERROR defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+//~| ERROR functions with a C variable argument list must be unsafe
 //~| ERROR `...` must be the last argument of a C-variadic function
 
 extern "C" {
diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr
index 0abe1526405..98f1d79f8f4 100644
--- a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr
+++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr
@@ -10,17 +10,27 @@ error: `...` is not supported for non-extern functions
 LL | fn f1_2(...) {}
    |         ^^^
 
-error: defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+error: functions with a C variable argument list must be unsafe
   --> $DIR/variadic-ffi-semantic-restrictions.rs:12:30
    |
 LL | extern "C" fn f2_1(x: isize, ...) {}
    |                              ^^^
+   |
+help: add the `unsafe` keyword to this definition
+   |
+LL | unsafe extern "C" fn f2_1(x: isize, ...) {}
+   | ++++++
 
-error: defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+error: functions with a C variable argument list must be unsafe
   --> $DIR/variadic-ffi-semantic-restrictions.rs:15:20
    |
 LL | extern "C" fn f2_2(...) {}
    |                    ^^^
+   |
+help: add the `unsafe` keyword to this definition
+   |
+LL | unsafe extern "C" fn f2_2(...) {}
+   | ++++++
 
 error: `...` must be the last argument of a C-variadic function
   --> $DIR/variadic-ffi-semantic-restrictions.rs:18:20
@@ -28,17 +38,27 @@ error: `...` must be the last argument of a C-variadic function
 LL | extern "C" fn f2_3(..., x: isize) {}
    |                    ^^^
 
-error: defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+error: functions with a C variable argument list must be unsafe
   --> $DIR/variadic-ffi-semantic-restrictions.rs:21:30
    |
 LL | extern "C" fn f3_1(x: isize, ...) {}
    |                              ^^^
+   |
+help: add the `unsafe` keyword to this definition
+   |
+LL | unsafe extern "C" fn f3_1(x: isize, ...) {}
+   | ++++++
 
-error: defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+error: functions with a C variable argument list must be unsafe
   --> $DIR/variadic-ffi-semantic-restrictions.rs:24:20
    |
 LL | extern "C" fn f3_2(...) {}
    |                    ^^^
+   |
+help: add the `unsafe` keyword to this definition
+   |
+LL | unsafe extern "C" fn f3_2(...) {}
+   | ++++++
 
 error: `...` must be the last argument of a C-variadic function
   --> $DIR/variadic-ffi-semantic-restrictions.rs:27:20
@@ -58,11 +78,16 @@ error: functions cannot be both `const` and C-variadic
 LL | const extern "C" fn f4_2(x: isize, ...) {}
    | ^^^^^ `const` because of this      ^^^ C-variadic because of this
 
-error: defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+error: functions with a C variable argument list must be unsafe
   --> $DIR/variadic-ffi-semantic-restrictions.rs:34:36
    |
 LL | const extern "C" fn f4_2(x: isize, ...) {}
    |                                    ^^^
+   |
+help: add the `unsafe` keyword to this definition
+   |
+LL | const unsafe extern "C" fn f4_2(x: isize, ...) {}
+   |       ++++++
 
 error: `...` must be the last argument of a C-variadic function
   --> $DIR/variadic-ffi-semantic-restrictions.rs:39:26
@@ -76,11 +101,16 @@ error: functions cannot be both `const` and C-variadic
 LL | const extern "C" fn f4_3(..., x: isize, ...) {}
    | ^^^^^ `const` because of this           ^^^ C-variadic because of this
 
-error: defining functions with C-variadic arguments is only allowed for free functions with the "C" or "C-unwind" calling convention
+error: functions with a C variable argument list must be unsafe
   --> $DIR/variadic-ffi-semantic-restrictions.rs:39:41
    |
 LL | const extern "C" fn f4_3(..., x: isize, ...) {}
    |                                         ^^^
+   |
+help: add the `unsafe` keyword to this definition
+   |
+LL | const unsafe extern "C" fn f4_3(..., x: isize, ...) {}
+   |       ++++++
 
 error: `...` must be the last argument of a C-variadic function
   --> $DIR/variadic-ffi-semantic-restrictions.rs:45:13