Skip to main content

rustc_lint/
c_void_returns.rs

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