From fa2ffcfb8edb7750be1f2f4ac8e3c900183d3570 Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Wed, 3 Jun 2026 07:48:15 +0700 Subject: [PATCH 1/4] Add `must_use` to `Box::as_{ptr,mut_ptr}` --- library/alloc/src/boxed.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 3087a7d2004bc..1237f5da2c532 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -1776,6 +1776,7 @@ impl Box { /// /// [`as_mut_ptr`]: Self::as_mut_ptr /// [`as_ptr`]: Self::as_ptr + #[must_use] #[stable(feature = "box_as_ptr", since = "CURRENT_RUSTC_VERSION")] #[rustc_never_returns_null_ptr] #[rustc_as_ptr] @@ -1823,6 +1824,7 @@ impl Box { /// /// [`as_mut_ptr`]: Self::as_mut_ptr /// [`as_ptr`]: Self::as_ptr + #[must_use] #[stable(feature = "box_as_ptr", since = "CURRENT_RUSTC_VERSION")] #[rustc_never_returns_null_ptr] #[rustc_as_ptr] From 5a398727fd6ad0be0609a0694382b4c2a9cec8ba Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Wed, 3 Jun 2026 07:55:56 +0700 Subject: [PATCH 2/4] Implement `Box::as_non_null()`. ACP: Tracking issue: The docs are mostly copied from `Box::as_mut_ptr()` --- library/alloc/src/boxed.rs | 48 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 1237f5da2c532..40e0d8cacfff1 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -1753,7 +1753,7 @@ impl Box { /// /// This method guarantees that for the purpose of the aliasing model, this method /// does not materialize a reference to the underlying memory, and thus the returned pointer - /// will remain valid when mixed with other calls to [`as_ptr`] and [`as_mut_ptr`]. + /// will remain valid when mixed with other calls to [`as_ptr`], [`as_mut_ptr`], and [`as_non_null`]. /// Note that calling other methods that materialize references to the memory /// may still invalidate this pointer. /// See the example below for how this guarantee can be used. @@ -1776,6 +1776,7 @@ impl Box { /// /// [`as_mut_ptr`]: Self::as_mut_ptr /// [`as_ptr`]: Self::as_ptr + /// [`as_non_null`]: Self::as_non_null #[must_use] #[stable(feature = "box_as_ptr", since = "CURRENT_RUSTC_VERSION")] #[rustc_never_returns_null_ptr] @@ -1798,7 +1799,7 @@ impl Box { /// /// This method guarantees that for the purpose of the aliasing model, this method /// does not materialize a reference to the underlying memory, and thus the returned pointer - /// will remain valid when mixed with other calls to [`as_ptr`] and [`as_mut_ptr`]. + /// will remain valid when mixed with other calls to [`as_ptr`], [`as_mut_ptr`], and [`as_non_null`]. /// Note that calling other methods that materialize mutable references to the memory, /// as well as writing to this memory, may still invalidate this pointer. /// See the example below for how this guarantee can be used. @@ -1824,6 +1825,7 @@ impl Box { /// /// [`as_mut_ptr`]: Self::as_mut_ptr /// [`as_ptr`]: Self::as_ptr + /// [`as_non_null`]: Self::as_non_null #[must_use] #[stable(feature = "box_as_ptr", since = "CURRENT_RUSTC_VERSION")] #[rustc_never_returns_null_ptr] @@ -1835,6 +1837,48 @@ impl Box { &raw const **b } + /// Returns a `NonNull` pointer to the `Box`'s contents. + /// + /// The caller must ensure that the `Box` outlives the pointer this + /// function returns, or else it will end up dangling. + /// + /// This method guarantees that for the purpose of the aliasing model, this method + /// does not materialize a reference to the underlying memory, and thus the returned pointer + /// will remain valid when mixed with other calls to [`as_ptr`], [`as_mut_ptr`], and [`as_non_null`]. + /// Note that calling other methods that materialize references to the memory + /// may still invalidate this pointer. + /// See the example below for how this guarantee can be used. + /// + /// # Examples + /// + /// Due to the aliasing guarantee, the following code is legal: + /// + /// ```rust + /// #![feature(box_as_non_null)] + /// + /// unsafe { + /// let mut b = Box::new(0); + /// let ptr1 = Box::as_non_null(&mut b); + /// ptr1.write(1); + /// let ptr2 = Box::as_non_null(&mut b); + /// ptr2.write(2); + /// // Notably, the write to `ptr2` did *not* invalidate `ptr1`: + /// ptr1.write(3); + /// } + /// ``` + /// + /// [`as_mut_ptr`]: Self::as_mut_ptr + /// [`as_ptr`]: Self::as_ptr + /// [`as_non_null`]: Self::as_non_null + #[must_use] + #[unstable(feature = "box_as_non_null", issue = "157345")] + #[rustc_as_ptr] + #[inline] + pub fn as_non_null(b: &mut Self) -> NonNull { + // SAFETY: `Box` is guaranteed to be non-null. + unsafe { NonNull::new_unchecked(Self::as_mut_ptr(b)) } + } + /// Returns a reference to the underlying allocator. /// /// Note: this is an associated function, which means that you have From b268dee060cac2af00a11058e79a980ccd6f2d9d Mon Sep 17 00:00:00 2001 From: Andy Russell Date: Sat, 9 May 2026 22:15:58 -0400 Subject: [PATCH 3/4] lint on `core::ffi::c_void` as a return type --- compiler/rustc_lint/src/c_void_returns.rs | 89 +++++++++++++++++++++++ compiler/rustc_lint/src/lib.rs | 3 + compiler/rustc_lint/src/lints.rs | 27 +++++++ tests/ui/lint/c-void-returns.rs | 22 ++++++ tests/ui/lint/c-void-returns.stderr | 38 ++++++++++ 5 files changed, 179 insertions(+) create mode 100644 compiler/rustc_lint/src/c_void_returns.rs create mode 100644 tests/ui/lint/c-void-returns.rs create mode 100644 tests/ui/lint/c-void-returns.stderr diff --git a/compiler/rustc_lint/src/c_void_returns.rs b/compiler/rustc_lint/src/c_void_returns.rs new file mode 100644 index 0000000000000..f4dd260dd5ae4 --- /dev/null +++ b/compiler/rustc_lint/src/c_void_returns.rs @@ -0,0 +1,89 @@ +use rustc_abi::ExternAbi; +use rustc_hir::def::Res; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{self as hir, LangItem}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::Span; + +use crate::lints::{CVoidReturn, ExternCVoidReturn}; +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `c_void_returns` lint detects the use of [`core::ffi::c_void`] as a return type. + /// + /// ### Example + /// + /// ```rust + /// use std::ffi::c_void; + /// + /// unsafe extern "C" { + /// fn foo() -> c_void; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// `c_void` is designed for use through a [`pointer`], equivalent to C's `void*` type. It is a + /// mistake to use it directly as a return type, and calling `extern` functions declared as such + /// may result in undefined behavior. C functions that return `void` must be declared to return + /// [`()`] in Rust (omitting the return type implicitly returns `()`). + /// + /// [`core::ffi::c_void`]: https://doc.rust-lang.org/core/ffi/enum.c_void.html + /// [`pointer`]: https://doc.rust-lang.org/core/primitive.pointer.html + /// [`()`]: https://doc.rust-lang.org/core/primitive.unit.html + pub C_VOID_RETURNS, + Warn, + "detects use of `c_void` as a return type" +} + +declare_lint_pass!(CVoidReturns => [C_VOID_RETURNS]); + +impl<'tcx> LateLintPass<'tcx> for CVoidReturns { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + fn_kind: FnKind<'tcx>, + decl: &'tcx hir::FnDecl<'tcx>, + _: &'tcx hir::Body<'tcx>, + _: Span, + _: LocalDefId, + ) { + check_decl( + cx, + decl, + !matches!(fn_kind, FnKind::ItemFn(.., hir::FnHeader { abi: ExternAbi::Rust, .. })), + ); + } + + fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ForeignItem<'tcx>) { + if let hir::ForeignItemKind::Fn(sig, ..) = item.kind { + check_decl(cx, sig.decl, true); + } + } + + fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { + if let hir::TyKind::FnPtr(fn_ptr_ty) = ty.kind { + check_decl(cx, fn_ptr_ty.decl, fn_ptr_ty.abi != ExternAbi::Rust); + } + } +} + +fn check_decl(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, is_extern: bool) { + if let hir::FnRetTy::Return(output_ty) = decl.output + && let hir::TyKind::Path(qpath) = output_ty.kind + && let Res::Def(.., def_id) = cx.qpath_res(&qpath, output_ty.hir_id) + && cx.tcx.is_lang_item(def_id, LangItem::CVoid) + { + let suggestion = + cx.sess().source_map().span_extend_to_prev_char(decl.output.span(), ')', true); + + if is_extern { + cx.emit_span_lint(C_VOID_RETURNS, decl.output.span(), ExternCVoidReturn { suggestion }); + } else { + cx.emit_span_lint(C_VOID_RETURNS, decl.output.span(), CVoidReturn { suggestion }); + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 74bee7d705e3a..0e96b9f118790 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -32,6 +32,7 @@ mod async_closures; mod async_fn_in_trait; mod autorefs; pub mod builtin; +mod c_void_returns; mod context; mod dangling; mod default_could_be_derived; @@ -86,6 +87,7 @@ use async_closures::AsyncClosureUsage; use async_fn_in_trait::AsyncFnInTrait; use autorefs::*; use builtin::*; +use c_void_returns::*; use dangling::*; use default_could_be_derived::DefaultCouldBeDerived; use deref_into_dyn_supertrait::*; @@ -269,6 +271,7 @@ late_lint_methods!( LifetimeSyntax: LifetimeSyntax, InternalEqTraitMethodImpls: InternalEqTraitMethodImpls, ImplicitProvenanceCasts: ImplicitProvenanceCasts, + CVoidReturns: CVoidReturns, ] ] ); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 83827c57aef00..eb82afab13186 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -614,6 +614,33 @@ pub(crate) enum BuiltinSpecialModuleNameUsed { Main, } +// c_void_return.rs +#[derive(Diagnostic)] +#[diag("`c_void` should not be used as a return type")] +#[help("returning `()` in Rust is equivalent to returning `void` in C")] +pub(crate) struct CVoidReturn { + #[suggestion( + "remove the return type to implicitly return `()`", + code = "", + applicability = "maybe-incorrect" + )] + pub suggestion: Span, +} + +// c_void_return.rs +#[derive(Diagnostic)] +#[diag("declarations returning `c_void` are not compatible with C functions returning `void`")] +#[help("returning `()` in Rust is equivalent to returning `void` in C")] +#[note("`c_void` is only used through raw pointers for compatibility with `void` pointers")] +pub(crate) struct ExternCVoidReturn { + #[suggestion( + "remove the return type to implicitly return `()`", + code = "", + applicability = "maybe-incorrect" + )] + pub suggestion: Span, +} + // deref_into_dyn_supertrait.rs #[derive(Diagnostic)] #[diag("this `Deref` implementation is covered by an implicit supertrait coercion")] diff --git a/tests/ui/lint/c-void-returns.rs b/tests/ui/lint/c-void-returns.rs new file mode 100644 index 0000000000000..7531da3d72f41 --- /dev/null +++ b/tests/ui/lint/c-void-returns.rs @@ -0,0 +1,22 @@ +#![allow(unused)] +#![deny(c_void_returns)] + +use std::ffi::c_void; +use std::ptr; + +fn foo() -> c_void { //~ ERROR c_void + unreachable!() +} + +fn bar() -> *mut c_void { + ptr::null_mut() +} + +unsafe extern "C" { + fn baz() -> c_void; //~ ERROR c_void + fn quux() -> *const c_void; +} + +type Xyzzy = fn() -> c_void; //~ ERROR c_void + +fn main() {} diff --git a/tests/ui/lint/c-void-returns.stderr b/tests/ui/lint/c-void-returns.stderr new file mode 100644 index 0000000000000..7dc724955ecb4 --- /dev/null +++ b/tests/ui/lint/c-void-returns.stderr @@ -0,0 +1,38 @@ +error: `c_void` should not be used as a return type + --> $DIR/c-void-returns.rs:7:13 + | +LL | fn foo() -> c_void { + | ----^^^^^^ + | | + | help: remove the return type to implicitly return `()` + | + = help: returning `()` in Rust is equivalent to returning `void` in C +note: the lint level is defined here + --> $DIR/c-void-returns.rs:2:9 + | +LL | #![deny(c_void_returns)] + | ^^^^^^^^^^^^^^ + +error: declarations returning `c_void` are not compatible with C functions returning `void` + --> $DIR/c-void-returns.rs:16:17 + | +LL | fn baz() -> c_void; + | ----^^^^^^ + | | + | help: remove the return type to implicitly return `()` + | + = help: returning `()` in Rust is equivalent to returning `void` in C + = note: `c_void` is only used through raw pointers for compatibility with `void` pointers + +error: `c_void` should not be used as a return type + --> $DIR/c-void-returns.rs:20:22 + | +LL | type Xyzzy = fn() -> c_void; + | ----^^^^^^ + | | + | help: remove the return type to implicitly return `()` + | + = help: returning `()` in Rust is equivalent to returning `void` in C + +error: aborting due to 3 previous errors + From 10e0c842120c660703a82fe56754d759e0084f51 Mon Sep 17 00:00:00 2001 From: Rachel Barker Date: Tue, 30 Jun 2026 14:13:22 +0100 Subject: [PATCH 4/4] Fix error message when rejecting implicit stage != 2 in CI The previous error message made it sound like stage != 2 was entirely disallowed under CI. But actually it was only *implicit* stage != 2 which is disallowed - an explicit `--stage 1` is permitted and is sometimes correct. Minimal command to trigger the error message: `GITHUB_ACTIONS=true ./x test ui` --- src/bootstrap/src/core/config/config.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 5a9c7264c006f..1db44c2c8b39c 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -1206,7 +1206,9 @@ impl Config { | Subcommand::Install => { assert_eq!( stage, 2, - "x.py should be run with `--stage 2` on CI, but was run with `--stage {stage}`", + "\ +x.py was run under CI with an implicit `--stage {stage}`. This is probably wrong and you want stage 2. +NOTE: Please add `--stage 2` to your command line, or if you're sure you want to run stage {stage} then add `--stage {stage}` explicitly" ); } Subcommand::Clean { .. }