1 use super::TRANSMUTE_UNDEFINED_REPR;
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::ty::is_c_void;
5 use rustc_lint::LateContext;
6 use rustc_middle::ty::SubstsRef;
7 use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy};
8 use rustc_span::DUMMY_SP;
10 #[expect(clippy::too_many_lines)]
11 pub(super) fn check<'tcx>(
12 cx: &LateContext<'tcx>,
14 from_ty_orig: Ty<'tcx>,
17 let mut from_ty = cx.tcx.erase_regions(from_ty_orig);
18 let mut to_ty = cx.tcx.erase_regions(to_ty_orig);
20 while from_ty != to_ty {
21 let reduced_tys = reduce_refs(cx, from_ty, to_ty);
22 match (reduce_ty(cx, reduced_tys.from_ty), reduce_ty(cx, reduced_tys.to_ty)) {
23 // Various forms of type erasure.
24 (ReducedTy::TypeErasure { raw_ptr_only: false }, _)
25 | (_, ReducedTy::TypeErasure { raw_ptr_only: false }) => return false,
26 (ReducedTy::TypeErasure { .. }, _) if reduced_tys.from_raw_ptr => return false,
27 (_, ReducedTy::TypeErasure { .. }) if reduced_tys.to_raw_ptr => return false,
29 // `Repr(C)` <-> unordered type.
30 // If the first field of the `Repr(C)` type matches then the transmute is ok
31 (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::UnorderedFields(to_sub_ty))
32 | (ReducedTy::UnorderedFields(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty))) => {
33 from_ty = from_sub_ty;
37 (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => {
38 from_ty = from_sub_ty;
42 (ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty)))
43 if reduced_tys.from_fat_ptr =>
45 from_ty = from_sub_ty;
51 (ReducedTy::Other(from_sub_ty), ReducedTy::Other(to_sub_ty))
52 if matches!(from_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_))
53 && matches!(to_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_)) =>
55 from_ty = from_sub_ty;
60 // fat ptr <-> (*size, *size)
61 (ReducedTy::Other(_), ReducedTy::UnorderedFields(to_ty))
62 if reduced_tys.from_fat_ptr && is_size_pair(to_ty) =>
66 (ReducedTy::UnorderedFields(from_ty), ReducedTy::Other(_))
67 if reduced_tys.to_fat_ptr && is_size_pair(from_ty) =>
72 // fat ptr -> some struct | some struct -> fat ptr
73 (ReducedTy::Other(_), _) if reduced_tys.from_fat_ptr => {
76 TRANSMUTE_UNDEFINED_REPR,
78 &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
80 if from_ty_orig.peel_refs() != from_ty.peel_refs() {
81 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
87 (_, ReducedTy::Other(_)) if reduced_tys.to_fat_ptr => {
90 TRANSMUTE_UNDEFINED_REPR,
92 &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
94 if to_ty_orig.peel_refs() != to_ty.peel_refs() {
95 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
102 (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => {
103 let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs))
104 = (from_ty.kind(), to_ty.kind())
105 && from_def == to_def
107 if same_except_params(from_subs, to_subs) {
116 TRANSMUTE_UNDEFINED_REPR,
119 "transmute from `{}` to `{}`, both of which have an undefined layout",
120 from_ty_orig, to_ty_orig
123 if let Some(same_adt_did) = same_adt_did {
125 "two instances of the same generic type (`{}`) may have different layouts",
126 cx.tcx.item_name(same_adt_did)
129 if from_ty_orig.peel_refs() != from_ty {
130 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
132 if to_ty_orig.peel_refs() != to_ty {
133 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
141 ReducedTy::UnorderedFields(from_ty),
142 ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
146 TRANSMUTE_UNDEFINED_REPR,
148 &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
150 if from_ty_orig.peel_refs() != from_ty {
151 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
158 ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
159 ReducedTy::UnorderedFields(to_ty),
163 TRANSMUTE_UNDEFINED_REPR,
165 &format!("transmute into `{}` which has an undefined layout", to_ty_orig),
167 if to_ty_orig.peel_refs() != to_ty {
168 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
175 ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true },
176 ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true },
178 | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => {
187 #[expect(clippy::struct_excessive_bools)]
188 struct ReducedTys<'tcx> {
197 /// Remove references so long as both types are references.
198 fn reduce_refs<'tcx>(cx: &LateContext<'tcx>, mut from_ty: Ty<'tcx>, mut to_ty: Ty<'tcx>) -> ReducedTys<'tcx> {
199 let mut from_raw_ptr = false;
200 let mut to_raw_ptr = false;
201 let (from_fat_ptr, to_fat_ptr) = loop {
202 break match (from_ty.kind(), to_ty.kind()) {
204 &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })),
205 &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })),
207 from_raw_ptr = matches!(*from_ty.kind(), ty::RawPtr(_));
208 from_ty = from_sub_ty;
209 to_raw_ptr = matches!(*to_ty.kind(), ty::RawPtr(_));
213 (&(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })), _)
214 if !unsized_ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env) =>
218 (_, &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })))
219 if !unsized_ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env) =>
236 enum ReducedTy<'tcx> {
237 /// The type can be used for type erasure.
238 TypeErasure { raw_ptr_only: bool },
239 /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero
240 /// sized fields with a defined order.
241 /// The second value is the first non-zero sized type.
242 OrderedFields(Ty<'tcx>, Option<Ty<'tcx>>),
243 /// The type is a struct containing multiple non-zero sized fields with no defined order.
244 UnorderedFields(Ty<'tcx>),
249 /// Reduce structs containing a single non-zero sized field to it's contained type.
250 fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> {
252 ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
253 return match *ty.kind() {
254 ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => {
255 ReducedTy::TypeErasure { raw_ptr_only: false }
257 ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
261 ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure { raw_ptr_only: false },
263 let mut iter = args.iter();
264 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
265 return ReducedTy::OrderedFields(ty, None);
267 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
271 ReducedTy::UnorderedFields(ty)
273 ty::Adt(def, substs) if def.is_struct() => {
278 .map(|f| cx.tcx.bound_type_of(f.did).subst(cx.tcx, substs));
279 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
280 return ReducedTy::TypeErasure { raw_ptr_only: false };
282 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
286 if def.repr().inhibit_struct_field_reordering_opt() {
287 ReducedTy::OrderedFields(ty, Some(sized_ty))
289 ReducedTy::UnorderedFields(ty)
292 ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => {
293 ReducedTy::TypeErasure { raw_ptr_only: false }
295 // TODO: Check if the conversion to or from at least one of a union's fields is valid.
296 ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure { raw_ptr_only: false },
297 ty::Foreign(_) | ty::Param(_) => ReducedTy::TypeErasure { raw_ptr_only: false },
298 ty::Int(_) | ty::Uint(_) => ReducedTy::TypeErasure { raw_ptr_only: true },
299 _ => ReducedTy::Other(ty),
304 fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
306 if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty);
307 if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty));
309 layout.layout.size().bytes() == 0
316 fn is_size_pair(ty: Ty<'_>) -> bool {
317 if let ty::Tuple(tys) = *ty.kind()
318 && let [ty1, ty2] = &**tys
320 matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
321 && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
327 fn same_except_params<'tcx>(subs1: SubstsRef<'tcx>, subs2: SubstsRef<'tcx>) -> bool {
328 // TODO: check const parameters as well. Currently this will consider `Array<5>` the same as
330 for (ty1, ty2) in subs1.types().zip(subs2.types()).filter(|(ty1, ty2)| ty1 != ty2) {
331 match (ty1.kind(), ty2.kind()) {
332 (ty::Param(_), _) | (_, ty::Param(_)) => (),
333 (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2)) if adt1 == adt2 && same_except_params(subs1, subs2) => (),