about summary refs log tree commit diff
diff options
context:
space:
mode:
authorllogiq <bogusandre@gmail.com>2025-06-05 19:39:14 +0000
committerGitHub <noreply@github.com>2025-06-05 19:39:14 +0000
commitda93448c9875e40b252c794d4b08a28fe3cbeb65 (patch)
tree2943d638f193bc0b702857146ca88b89d36a182b
parentb379d54c22f9f6b9577075d7f89645ac2498c931 (diff)
parent1ae37ada0845bdaa13008c3957b98cc75f068fd4 (diff)
downloadrust-da93448c9875e40b252c794d4b08a28fe3cbeb65.tar.gz
rust-da93448c9875e40b252c794d4b08a28fe3cbeb65.zip
Do not recurse indefinitely while checking for inner mutability (#14965)
`clippy_utils::ty::InteriorMut::interior_mut_ty_chain` must stop
recursing forever when types are chained indefinitely due to the use of
associated types in generics. A false negative is acceptable, and
documented here.

Should this situation be later identified specifically, a conversion of
`Option` to `Result` would allow separating the infinitely recursive
case from a negative one.

changelog: [`mutable_key_type`]: fix ICE when infinitely associated
generic types are used

Fixes rust-lang/rust-clippy#14935
-rw-r--r--clippy_utils/src/ty/mod.rs37
-rw-r--r--tests/ui/crashes/ice-14935.rs27
2 files changed, 55 insertions, 9 deletions
diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs
index 61e70b3fa0b..1f0a0f2b02a 100644
--- a/clippy_utils/src/ty/mod.rs
+++ b/clippy_utils/src/ty/mod.rs
@@ -1137,21 +1137,38 @@ impl<'tcx> InteriorMut<'tcx> {
 
     /// Check if given type has interior mutability such as [`std::cell::Cell`] or
     /// [`std::cell::RefCell`] etc. and if it does, returns a chain of types that causes
-    /// this type to be interior mutable
+    /// this type to be interior mutable.  False negatives may be expected for infinitely recursive
+    /// types, and `None` will be returned there.
     pub fn interior_mut_ty_chain(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx ty::List<Ty<'tcx>>> {
+        self.interior_mut_ty_chain_inner(cx, ty, 0)
+    }
+
+    fn interior_mut_ty_chain_inner(
+        &mut self,
+        cx: &LateContext<'tcx>,
+        ty: Ty<'tcx>,
+        depth: usize,
+    ) -> Option<&'tcx ty::List<Ty<'tcx>>> {
+        if !cx.tcx.recursion_limit().value_within_limit(depth) {
+            return None;
+        }
+
         match self.tys.entry(ty) {
             Entry::Occupied(o) => return *o.get(),
             // Temporarily insert a `None` to break cycles
             Entry::Vacant(v) => v.insert(None),
         };
+        let depth = depth + 1;
 
         let chain = match *ty.kind() {
-            ty::RawPtr(inner_ty, _) if !self.ignore_pointers => self.interior_mut_ty_chain(cx, inner_ty),
-            ty::Ref(_, inner_ty, _) | ty::Slice(inner_ty) => self.interior_mut_ty_chain(cx, inner_ty),
+            ty::RawPtr(inner_ty, _) if !self.ignore_pointers => self.interior_mut_ty_chain_inner(cx, inner_ty, depth),
+            ty::Ref(_, inner_ty, _) | ty::Slice(inner_ty) => self.interior_mut_ty_chain_inner(cx, inner_ty, depth),
             ty::Array(inner_ty, size) if size.try_to_target_usize(cx.tcx) != Some(0) => {
-                self.interior_mut_ty_chain(cx, inner_ty)
+                self.interior_mut_ty_chain_inner(cx, inner_ty, depth)
             },
-            ty::Tuple(fields) => fields.iter().find_map(|ty| self.interior_mut_ty_chain(cx, ty)),
+            ty::Tuple(fields) => fields
+                .iter()
+                .find_map(|ty| self.interior_mut_ty_chain_inner(cx, ty, depth)),
             ty::Adt(def, _) if def.is_unsafe_cell() => Some(ty::List::empty()),
             ty::Adt(def, args) => {
                 let is_std_collection = matches!(
@@ -1171,16 +1188,17 @@ impl<'tcx> InteriorMut<'tcx> {
 
                 if is_std_collection || def.is_box() {
                     // Include the types from std collections that are behind pointers internally
-                    args.types().find_map(|ty| self.interior_mut_ty_chain(cx, ty))
+                    args.types()
+                        .find_map(|ty| self.interior_mut_ty_chain_inner(cx, ty, depth))
                 } else if self.ignored_def_ids.contains(&def.did()) || def.is_phantom_data() {
                     None
                 } else {
                     def.all_fields()
-                        .find_map(|f| self.interior_mut_ty_chain(cx, f.ty(cx.tcx, args)))
+                        .find_map(|f| self.interior_mut_ty_chain_inner(cx, f.ty(cx.tcx, args), depth))
                 }
             },
             ty::Alias(ty::Projection, _) => match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) {
-                Ok(normalized_ty) if ty != normalized_ty => self.interior_mut_ty_chain(cx, normalized_ty),
+                Ok(normalized_ty) if ty != normalized_ty => self.interior_mut_ty_chain_inner(cx, normalized_ty, depth),
                 _ => None,
             },
             _ => None,
@@ -1342,7 +1360,8 @@ pub fn has_non_owning_mutable_access<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'
                 mutability.is_mut() || !pointee_ty.is_freeze(cx.tcx, cx.typing_env())
             },
             ty::Closure(_, closure_args) => {
-                matches!(closure_args.types().next_back(), Some(captures) if has_non_owning_mutable_access_inner(cx, phantoms, captures))
+                matches!(closure_args.types().next_back(),
+                         Some(captures) if has_non_owning_mutable_access_inner(cx, phantoms, captures))
             },
             ty::Tuple(tuple_args) => tuple_args
                 .iter()
diff --git a/tests/ui/crashes/ice-14935.rs b/tests/ui/crashes/ice-14935.rs
new file mode 100644
index 00000000000..74cda9aae53
--- /dev/null
+++ b/tests/ui/crashes/ice-14935.rs
@@ -0,0 +1,27 @@
+//@check-pass
+#![warn(clippy::mutable_key_type)]
+
+use std::marker::PhantomData;
+
+trait Group {
+    type ExposantSet: Group;
+}
+
+struct Pow<T: Group> {
+    exposant: Box<Pow<T::ExposantSet>>,
+    _p: PhantomData<T>,
+}
+
+impl<T: Group> Pow<T> {
+    fn is_zero(&self) -> bool {
+        false
+    }
+    fn normalize(&self) {
+        #[expect(clippy::if_same_then_else)]
+        if self.is_zero() {
+        } else if false {
+        }
+    }
+}
+
+fn main() {}