Do not recurse indefinitely while checking for inner mutability
`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.
This commit is contained in:
parent
84ef7fb414
commit
1ae37ada08
2 changed files with 55 additions and 9 deletions
|
|
@ -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()
|
||||
|
|
|
|||
27
tests/ui/crashes/ice-14935.rs
Normal file
27
tests/ui/crashes/ice-14935.rs
Normal file
|
|
@ -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() {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue