From 51b4d2a1e825c39c444be1385f3618302ef0b452 Mon Sep 17 00:00:00 2001
From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com>
Date: Sun, 26 Mar 2023 14:52:46 +0000
Subject: [PATCH] In uninit checking, add fallback for polymorphic types

---
 clippy_utils/src/ty.rs | 22 +++++++++++++++++++---
 tests/ui/uninit.rs     | 18 +++++++++++++++---
 tests/ui/uninit.stderr | 10 +++++-----
 tests/ui/uninit_vec.rs |  8 ++++++++
 4 files changed, 47 insertions(+), 11 deletions(-)

diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs
index 0b47234647f..9449f0b5567 100644
--- a/clippy_utils/src/ty.rs
+++ b/clippy_utils/src/ty.rs
@@ -541,9 +541,25 @@ pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
 pub fn is_uninit_value_valid_for_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
     cx.tcx
         .check_validity_requirement((ValidityRequirement::Uninit, cx.param_env.and(ty)))
-        // For types containing generic parameters we cannot get a layout to check.
-        // Therefore, we are conservative and assume that they don't allow uninit.
-        .unwrap_or(false)
+        .unwrap_or_else(|_| is_uninit_value_valid_for_ty_fallback(cx, ty))
+}
+
+/// A fallback for polymorphic types, which are not supported by `check_validity_requirement`.
+fn is_uninit_value_valid_for_ty_fallback<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+    match *ty.kind() {
+        // The array length may be polymorphic, let's try the inner type.
+        ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
+        // Peek through tuples and try their fallbacks.
+        ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
+        // Unions are always fine right now.
+        // This includes MaybeUninit, the main way people use uninitialized memory.
+        // For ADTs, we could look at all fields just like for tuples, but that's potentially
+        // exponential, so let's avoid doing that for now. Code doing that is sketchy enough to
+        // just use an `#[allow()]`.
+        ty::Adt(adt, _) => adt.is_union(),
+        // For the rest, conservatively assume that they cannot be uninit.
+        _ => false,
+    }
 }
 
 /// Gets an iterator over all predicates which apply to the given item.
diff --git a/tests/ui/uninit.rs b/tests/ui/uninit.rs
index 412b36b4ee8..c996de89422 100644
--- a/tests/ui/uninit.rs
+++ b/tests/ui/uninit.rs
@@ -1,7 +1,7 @@
 #![feature(stmt_expr_attributes)]
 #![allow(clippy::let_unit_value, invalid_value)]
 
-use std::mem::{self, MaybeUninit};
+use std::mem::MaybeUninit;
 
 union MyOwnMaybeUninit {
     value: u8,
@@ -30,12 +30,24 @@ fn main() {
     let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() };
 
     // Was a false negative.
-    let _: usize = unsafe { mem::MaybeUninit::uninit().assume_init() };
+    let _: usize = unsafe { MaybeUninit::uninit().assume_init() };
 
     polymorphic::<()>();
+    polymorphic_maybe_uninit_array::<10>();
+    polymorphic_maybe_uninit::<u8>();
 
     fn polymorphic<T>() {
         // We are conservative around polymorphic types.
-        let _: T = unsafe { mem::MaybeUninit::uninit().assume_init() };
+        let _: T = unsafe { MaybeUninit::uninit().assume_init() };
+    }
+
+    fn polymorphic_maybe_uninit_array<const N: usize>() {
+        // While the type is polymorphic, MaybeUninit<u8> is not.
+        let _: [MaybeUninit<u8>; N] = unsafe { MaybeUninit::uninit().assume_init() };
+    }
+
+    fn polymorphic_maybe_uninit<T>() {
+        // The entire type is polymorphic, but it's wrapped in a MaybeUninit.
+        let _: MaybeUninit<T> = unsafe { MaybeUninit::uninit().assume_init() };
     }
 }
diff --git a/tests/ui/uninit.stderr b/tests/ui/uninit.stderr
index 9e01b9a4aa8..248de56da76 100644
--- a/tests/ui/uninit.stderr
+++ b/tests/ui/uninit.stderr
@@ -9,14 +9,14 @@ LL |     let _: usize = unsafe { MaybeUninit::uninit().assume_init() };
 error: this call for this type may be undefined behavior
   --> $DIR/uninit.rs:33:29
    |
-LL |     let _: usize = unsafe { mem::MaybeUninit::uninit().assume_init() };
-   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     let _: usize = unsafe { MaybeUninit::uninit().assume_init() };
+   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: this call for this type may be undefined behavior
-  --> $DIR/uninit.rs:39:29
+  --> $DIR/uninit.rs:41:29
    |
-LL |         let _: T = unsafe { mem::MaybeUninit::uninit().assume_init() };
-   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         let _: T = unsafe { MaybeUninit::uninit().assume_init() };
+   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 3 previous errors
 
diff --git a/tests/ui/uninit_vec.rs b/tests/ui/uninit_vec.rs
index 59ec64a7ab1..79effc82fdf 100644
--- a/tests/ui/uninit_vec.rs
+++ b/tests/ui/uninit_vec.rs
@@ -124,4 +124,12 @@ fn main() {
             vec.set_len(10);
         }
     }
+
+    fn poly_maybe_uninit<T>() {
+        // We are conservative around polymorphic types.
+        let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
+        unsafe {
+            vec.set_len(10);
+        }
+    }
 }