diff options
| author | llogiq <bogusandre@gmail.com> | 2025-06-05 19:39:14 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-05 19:39:14 +0000 |
| commit | da93448c9875e40b252c794d4b08a28fe3cbeb65 (patch) | |
| tree | 2943d638f193bc0b702857146ca88b89d36a182b | |
| parent | b379d54c22f9f6b9577075d7f89645ac2498c931 (diff) | |
| parent | 1ae37ada0845bdaa13008c3957b98cc75f068fd4 (diff) | |
| download | rust-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.rs | 37 | ||||
| -rw-r--r-- | tests/ui/crashes/ice-14935.rs | 27 |
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() {} |
