Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
use rustc_abi::ExternAbi;
use rustc_hir::def::Res;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{self as hir, LangItem};
use rustc_middle::ty;
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::Span;

use crate::lints::{CVoidReturn, ExternCVoidReturn};
use crate::lints::{CVoidReference, CVoidReturn, ExternCVoidReturn};
use crate::{LateContext, LateLintPass, LintContext};

fn is_c_void(cx: &LateContext<'_>, ty: hir::Ty<'_>) -> bool {
if let hir::TyKind::Path(qpath) = ty.kind
&& let Res::Def(.., def_id) = cx.qpath_res(&qpath, ty.hir_id)
{
// need to look through type aliases (like `std::os::raw::c_void`)
let def_id = if DefKind::TyAlias == cx.tcx.def_kind(def_id)
&& let ty::Adt(adt_def, _) = cx.tcx.type_of(def_id).skip_binder().kind()
{
adt_def.did()
} else {
def_id
};
cx.tcx.is_lang_item(def_id, LangItem::CVoid)
} else {
false
}
}

declare_lint! {
/// The `c_void_returns` lint detects the use of [`core::ffi::c_void`] as a return type.
///
Expand Down Expand Up @@ -51,7 +70,7 @@ impl<'tcx> LateLintPass<'tcx> for CVoidReturns {
_: Span,
_: LocalDefId,
) {
check_decl(
check_decl_for_c_void_return(
cx,
decl,
!matches!(fn_kind, FnKind::ItemFn(.., hir::FnHeader { abi: ExternAbi::Rust, .. })),
Expand All @@ -60,22 +79,20 @@ impl<'tcx> LateLintPass<'tcx> for CVoidReturns {

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);
check_decl_for_c_void_return(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);
check_decl_for_c_void_return(cx, fn_ptr_ty.decl, fn_ptr_ty.abi != ExternAbi::Rust);
}
}
}

fn check_decl(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, is_extern: bool) {
fn check_decl_for_c_void_return(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)
&& is_c_void(cx, *output_ty)
{
let suggestion =
cx.sess().source_map().span_extend_to_prev_char(decl.output.span(), ')', true);
Expand All @@ -87,3 +104,44 @@ fn check_decl(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, is_extern: bool) {
}
}
}

declare_lint! {
/// The `c_void_references` lint detects the use of [`core::ffi::c_void`] as the referent of an `&` or `&mut` reference.
///
/// ### Example
///
/// ```rust
/// use std::ffi::c_void;
///
/// fn foo(v: &c_void) {
/// // ....
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// `c_void` is designed for use through a [`raw pointer`], equivalent to C's `void*` type.
/// However, for historical reasons, Rust considers it to have size 1, and so using it via
/// a Rust reference can easily cause Undefined Behavior.
///
/// [`core::ffi::c_void`]: https://doc.rust-lang.org/core/ffi/enum.c_void.html
/// [`raw pointer`]: https://doc.rust-lang.org/core/primitive.pointer.html
/// [`()`]: https://doc.rust-lang.org/core/primitive.unit.html
pub C_VOID_REFERENCES,
Warn,
"detects use of `c_void` as the referent of a Rust reference type"
}

declare_lint_pass!(CVoidReferences => [C_VOID_REFERENCES]);

impl<'tcx> LateLintPass<'tcx> for CVoidReferences {
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
if let hir::TyKind::Ref(_, hir::MutTy { ty: pointee_ty, .. }) = ty.kind
&& is_c_void(cx, *pointee_ty)
{
cx.emit_span_lint(C_VOID_REFERENCES, ty.span, CVoidReference);
}
}
}
5 changes: 3 additions & 2 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ mod async_closures;
mod async_fn_in_trait;
mod autorefs;
pub mod builtin;
mod c_void_returns;
mod c_void;
mod context;
mod dangling;
mod default_could_be_derived;
Expand Down Expand Up @@ -88,7 +88,7 @@ use async_closures::AsyncClosureUsage;
use async_fn_in_trait::AsyncFnInTrait;
use autorefs::*;
use builtin::*;
use c_void_returns::*;
use c_void::*;
use dangling::*;
use default_could_be_derived::DefaultCouldBeDerived;
use deref_into_dyn_supertrait::*;
Expand Down Expand Up @@ -273,6 +273,7 @@ late_lint_methods!(
InternalEqTraitMethodImpls: InternalEqTraitMethodImpls,
ImplicitProvenanceCasts: ImplicitProvenanceCasts,
CVoidReturns: CVoidReturns,
CVoidReferences: CVoidReferences,
]
]
);
Expand Down
13 changes: 11 additions & 2 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ pub(crate) enum BuiltinSpecialModuleNameUsed {
Main,
}

// c_void_return.rs
// c_void.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")]
Expand All @@ -625,7 +625,7 @@ pub(crate) struct CVoidReturn {
pub suggestion: Span,
}

// c_void_return.rs
// c_void.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")]
Expand All @@ -639,6 +639,15 @@ pub(crate) struct ExternCVoidReturn {
pub suggestion: Span,
}

// c_void.rs
#[derive(Diagnostic)]
#[diag("`c_void` should not be used as the referent of an `&` or `&mut` reference")]
#[help("use a raw pointer, or a reference to `()`, instead")]
#[note(
"for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior"
)]
pub(crate) struct CVoidReference;

// deref_into_dyn_supertrait.rs
#[derive(Diagnostic)]
#[diag("this `Deref` implementation is covered by an implicit supertrait coercion")]
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/native_lib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ pub fn build_libffi_closure<'tcx, 'this>(
/// As future improvement we might continue execution in the interpreter here.
unsafe extern "C" fn libffi_closure_callback<'tcx>(
_cif: &libffi::low::ffi_cif,
_result: &mut c_void,
_result: &mut (),

@RalfJung RalfJung Jul 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a raw pointer? Presumably it's mutating more than 0 bytes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type is forced on us by libffi.
Also we're not doing anything with this reference.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing how libffi itself forces &mut c_void? If I look at the repo, it only uses *mut c_void.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It forces &mut <something>.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense.

_args: *const *const c_void,
data: &LibffiClosureData<'tcx>,
) {
Expand Down
61 changes: 61 additions & 0 deletions tests/ui/lint/c-void-references.alias.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:17:13
|
LL | fn bar() -> &'static mut c_void {
| ^^^^^^^^^^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior
note: the lint level is defined here
--> $DIR/c-void-references.rs:4:9
|
LL | #![deny(c_void_references)]
| ^^^^^^^^^^^^^^^^^

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:22:20
|
LL | fn baz() -> Option<&'static c_void> {
| ^^^^^^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:27:12
|
LL | fn quux(_: &c_void) {}
| ^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:29:16
|
LL | type Boo<'a> = &'a c_void;
| ^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:32:12
|
LL | let _: &'static c_void = panic!();
| ^^^^^^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:38:20
|
LL | impl<'a> Trait for &'a c_void {}
| ^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: aborting due to 6 previous errors

61 changes: 61 additions & 0 deletions tests/ui/lint/c-void-references.direct.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:17:13
|
LL | fn bar() -> &'static mut c_void {
| ^^^^^^^^^^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior
note: the lint level is defined here
--> $DIR/c-void-references.rs:4:9
|
LL | #![deny(c_void_references)]
| ^^^^^^^^^^^^^^^^^

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:22:20
|
LL | fn baz() -> Option<&'static c_void> {
| ^^^^^^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:27:12
|
LL | fn quux(_: &c_void) {}
| ^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:29:16
|
LL | type Boo<'a> = &'a c_void;
| ^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:32:12
|
LL | let _: &'static c_void = panic!();
| ^^^^^^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: `c_void` should not be used as the referent of an `&` or `&mut` reference
--> $DIR/c-void-references.rs:38:20
|
LL | impl<'a> Trait for &'a c_void {}
| ^^^^^^^^^^
|
= help: use a raw pointer, or a reference to `()`, instead
= note: for legacy reasons, Rust considers `c_void` to have size 1, so references to it can cause Undefined Behavior

error: aborting due to 6 previous errors

39 changes: 39 additions & 0 deletions tests/ui/lint/c-void-references.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//@ revisions: direct alias

#![allow(unused)]
#![deny(c_void_references)]

#[cfg(direct)]
use std::ffi::c_void;
#[cfg(alias)]
#[expect(non_camel_case_types)]
type c_void = std::ffi::c_void;

fn foo() -> *mut c_void {
// fine
std::ptr::null_mut()
}

fn bar() -> &'static mut c_void {
//~^ ERROR c_void
panic!();
}

fn baz() -> Option<&'static c_void> {
//~^ ERROR c_void
None
}

fn quux(_: &c_void) {} //~ ERROR c_void

type Boo<'a> = &'a c_void;
//~^ ERROR c_void
fn main() {
let _: &'static c_void = panic!();
//~^ ERROR c_void
}

trait Trait {}

impl<'a> Trait for &'a c_void {}
//~^ ERROR c_void
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: `c_void` should not be used as a return type
--> $DIR/c-void-returns.rs:7:13
--> $DIR/c-void-returns.rs:14:13
|
LL | fn foo() -> c_void {
| ----^^^^^^
Expand All @@ -8,13 +8,13 @@ LL | fn foo() -> c_void {
|
= 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
--> $DIR/c-void-returns.rs:4: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
--> $DIR/c-void-returns.rs:24:17
|
LL | fn baz() -> c_void;
| ----^^^^^^
Expand All @@ -25,7 +25,7 @@ LL | fn baz() -> c_void;
= 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
--> $DIR/c-void-returns.rs:28:22
|
LL | type Xyzzy = fn() -> c_void;
| ----^^^^^^
Expand Down
Loading
Loading