diff --git a/CHANGELOG.md b/CHANGELOG.md index abe975fa42b2..bc1a480304c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5581,6 +5581,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_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninhabited_reference [`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 5a109fcc2ccd..6f0ca037ce10 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -677,6 +677,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::unicode::INVISIBLE_CHARACTERS_INFO, crate::unicode::NON_ASCII_LITERAL_INFO, crate::unicode::UNICODE_NOT_NFC_INFO, + crate::uninhabited_reference::UNINHABITED_REFERENCE_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 1c59b2df853b..3ce8cddc5c0d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -325,6 +325,7 @@ mod tuple_array_conversions; mod types; mod undocumented_unsafe_blocks; mod unicode; +mod uninhabited_reference; mod uninit_vec; mod unit_return_expecting_ord; mod unit_types; @@ -1069,6 +1070,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter)); 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(uninhabited_reference::UninhabitedReference)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/uninhabited_reference.rs b/clippy_lints/src/uninhabited_reference.rs new file mode 100644 index 000000000000..6041a1486eda --- /dev/null +++ b/clippy_lints/src/uninhabited_reference.rs @@ -0,0 +1,81 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, ExprKind, FnDecl, FnRetTy, TyKind, UnOp}; +use rustc_hir_analysis::hir_ty_to_ty; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +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 behaviour from safe code. + /// + /// ```no_run + /// fn create_ref() -> &'static std::convert::Infallible { + /// unsafe { std::mem::transmute(&()) } + /// } + /// ``` + #[clippy::version = "1.76.0"] + pub UNINHABITED_REFERENCE, + suspicious, + "reference to uninhabited type" +} + +declare_lint_pass!(UninhabitedReference => [UNINHABITED_REFERENCE]); + +impl LateLintPass<'_> for UninhabitedReference { + fn check_expr_post(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) { + if expr.span.from_expansion() { + 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_REFERENCE, + 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: rustc_span::Span, + _: rustc_span::def_id::LocalDefId, + ) { + if span.from_expansion() || 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_REFERENCE, + 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 60304177b424..45ee25d271ea 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_reference)] enum SingleVariantEnum { Variant(i32), diff --git a/tests/ui/infallible_destructuring_match.rs b/tests/ui/infallible_destructuring_match.rs index b77aac4a16c1..fa743c28c947 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_reference)] enum SingleVariantEnum { Variant(i32), diff --git a/tests/ui/uninhabited_reference.rs b/tests/ui/uninhabited_reference.rs new file mode 100644 index 000000000000..9e37e14e0de3 --- /dev/null +++ b/tests/ui/uninhabited_reference.rs @@ -0,0 +1,11 @@ +#![warn(clippy::uninhabited_reference)] +#![feature(never_type)] + +fn ret_uninh_ref() -> &'static std::convert::Infallible { + unsafe { std::mem::transmute(&()) } +} + +fn main() { + let x = ret_uninh_ref(); + let _ = *x; +} diff --git a/tests/ui/uninhabited_reference.stderr b/tests/ui/uninhabited_reference.stderr new file mode 100644 index 000000000000..2376aea227a9 --- /dev/null +++ b/tests/ui/uninhabited_reference.stderr @@ -0,0 +1,17 @@ +error: dereferencing a reference to an uninhabited type would be undefined behavior + --> $DIR/uninhabited_reference.rs:4:23 + | +LL | fn ret_uninh_ref() -> &'static std::convert::Infallible { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::uninhabited-reference` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::uninhabited_reference)]` + +error: dereferencing a reference to an uninhabited type is undefined behavior + --> $DIR/uninhabited_reference.rs:10:13 + | +LL | let _ = *x; + | ^^ + +error: aborting due to 2 previous errors +