diff --git a/CHANGELOG.md b/CHANGELOG.md index 601c27616e0..01338ed68fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5583,6 +5583,7 @@ Released 2018-09-13 [`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops [`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc [`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented +[`uninhabited_references`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninhabited_references [`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init [`uninit_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_vec [`uninlined_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 29c96a7d6da..4bccb7e7c76 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -679,6 +679,7 @@ crate::unicode::INVISIBLE_CHARACTERS_INFO, crate::unicode::NON_ASCII_LITERAL_INFO, crate::unicode::UNICODE_NOT_NFC_INFO, + crate::uninhabited_references::UNINHABITED_REFERENCES_INFO, crate::uninit_vec::UNINIT_VEC_INFO, crate::unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD_INFO, crate::unit_types::LET_UNIT_VALUE_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8560edbee76..f2b7375a742 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -326,6 +326,7 @@ mod types; mod undocumented_unsafe_blocks; mod unicode; +mod uninhabited_references; mod uninit_vec; mod unit_return_expecting_ord; mod unit_types; @@ -1071,6 +1072,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(iter_over_hash_type::IterOverHashType)); store.register_late_pass(|_| Box::new(impl_hash_with_borrow_str_and_bytes::ImplHashWithBorrowStrBytes)); store.register_late_pass(|_| Box::new(repeat_vec_with_capacity::RepeatVecWithCapacity)); + store.register_late_pass(|_| Box::new(uninhabited_references::UninhabitedReferences)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/uninhabited_references.rs b/clippy_lints/src/uninhabited_references.rs new file mode 100644 index 00000000000..d41576cadad --- /dev/null +++ b/clippy_lints/src/uninhabited_references.rs @@ -0,0 +1,84 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnRetTy, TyKind, UnOp}; +use rustc_hir_analysis::hir_ty_to_ty; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::declare_lint_pass; +use rustc_span::def_id::LocalDefId; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// It detects references to uninhabited types, such as `!` and + /// warns when those are either dereferenced or returned from a function. + /// + /// ### Why is this bad? + /// Dereferencing a reference to an uninhabited type would create + /// an instance of such a type, which cannot exist. This constitutes + /// undefined behaviour. Such a reference could have been created + /// by `unsafe` code. + /// + /// ### Example + /// The following function can return a reference to an uninhabited type + /// (`Infallible`) because it uses `unsafe` code to create it. However, + /// the user of such a function could dereference the return value and + /// trigger an undefined behavior from safe code. + /// + /// ```no_run + /// fn create_ref() -> &'static std::convert::Infallible { + /// unsafe { std::mem::transmute(&()) } + /// } + /// ``` + #[clippy::version = "1.76.0"] + pub UNINHABITED_REFERENCES, + suspicious, + "reference to uninhabited type" +} + +declare_lint_pass!(UninhabitedReferences => [UNINHABITED_REFERENCES]); + +impl LateLintPass<'_> for UninhabitedReferences { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { + if in_external_macro(cx.tcx.sess, expr.span) { + return; + } + + if let ExprKind::Unary(UnOp::Deref, _) = expr.kind { + let ty = cx.typeck_results().expr_ty_adjusted(expr); + if ty.is_privately_uninhabited(cx.tcx, cx.param_env) { + span_lint( + cx, + UNINHABITED_REFERENCES, + expr.span, + "dereferencing a reference to an uninhabited type is undefined behavior", + ); + } + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'_>, + kind: FnKind<'_>, + fndecl: &'_ FnDecl<'_>, + _: &'_ Body<'_>, + span: Span, + _: LocalDefId, + ) { + if in_external_macro(cx.tcx.sess, span) || matches!(kind, FnKind::Closure) { + return; + } + if let FnRetTy::Return(hir_ty) = fndecl.output + && let TyKind::Ref(_, mut_ty) = hir_ty.kind + && hir_ty_to_ty(cx.tcx, mut_ty.ty).is_privately_uninhabited(cx.tcx, cx.param_env) + { + span_lint( + cx, + UNINHABITED_REFERENCES, + hir_ty.span, + "dereferencing a reference to an uninhabited type would be undefined behavior", + ); + } + } +} diff --git a/tests/ui/infallible_destructuring_match.fixed b/tests/ui/infallible_destructuring_match.fixed index 60304177b42..ecf1b14e5b6 100644 --- a/tests/ui/infallible_destructuring_match.fixed +++ b/tests/ui/infallible_destructuring_match.fixed @@ -1,6 +1,6 @@ #![feature(exhaustive_patterns, never_type)] #![allow(dead_code, unreachable_code, unused_variables)] -#![allow(clippy::let_and_return)] +#![allow(clippy::let_and_return, clippy::uninhabited_references)] enum SingleVariantEnum { Variant(i32), diff --git a/tests/ui/infallible_destructuring_match.rs b/tests/ui/infallible_destructuring_match.rs index b77aac4a16c..7cc7cb9d687 100644 --- a/tests/ui/infallible_destructuring_match.rs +++ b/tests/ui/infallible_destructuring_match.rs @@ -1,6 +1,6 @@ #![feature(exhaustive_patterns, never_type)] #![allow(dead_code, unreachable_code, unused_variables)] -#![allow(clippy::let_and_return)] +#![allow(clippy::let_and_return, clippy::uninhabited_references)] enum SingleVariantEnum { Variant(i32), diff --git a/tests/ui/uninhabited_references.rs b/tests/ui/uninhabited_references.rs new file mode 100644 index 00000000000..cd07b590a61 --- /dev/null +++ b/tests/ui/uninhabited_references.rs @@ -0,0 +1,22 @@ +#![warn(clippy::uninhabited_references)] +#![feature(never_type)] + +fn ret_uninh_ref() -> &'static std::convert::Infallible { + unsafe { std::mem::transmute(&()) } +} + +macro_rules! ret_something { + ($name:ident, $ty:ty) => { + fn $name(x: &$ty) -> &$ty { + &*x + } + }; +} + +ret_something!(id_u32, u32); +ret_something!(id_never, !); + +fn main() { + let x = ret_uninh_ref(); + let _ = *x; +} diff --git a/tests/ui/uninhabited_references.stderr b/tests/ui/uninhabited_references.stderr new file mode 100644 index 00000000000..2cdf320b809 --- /dev/null +++ b/tests/ui/uninhabited_references.stderr @@ -0,0 +1,39 @@ +error: dereferencing a reference to an uninhabited type would be undefined behavior + --> $DIR/uninhabited_references.rs:4:23 + | +LL | fn ret_uninh_ref() -> &'static std::convert::Infallible { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::uninhabited-references` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::uninhabited_references)]` + +error: dereferencing a reference to an uninhabited type would be undefined behavior + --> $DIR/uninhabited_references.rs:10:30 + | +LL | fn $name(x: &$ty) -> &$ty { + | ^^^^ +... +LL | ret_something!(id_never, !); + | --------------------------- in this macro invocation + | + = note: this error originates in the macro `ret_something` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: dereferencing a reference to an uninhabited type is undefined behavior + --> $DIR/uninhabited_references.rs:11:14 + | +LL | &*x + | ^^ +... +LL | ret_something!(id_never, !); + | --------------------------- in this macro invocation + | + = note: this error originates in the macro `ret_something` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: dereferencing a reference to an uninhabited type is undefined behavior + --> $DIR/uninhabited_references.rs:21:13 + | +LL | let _ = *x; + | ^^ + +error: aborting due to 4 previous errors +