Skip to content

Commit 0c72496

Browse files
Rollup merge of #156379 - euclio:c-void-returns, r=mati865
lint on `core::ffi::c_void` as a return type Fixes #100972. This PR introduces a new ~deny-by-default~ warn-by-default lint `c_void_returns` that fires on usage of `core::ffi::c_void` as a return type. This is never correct, and is a potential stumbling block for users coming from C.
2 parents f46ec52 + b268dee commit 0c72496

5 files changed

Lines changed: 179 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use rustc_abi::ExternAbi;
2+
use rustc_hir::def::Res;
3+
use rustc_hir::def_id::LocalDefId;
4+
use rustc_hir::intravisit::FnKind;
5+
use rustc_hir::{self as hir, LangItem};
6+
use rustc_session::{declare_lint, declare_lint_pass};
7+
use rustc_span::Span;
8+
9+
use crate::lints::{CVoidReturn, ExternCVoidReturn};
10+
use crate::{LateContext, LateLintPass, LintContext};
11+
12+
declare_lint! {
13+
/// The `c_void_returns` lint detects the use of [`core::ffi::c_void`] as a return type.
14+
///
15+
/// ### Example
16+
///
17+
/// ```rust
18+
/// use std::ffi::c_void;
19+
///
20+
/// unsafe extern "C" {
21+
/// fn foo() -> c_void;
22+
/// }
23+
/// ```
24+
///
25+
/// {{produces}}
26+
///
27+
/// ### Explanation
28+
///
29+
/// `c_void` is designed for use through a [`pointer`], equivalent to C's `void*` type. It is a
30+
/// mistake to use it directly as a return type, and calling `extern` functions declared as such
31+
/// may result in undefined behavior. C functions that return `void` must be declared to return
32+
/// [`()`] in Rust (omitting the return type implicitly returns `()`).
33+
///
34+
/// [`core::ffi::c_void`]: https://doc.rust-lang.org/core/ffi/enum.c_void.html
35+
/// [`pointer`]: https://doc.rust-lang.org/core/primitive.pointer.html
36+
/// [`()`]: https://doc.rust-lang.org/core/primitive.unit.html
37+
pub C_VOID_RETURNS,
38+
Warn,
39+
"detects use of `c_void` as a return type"
40+
}
41+
42+
declare_lint_pass!(CVoidReturns => [C_VOID_RETURNS]);
43+
44+
impl<'tcx> LateLintPass<'tcx> for CVoidReturns {
45+
fn check_fn(
46+
&mut self,
47+
cx: &LateContext<'tcx>,
48+
fn_kind: FnKind<'tcx>,
49+
decl: &'tcx hir::FnDecl<'tcx>,
50+
_: &'tcx hir::Body<'tcx>,
51+
_: Span,
52+
_: LocalDefId,
53+
) {
54+
check_decl(
55+
cx,
56+
decl,
57+
!matches!(fn_kind, FnKind::ItemFn(.., hir::FnHeader { abi: ExternAbi::Rust, .. })),
58+
);
59+
}
60+
61+
fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ForeignItem<'tcx>) {
62+
if let hir::ForeignItemKind::Fn(sig, ..) = item.kind {
63+
check_decl(cx, sig.decl, true);
64+
}
65+
}
66+
67+
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
68+
if let hir::TyKind::FnPtr(fn_ptr_ty) = ty.kind {
69+
check_decl(cx, fn_ptr_ty.decl, fn_ptr_ty.abi != ExternAbi::Rust);
70+
}
71+
}
72+
}
73+
74+
fn check_decl(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, is_extern: bool) {
75+
if let hir::FnRetTy::Return(output_ty) = decl.output
76+
&& let hir::TyKind::Path(qpath) = output_ty.kind
77+
&& let Res::Def(.., def_id) = cx.qpath_res(&qpath, output_ty.hir_id)
78+
&& cx.tcx.is_lang_item(def_id, LangItem::CVoid)
79+
{
80+
let suggestion =
81+
cx.sess().source_map().span_extend_to_prev_char(decl.output.span(), ')', true);
82+
83+
if is_extern {
84+
cx.emit_span_lint(C_VOID_RETURNS, decl.output.span(), ExternCVoidReturn { suggestion });
85+
} else {
86+
cx.emit_span_lint(C_VOID_RETURNS, decl.output.span(), CVoidReturn { suggestion });
87+
}
88+
}
89+
}

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mod async_closures;
3232
mod async_fn_in_trait;
3333
mod autorefs;
3434
pub mod builtin;
35+
mod c_void_returns;
3536
mod context;
3637
mod dangling;
3738
mod default_could_be_derived;
@@ -86,6 +87,7 @@ use async_closures::AsyncClosureUsage;
8687
use async_fn_in_trait::AsyncFnInTrait;
8788
use autorefs::*;
8889
use builtin::*;
90+
use c_void_returns::*;
8991
use dangling::*;
9092
use default_could_be_derived::DefaultCouldBeDerived;
9193
use deref_into_dyn_supertrait::*;
@@ -269,6 +271,7 @@ late_lint_methods!(
269271
LifetimeSyntax: LifetimeSyntax,
270272
InternalEqTraitMethodImpls: InternalEqTraitMethodImpls,
271273
ImplicitProvenanceCasts: ImplicitProvenanceCasts,
274+
CVoidReturns: CVoidReturns,
272275
]
273276
]
274277
);

compiler/rustc_lint/src/lints.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,33 @@ pub(crate) enum BuiltinSpecialModuleNameUsed {
614614
Main,
615615
}
616616

617+
// c_void_return.rs
618+
#[derive(Diagnostic)]
619+
#[diag("`c_void` should not be used as a return type")]
620+
#[help("returning `()` in Rust is equivalent to returning `void` in C")]
621+
pub(crate) struct CVoidReturn {
622+
#[suggestion(
623+
"remove the return type to implicitly return `()`",
624+
code = "",
625+
applicability = "maybe-incorrect"
626+
)]
627+
pub suggestion: Span,
628+
}
629+
630+
// c_void_return.rs
631+
#[derive(Diagnostic)]
632+
#[diag("declarations returning `c_void` are not compatible with C functions returning `void`")]
633+
#[help("returning `()` in Rust is equivalent to returning `void` in C")]
634+
#[note("`c_void` is only used through raw pointers for compatibility with `void` pointers")]
635+
pub(crate) struct ExternCVoidReturn {
636+
#[suggestion(
637+
"remove the return type to implicitly return `()`",
638+
code = "",
639+
applicability = "maybe-incorrect"
640+
)]
641+
pub suggestion: Span,
642+
}
643+
617644
// deref_into_dyn_supertrait.rs
618645
#[derive(Diagnostic)]
619646
#[diag("this `Deref` implementation is covered by an implicit supertrait coercion")]

tests/ui/lint/c-void-returns.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![allow(unused)]
2+
#![deny(c_void_returns)]
3+
4+
use std::ffi::c_void;
5+
use std::ptr;
6+
7+
fn foo() -> c_void { //~ ERROR c_void
8+
unreachable!()
9+
}
10+
11+
fn bar() -> *mut c_void {
12+
ptr::null_mut()
13+
}
14+
15+
unsafe extern "C" {
16+
fn baz() -> c_void; //~ ERROR c_void
17+
fn quux() -> *const c_void;
18+
}
19+
20+
type Xyzzy = fn() -> c_void; //~ ERROR c_void
21+
22+
fn main() {}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error: `c_void` should not be used as a return type
2+
--> $DIR/c-void-returns.rs:7:13
3+
|
4+
LL | fn foo() -> c_void {
5+
| ----^^^^^^
6+
| |
7+
| help: remove the return type to implicitly return `()`
8+
|
9+
= help: returning `()` in Rust is equivalent to returning `void` in C
10+
note: the lint level is defined here
11+
--> $DIR/c-void-returns.rs:2:9
12+
|
13+
LL | #![deny(c_void_returns)]
14+
| ^^^^^^^^^^^^^^
15+
16+
error: declarations returning `c_void` are not compatible with C functions returning `void`
17+
--> $DIR/c-void-returns.rs:16:17
18+
|
19+
LL | fn baz() -> c_void;
20+
| ----^^^^^^
21+
| |
22+
| help: remove the return type to implicitly return `()`
23+
|
24+
= help: returning `()` in Rust is equivalent to returning `void` in C
25+
= note: `c_void` is only used through raw pointers for compatibility with `void` pointers
26+
27+
error: `c_void` should not be used as a return type
28+
--> $DIR/c-void-returns.rs:20:22
29+
|
30+
LL | type Xyzzy = fn() -> c_void;
31+
| ----^^^^^^
32+
| |
33+
| help: remove the return type to implicitly return `()`
34+
|
35+
= help: returning `()` in Rust is equivalent to returning `void` in C
36+
37+
error: aborting due to 3 previous errors
38+

0 commit comments

Comments
 (0)