about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-05-02 02:41:40 +0000
committerbors <bors@rust-lang.org>2024-05-02 02:41:40 +0000
commitfcc06c894b17f4d0c80b8934ea5f27faa894c960 (patch)
treeb307cfe20d426b8d257ddb37a0cd4d4925054ba2
parentf92d49b7fe059e05eea43d7fc530aa96b8028fed (diff)
parentb5626172d13bed3f82ead73493e548d6df9bf63a (diff)
downloadrust-fcc06c894b17f4d0c80b8934ea5f27faa894c960.tar.gz
rust-fcc06c894b17f4d0c80b8934ea5f27faa894c960.zip
Auto merge of #123939 - WaffleLapkin:never-fallback-unsafe-lint, r=compiler-errors
Add a lint against never type fallback affecting unsafe code

~~I'm not very happy with the code quality... `VecGraph` not allowing you to get predecessors is very annoying. This should work though, so there is that.~~ (ended up updating `VecGraph` to support getting predecessors)

~~First few commits are from https://github.com/rust-lang/rust/pull/123934 https://github.com/rust-lang/rust/pull/123980~~
-rw-r--r--compiler/rustc_data_structures/src/lib.rs1
-rw-r--r--compiler/rustc_data_structures/src/unord.rs6
-rw-r--r--compiler/rustc_hir_typeck/messages.ftl11
-rw-r--r--compiler/rustc_hir_typeck/src/errors.rs20
-rw-r--r--compiler/rustc_hir_typeck/src/fallback.rs245
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs4
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs80
-rw-r--r--tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs141
-rw-r--r--tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.stderr87
9 files changed, 574 insertions, 21 deletions
diff --git a/compiler/rustc_data_structures/src/lib.rs b/compiler/rustc_data_structures/src/lib.rs
index 0c322939db9..cf54e700e2b 100644
--- a/compiler/rustc_data_structures/src/lib.rs
+++ b/compiler/rustc_data_structures/src/lib.rs
@@ -25,6 +25,7 @@
 #![feature(lazy_cell)]
 #![feature(lint_reasons)]
 #![feature(macro_metavar_expr)]
+#![feature(map_try_insert)]
 #![feature(maybe_uninit_uninit_array)]
 #![feature(min_specialization)]
 #![feature(negative_impls)]
diff --git a/compiler/rustc_data_structures/src/unord.rs b/compiler/rustc_data_structures/src/unord.rs
index ca66d58c139..1ccd22a56c9 100644
--- a/compiler/rustc_data_structures/src/unord.rs
+++ b/compiler/rustc_data_structures/src/unord.rs
@@ -4,6 +4,7 @@
 
 use rustc_hash::{FxHashMap, FxHashSet};
 use rustc_macros::{Decodable_Generic, Encodable_Generic};
+use std::collections::hash_map::OccupiedError;
 use std::{
     borrow::{Borrow, BorrowMut},
     collections::hash_map::Entry,
@@ -470,6 +471,11 @@ impl<K: Eq + Hash, V> UnordMap<K, V> {
     }
 
     #[inline]
+    pub fn try_insert(&mut self, k: K, v: V) -> Result<&mut V, OccupiedError<'_, K, V>> {
+        self.inner.try_insert(k, v)
+    }
+
+    #[inline]
     pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
     where
         K: Borrow<Q>,
diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl
index 07b4948872d..0560d0d902a 100644
--- a/compiler/rustc_hir_typeck/messages.ftl
+++ b/compiler/rustc_hir_typeck/messages.ftl
@@ -99,6 +99,17 @@ hir_typeck_lossy_provenance_ptr2int =
 
 hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}`
 
+hir_typeck_never_type_fallback_flowing_into_unsafe_call = never type fallback affects this call to an `unsafe` function
+    .help = specify the type explicitly
+hir_typeck_never_type_fallback_flowing_into_unsafe_deref = never type fallback affects this raw pointer dereference
+    .help = specify the type explicitly
+hir_typeck_never_type_fallback_flowing_into_unsafe_method = never type fallback affects this call to an `unsafe` method
+    .help = specify the type explicitly
+hir_typeck_never_type_fallback_flowing_into_unsafe_path = never type fallback affects this `unsafe` function
+    .help = specify the type explicitly
+hir_typeck_never_type_fallback_flowing_into_unsafe_union_field = never type fallback affects this union access
+    .help = specify the type explicitly
+
 hir_typeck_no_associated_item = no {$item_kind} named `{$item_name}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method ->
     [true] {""}
     *[other] {" "}in the current scope
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index 1c4d5657b17..ba8f246fd8d 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -164,6 +164,25 @@ pub struct MissingParenthesesInRange {
     pub add_missing_parentheses: Option<AddMissingParenthesesInRange>,
 }
 
+#[derive(LintDiagnostic)]
+pub enum NeverTypeFallbackFlowingIntoUnsafe {
+    #[help]
+    #[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_call)]
+    Call,
+    #[help]
+    #[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_method)]
+    Method,
+    #[help]
+    #[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_path)]
+    Path,
+    #[help]
+    #[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_union_field)]
+    UnionField,
+    #[help]
+    #[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_deref)]
+    Deref,
+}
+
 #[derive(Subdiagnostic)]
 #[multipart_suggestion(
     hir_typeck_add_missing_parentheses_in_range,
@@ -632,7 +651,6 @@ pub enum SuggestBoxingForReturnImplTrait {
         ends: Vec<Span>,
     },
 }
-
 #[derive(LintDiagnostic)]
 #[diag(hir_typeck_dereferencing_mut_binding)]
 pub struct DereferencingMutBinding {
diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs
index c0b3984e3e1..f240a53a679 100644
--- a/compiler/rustc_hir_typeck/src/fallback.rs
+++ b/compiler/rustc_hir_typeck/src/fallback.rs
@@ -1,11 +1,18 @@
-use crate::FnCtxt;
+use std::cell::OnceCell;
+
+use crate::{errors, FnCtxt, TypeckRootCtxt};
 use rustc_data_structures::{
     graph::{self, iterate::DepthFirstSearch, vec_graph::VecGraph},
     unord::{UnordBag, UnordMap, UnordSet},
 };
+use rustc_hir as hir;
+use rustc_hir::intravisit::Visitor;
+use rustc_hir::HirId;
 use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
-use rustc_middle::ty::{self, Ty};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
+use rustc_session::lint;
 use rustc_span::DUMMY_SP;
+use rustc_span::{def_id::LocalDefId, Span};
 
 #[derive(Copy, Clone)]
 pub enum DivergingFallbackBehavior {
@@ -335,6 +342,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
         // reach a member of N. If so, it falls back to `()`. Else
         // `!`.
         let mut diverging_fallback = UnordMap::with_capacity(diverging_vids.len());
+        let unsafe_infer_vars = OnceCell::new();
         for &diverging_vid in &diverging_vids {
             let diverging_ty = Ty::new_var(self.tcx, diverging_vid);
             let root_vid = self.root_var(diverging_vid);
@@ -354,11 +362,51 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
                 output: infer_var_infos.items().any(|info| info.output),
             };
 
+            let mut fallback_to = |ty| {
+                let unsafe_infer_vars = unsafe_infer_vars.get_or_init(|| {
+                    let unsafe_infer_vars = compute_unsafe_infer_vars(self.root_ctxt, self.body_id);
+                    debug!(?unsafe_infer_vars);
+                    unsafe_infer_vars
+                });
+
+                let affected_unsafe_infer_vars =
+                    graph::depth_first_search_as_undirected(&coercion_graph, root_vid)
+                        .filter_map(|x| unsafe_infer_vars.get(&x).copied())
+                        .collect::<Vec<_>>();
+
+                for (hir_id, span, reason) in affected_unsafe_infer_vars {
+                    self.tcx.emit_node_span_lint(
+                        lint::builtin::NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
+                        hir_id,
+                        span,
+                        match reason {
+                            UnsafeUseReason::Call => {
+                                errors::NeverTypeFallbackFlowingIntoUnsafe::Call
+                            }
+                            UnsafeUseReason::Method => {
+                                errors::NeverTypeFallbackFlowingIntoUnsafe::Method
+                            }
+                            UnsafeUseReason::Path => {
+                                errors::NeverTypeFallbackFlowingIntoUnsafe::Path
+                            }
+                            UnsafeUseReason::UnionField => {
+                                errors::NeverTypeFallbackFlowingIntoUnsafe::UnionField
+                            }
+                            UnsafeUseReason::Deref => {
+                                errors::NeverTypeFallbackFlowingIntoUnsafe::Deref
+                            }
+                        },
+                    );
+                }
+
+                diverging_fallback.insert(diverging_ty, ty);
+            };
+
             use DivergingFallbackBehavior::*;
             match behavior {
                 FallbackToUnit => {
                     debug!("fallback to () - legacy: {:?}", diverging_vid);
-                    diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
+                    fallback_to(self.tcx.types.unit);
                 }
                 FallbackToNiko => {
                     if found_infer_var_info.self_in_trait && found_infer_var_info.output {
@@ -387,13 +435,13 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
                         // set, see the relationship finding module in
                         // compiler/rustc_trait_selection/src/traits/relationships.rs.
                         debug!("fallback to () - found trait and projection: {:?}", diverging_vid);
-                        diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
+                        fallback_to(self.tcx.types.unit);
                     } else if can_reach_non_diverging {
                         debug!("fallback to () - reached non-diverging: {:?}", diverging_vid);
-                        diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
+                        fallback_to(self.tcx.types.unit);
                     } else {
                         debug!("fallback to ! - all diverging: {:?}", diverging_vid);
-                        diverging_fallback.insert(diverging_ty, self.tcx.types.never);
+                        fallback_to(self.tcx.types.never);
                     }
                 }
                 FallbackToNever => {
@@ -401,7 +449,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
                         "fallback to ! - `rustc_never_type_mode = \"fallback_to_never\")`: {:?}",
                         diverging_vid
                     );
-                    diverging_fallback.insert(diverging_ty, self.tcx.types.never);
+                    fallback_to(self.tcx.types.never);
                 }
                 NoFallback => {
                     debug!(
@@ -417,7 +465,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
 
     /// Returns a graph whose nodes are (unresolved) inference variables and where
     /// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
-    fn create_coercion_graph(&self) -> VecGraph<ty::TyVid> {
+    fn create_coercion_graph(&self) -> VecGraph<ty::TyVid, true> {
         let pending_obligations = self.fulfillment_cx.borrow_mut().pending_obligations();
         debug!("create_coercion_graph: pending_obligations={:?}", pending_obligations);
         let coercion_edges: Vec<(ty::TyVid, ty::TyVid)> = pending_obligations
@@ -436,17 +484,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
                 //
                 // In practice currently the two ways that this happens is
                 // coercion and subtyping.
-                let (a, b) = if let ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) = atom {
-                    (a, b)
-                } else if let ty::PredicateKind::Subtype(ty::SubtypePredicate {
-                    a_is_expected: _,
-                    a,
-                    b,
-                }) = atom
-                {
-                    (a, b)
-                } else {
-                    return None;
+                let (a, b) = match atom {
+                    ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) => (a, b),
+                    ty::PredicateKind::Subtype(ty::SubtypePredicate { a_is_expected: _, a, b }) => {
+                        (a, b)
+                    }
+                    _ => return None,
                 };
 
                 let a_vid = self.root_vid(a)?;
@@ -456,6 +499,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
             .collect();
         debug!("create_coercion_graph: coercion_edges={:?}", coercion_edges);
         let num_ty_vars = self.num_ty_vars();
+
         VecGraph::new(num_ty_vars, coercion_edges)
     }
 
@@ -464,3 +508,166 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
         Some(self.root_var(self.shallow_resolve(ty).ty_vid()?))
     }
 }
+
+#[derive(Debug, Copy, Clone)]
+pub(crate) enum UnsafeUseReason {
+    Call,
+    Method,
+    Path,
+    UnionField,
+    Deref,
+}
+
+/// Finds all type variables which are passed to an `unsafe` operation.
+///
+/// For example, for this function `f`:
+/// ```ignore (demonstrative)
+/// fn f() {
+///     unsafe {
+///         let x /* ?X */ = core::mem::zeroed();
+///         //               ^^^^^^^^^^^^^^^^^^^ -- hir_id, span, reason
+///
+///         let y = core::mem::zeroed::<Option<_ /* ?Y */>>();
+///         //      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- hir_id, span, reason
+///     }
+/// }
+/// ```
+///
+/// `compute_unsafe_infer_vars` will return `{ id(?X) -> (hir_id, span, Call) }`
+fn compute_unsafe_infer_vars<'a, 'tcx>(
+    root_ctxt: &'a TypeckRootCtxt<'tcx>,
+    body_id: LocalDefId,
+) -> UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)> {
+    let body_id =
+        root_ctxt.tcx.hir().maybe_body_owned_by(body_id).expect("body id must have an owner");
+    let body = root_ctxt.tcx.hir().body(body_id);
+    let mut res = UnordMap::default();
+
+    struct UnsafeInferVarsVisitor<'a, 'tcx, 'r> {
+        root_ctxt: &'a TypeckRootCtxt<'tcx>,
+        res: &'r mut UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)>,
+    }
+
+    impl Visitor<'_> for UnsafeInferVarsVisitor<'_, '_, '_> {
+        fn visit_expr(&mut self, ex: &'_ hir::Expr<'_>) {
+            let typeck_results = self.root_ctxt.typeck_results.borrow();
+
+            match ex.kind {
+                hir::ExprKind::MethodCall(..) => {
+                    if let Some(def_id) = typeck_results.type_dependent_def_id(ex.hir_id)
+                        && let method_ty = self.root_ctxt.tcx.type_of(def_id).instantiate_identity()
+                        && let sig = method_ty.fn_sig(self.root_ctxt.tcx)
+                        && let hir::Unsafety::Unsafe = sig.unsafety()
+                    {
+                        let mut collector = InferVarCollector {
+                            value: (ex.hir_id, ex.span, UnsafeUseReason::Method),
+                            res: self.res,
+                        };
+
+                        // Collect generic arguments (incl. `Self`) of the method
+                        typeck_results
+                            .node_args(ex.hir_id)
+                            .types()
+                            .for_each(|t| t.visit_with(&mut collector));
+                    }
+                }
+
+                hir::ExprKind::Call(func, ..) => {
+                    let func_ty = typeck_results.expr_ty(func);
+
+                    if func_ty.is_fn()
+                        && let sig = func_ty.fn_sig(self.root_ctxt.tcx)
+                        && let hir::Unsafety::Unsafe = sig.unsafety()
+                    {
+                        let mut collector = InferVarCollector {
+                            value: (ex.hir_id, ex.span, UnsafeUseReason::Call),
+                            res: self.res,
+                        };
+
+                        // Try collecting generic arguments of the function.
+                        // Note that we do this below for any paths (that don't have to be called),
+                        // but there we do it with a different span/reason.
+                        // This takes priority.
+                        typeck_results
+                            .node_args(func.hir_id)
+                            .types()
+                            .for_each(|t| t.visit_with(&mut collector));
+
+                        // Also check the return type, for cases like `returns_unsafe_fn_ptr()()`
+                        sig.output().visit_with(&mut collector);
+                    }
+                }
+
+                // Check paths which refer to functions.
+                // We do this, instead of only checking `Call` to make sure the lint can't be
+                // avoided by storing unsafe function in a variable.
+                hir::ExprKind::Path(_) => {
+                    let ty = typeck_results.expr_ty(ex);
+
+                    // If this path refers to an unsafe function, collect inference variables which may affect it.
+                    // `is_fn` excludes closures, but those can't be unsafe.
+                    if ty.is_fn()
+                        && let sig = ty.fn_sig(self.root_ctxt.tcx)
+                        && let hir::Unsafety::Unsafe = sig.unsafety()
+                    {
+                        let mut collector = InferVarCollector {
+                            value: (ex.hir_id, ex.span, UnsafeUseReason::Path),
+                            res: self.res,
+                        };
+
+                        // Collect generic arguments of the function
+                        typeck_results
+                            .node_args(ex.hir_id)
+                            .types()
+                            .for_each(|t| t.visit_with(&mut collector));
+                    }
+                }
+
+                hir::ExprKind::Unary(hir::UnOp::Deref, pointer) => {
+                    if let ty::RawPtr(pointee, _) = typeck_results.expr_ty(pointer).kind() {
+                        pointee.visit_with(&mut InferVarCollector {
+                            value: (ex.hir_id, ex.span, UnsafeUseReason::Deref),
+                            res: self.res,
+                        });
+                    }
+                }
+
+                hir::ExprKind::Field(base, _) => {
+                    let base_ty = typeck_results.expr_ty(base);
+
+                    if base_ty.is_union() {
+                        typeck_results.expr_ty(ex).visit_with(&mut InferVarCollector {
+                            value: (ex.hir_id, ex.span, UnsafeUseReason::UnionField),
+                            res: self.res,
+                        });
+                    }
+                }
+
+                _ => (),
+            };
+
+            hir::intravisit::walk_expr(self, ex);
+        }
+    }
+
+    struct InferVarCollector<'r, V> {
+        value: V,
+        res: &'r mut UnordMap<ty::TyVid, V>,
+    }
+
+    impl<'tcx, V: Copy> ty::TypeVisitor<TyCtxt<'tcx>> for InferVarCollector<'_, V> {
+        fn visit_ty(&mut self, t: Ty<'tcx>) {
+            if let Some(vid) = t.ty_vid() {
+                _ = self.res.try_insert(vid, self.value);
+            } else {
+                t.super_visit_with(self)
+            }
+        }
+    }
+
+    UnsafeInferVarsVisitor { root_ctxt, res: &mut res }.visit_expr(&body.value);
+
+    debug!(?res, "collected the following unsafe vars for {body_id:?}");
+
+    res
+}
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
index 2f96cf9e373..794b854ca5f 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
@@ -5,12 +5,14 @@ mod checks;
 mod inspect_obligations;
 mod suggestions;
 
+use rustc_errors::ErrorGuaranteed;
+
 use crate::coercion::DynamicCoerceMany;
 use crate::fallback::DivergingFallbackBehavior;
 use crate::fn_ctxt::checks::DivergingBlockBehavior;
 use crate::{CoroutineTypes, Diverges, EnclosingBreakables, TypeckRootCtxt};
 use hir::def_id::CRATE_DEF_ID;
-use rustc_errors::{DiagCtxt, ErrorGuaranteed};
+use rustc_errors::DiagCtxt;
 use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 86a0f33a8d1..c7996c27c2f 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -69,6 +69,7 @@ declare_lint_pass! {
         MISSING_FRAGMENT_SPECIFIER,
         MUST_NOT_SUSPEND,
         NAMED_ARGUMENTS_USED_POSITIONALLY,
+        NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
         NON_CONTIGUOUS_RANGE_ENDPOINTS,
         NON_EXHAUSTIVE_OMITTED_PATTERNS,
         ORDER_DEPENDENT_TRAIT_OBJECTS,
@@ -4246,6 +4247,85 @@ declare_lint! {
 }
 
 declare_lint! {
+    /// The `never_type_fallback_flowing_into_unsafe` lint detects cases where never type fallback
+    /// affects unsafe function calls.
+    ///
+    /// ### Never type fallback
+    ///
+    /// When the compiler sees a value of type [`!`] it implicitly inserts a coercion (if possible),
+    /// to allow type check to infer any type:
+    ///
+    /// ```ignore (illustrative-and-has-placeholders)
+    /// // this
+    /// let x: u8 = panic!();
+    ///
+    /// // is (essentially) turned by the compiler into
+    /// let x: u8 = absurd(panic!());
+    ///
+    /// // where absurd is a function with the following signature
+    /// // (it's sound, because `!` always marks unreachable code):
+    /// fn absurd<T>(_: !) -> T { ... }
+    // FIXME: use `core::convert::absurd` here instead, once it's merged
+    /// ```
+    ///
+    /// While it's convenient to be able to use non-diverging code in one of the branches (like
+    /// `if a { b } else { return }`) this could lead to compilation errors:
+    ///
+    /// ```compile_fail
+    /// // this
+    /// { panic!() };
+    ///
+    /// // gets turned into this
+    /// { absurd(panic!()) }; // error: can't infer the type of `absurd`
+    /// ```
+    ///
+    /// To prevent such errors, compiler remembers where it inserted `absurd` calls, and if it
+    /// can't infer their type, it sets the type to fallback. `{ absurd::<Fallback>(panic!()) };`.
+    /// This is what is known as "never type fallback".
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(never_type_fallback_flowing_into_unsafe)]
+    /// fn main() {
+    ///     if true {
+    ///         // return has type `!` which, is some cases, causes never type fallback
+    ///         return
+    ///     } else {
+    ///         // `zeroed` is an unsafe function, which returns an unbounded type
+    ///         unsafe { std::mem::zeroed() }
+    ///     };
+    ///     // depending on the fallback, `zeroed` may create `()` (which is completely sound),
+    ///     // or `!` (which is instant undefined behavior)
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Due to historic reasons never type fallback was `()`, meaning that `!` got spontaneously
+    /// coerced to `()`. There are plans to change that, but they may make the code such as above
+    /// unsound. Instead of depending on the fallback, you should specify the type explicitly:
+    /// ```
+    /// if true {
+    ///     return
+    /// } else {
+    ///     // type is explicitly specified, fallback can't hurt us no more
+    ///     unsafe { std::mem::zeroed::<()>() }
+    /// };
+    /// ```
+    ///
+    /// See [Tracking Issue for making `!` fall back to `!`](https://github.com/rust-lang/rust/issues/123748).
+    ///
+    /// [`!`]: https://doc.rust-lang.org/core/primitive.never.html
+    /// [`()`]: https://doc.rust-lang.org/core/primitive.unit.html
+    pub NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
+    Warn,
+    "never type fallback affecting unsafe function calls"
+}
+
+declare_lint! {
     /// The `byte_slice_in_packed_struct_with_derive` lint detects cases where a byte slice field
     /// (`[u8]`) or string slice field (`str`) is used in a `packed` struct that derives one or
     /// more built-in traits.
diff --git a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs
new file mode 100644
index 00000000000..0ae498c134f
--- /dev/null
+++ b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs
@@ -0,0 +1,141 @@
+//@ check-pass
+use std::{marker, mem, ptr};
+
+fn main() {}
+
+fn _zero() {
+    if false {
+        unsafe { mem::zeroed() }
+        //~^ warn: never type fallback affects this call to an `unsafe` function
+    } else {
+        return;
+    };
+
+    // no ; -> type is inferred without fallback
+    if true { unsafe { mem::zeroed() } } else { return }
+}
+
+fn _trans() {
+    if false {
+        unsafe {
+            struct Zst;
+            core::mem::transmute(Zst)
+            //~^ warn: never type fallback affects this call to an `unsafe` function
+        }
+    } else {
+        return;
+    };
+}
+
+fn _union() {
+    if false {
+        union Union<T: Copy> {
+            a: (),
+            b: T,
+        }
+
+        unsafe { Union { a: () }.b }
+        //~^ warn: never type fallback affects this union access
+    } else {
+        return;
+    };
+}
+
+fn _deref() {
+    if false {
+        unsafe { *ptr::from_ref(&()).cast() }
+        //~^ warn: never type fallback affects this raw pointer dereference
+    } else {
+        return;
+    };
+}
+
+fn _only_generics() {
+    if false {
+        unsafe fn internally_create<T>(_: Option<T>) {
+            let _ = mem::zeroed::<T>();
+        }
+
+        // We need the option (and unwrap later) to call a function in a way,
+        // which makes it affected by the fallback, but without having it return anything
+        let x = None;
+
+        unsafe { internally_create(x) }
+        //~^ warn: never type fallback affects this call to an `unsafe` function
+
+        x.unwrap()
+    } else {
+        return;
+    };
+}
+
+fn _stored_function() {
+    if false {
+        let zeroed = mem::zeroed;
+        //~^ warn: never type fallback affects this `unsafe` function
+
+        unsafe { zeroed() }
+        //~^ warn: never type fallback affects this call to an `unsafe` function
+    } else {
+        return;
+    };
+}
+
+fn _only_generics_stored_function() {
+    if false {
+        unsafe fn internally_create<T>(_: Option<T>) {
+            let _ = mem::zeroed::<T>();
+        }
+
+        let x = None;
+        let f = internally_create;
+        //~^ warn: never type fallback affects this `unsafe` function
+
+        unsafe { f(x) }
+
+        x.unwrap()
+    } else {
+        return;
+    };
+}
+
+fn _method() {
+    struct S<T>(marker::PhantomData<T>);
+
+    impl<T> S<T> {
+        #[allow(unused)] // FIXME: the unused lint is probably incorrect here
+        unsafe fn create_out_of_thin_air(&self) -> T {
+            todo!()
+        }
+    }
+
+    if false {
+        unsafe {
+            S(marker::PhantomData).create_out_of_thin_air()
+            //~^ warn: never type fallback affects this call to an `unsafe` method
+        }
+    } else {
+        return;
+    };
+}
+
+// Minimization of the famous `objc` crate issue
+fn _objc() {
+    pub unsafe fn send_message<R>() -> Result<R, ()> {
+        Ok(unsafe { core::mem::zeroed() })
+    }
+
+    macro_rules! msg_send {
+        () => {
+            match send_message::<_ /* ?0 */>() {
+                //~^ warn: never type fallback affects this call to an `unsafe` function
+                Ok(x) => x,
+                Err(_) => loop {},
+            }
+        };
+    }
+
+    unsafe {
+        msg_send!();
+    }
+}
diff --git a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.stderr b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.stderr
new file mode 100644
index 00000000000..84c9385fd13
--- /dev/null
+++ b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.stderr
@@ -0,0 +1,87 @@
+warning: never type fallback affects this call to an `unsafe` function
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:8:18
+   |
+LL |         unsafe { mem::zeroed() }
+   |                  ^^^^^^^^^^^^^
+   |
+   = help: specify the type explicitly
+   = note: `#[warn(never_type_fallback_flowing_into_unsafe)]` on by default
+
+warning: never type fallback affects this call to an `unsafe` function
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:22:13
+   |
+LL |             core::mem::transmute(Zst)
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: specify the type explicitly
+
+warning: never type fallback affects this union access
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:37:18
+   |
+LL |         unsafe { Union { a: () }.b }
+   |                  ^^^^^^^^^^^^^^^^^
+   |
+   = help: specify the type explicitly
+
+warning: never type fallback affects this raw pointer dereference
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:46:18
+   |
+LL |         unsafe { *ptr::from_ref(&()).cast() }
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: specify the type explicitly
+
+warning: never type fallback affects this call to an `unsafe` function
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:63:18
+   |
+LL |         unsafe { internally_create(x) }
+   |                  ^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: specify the type explicitly
+
+warning: never type fallback affects this call to an `unsafe` function
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:77:18
+   |
+LL |         unsafe { zeroed() }
+   |                  ^^^^^^^^
+   |
+   = help: specify the type explicitly
+
+warning: never type fallback affects this `unsafe` function
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:74:22
+   |
+LL |         let zeroed = mem::zeroed;
+   |                      ^^^^^^^^^^^
+   |
+   = help: specify the type explicitly
+
+warning: never type fallback affects this `unsafe` function
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:91:17
+   |
+LL |         let f = internally_create;
+   |                 ^^^^^^^^^^^^^^^^^
+   |
+   = help: specify the type explicitly
+
+warning: never type fallback affects this call to an `unsafe` method
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:114:13
+   |
+LL |             S(marker::PhantomData).create_out_of_thin_air()
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: specify the type explicitly
+
+warning: never type fallback affects this call to an `unsafe` function
+  --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:130:19
+   |
+LL |             match send_message::<_ /* ?0 */>() {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL |         msg_send!();
+   |         ----------- in this macro invocation
+   |
+   = help: specify the type explicitly
+   = note: this warning originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+warning: 10 warnings emitted
+