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};
9 #[expect(clippy::too_many_lines)]
10 pub(super) fn check<'tcx>(
11 cx: &LateContext<'tcx>,
13 from_ty_orig: Ty<'tcx>,
16 let mut from_ty = cx.tcx.erase_regions(from_ty_orig);
17 let mut to_ty = cx.tcx.erase_regions(to_ty_orig);
19 while from_ty != to_ty {
20 let reduced_tys = reduce_refs(cx, from_ty, to_ty);
21 match (reduce_ty(cx, reduced_tys.from_ty), reduce_ty(cx, reduced_tys.to_ty)) {
22 // Various forms of type erasure.
23 (ReducedTy::TypeErasure { raw_ptr_only: false }, _)
24 | (_, ReducedTy::TypeErasure { raw_ptr_only: false }) => return false,
25 (ReducedTy::TypeErasure { .. }, _) if reduced_tys.from_raw_ptr => return false,
26 (_, ReducedTy::TypeErasure { .. }) if reduced_tys.to_raw_ptr => return false,
28 // `Repr(C)` <-> unordered type.
29 // If the first field of the `Repr(C)` type matches then the transmute is ok
30 (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::UnorderedFields(to_sub_ty))
31 | (ReducedTy::UnorderedFields(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty))) => {
32 from_ty = from_sub_ty;
36 (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => {
37 from_ty = from_sub_ty;
41 (ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty)))
42 if reduced_tys.from_fat_ptr =>
44 from_ty = from_sub_ty;
50 (ReducedTy::Other(from_sub_ty), ReducedTy::Other(to_sub_ty))
51 if matches!(from_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_))
52 && matches!(to_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_)) =>
54 from_ty = from_sub_ty;
59 // fat ptr <-> (*size, *size)
60 (ReducedTy::Other(_), ReducedTy::UnorderedFields(to_ty))
61 if reduced_tys.from_fat_ptr && is_size_pair(to_ty) =>
65 (ReducedTy::UnorderedFields(from_ty), ReducedTy::Other(_))
66 if reduced_tys.to_fat_ptr && is_size_pair(from_ty) =>
71 // fat ptr -> some struct | some struct -> fat ptr
72 (ReducedTy::Other(_), _) if reduced_tys.from_fat_ptr => {
75 TRANSMUTE_UNDEFINED_REPR,
77 &format!("transmute from `{from_ty_orig}` which has an undefined layout"),
79 if from_ty_orig.peel_refs() != from_ty.peel_refs() {
80 diag.note(format!("the contained type `{from_ty}` has an undefined layout"));
86 (_, ReducedTy::Other(_)) if reduced_tys.to_fat_ptr => {
89 TRANSMUTE_UNDEFINED_REPR,
91 &format!("transmute to `{to_ty_orig}` which has an undefined layout"),
93 if to_ty_orig.peel_refs() != to_ty.peel_refs() {
94 diag.note(format!("the contained type `{to_ty}` has an undefined layout"));
101 (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => {
102 let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs))
103 = (from_ty.kind(), to_ty.kind())
104 && from_def == to_def
106 if same_except_params(from_subs, to_subs) {
115 TRANSMUTE_UNDEFINED_REPR,
118 "transmute from `{from_ty_orig}` to `{to_ty_orig}`, both of which have an undefined layout"
121 if let Some(same_adt_did) = same_adt_did {
123 "two instances of the same generic type (`{}`) may have different layouts",
124 cx.tcx.item_name(same_adt_did)
127 if from_ty_orig.peel_refs() != from_ty {
128 diag.note(format!("the contained type `{from_ty}` has an undefined layout"));
130 if to_ty_orig.peel_refs() != to_ty {
131 diag.note(format!("the contained type `{to_ty}` has an undefined layout"));
139 ReducedTy::UnorderedFields(from_ty),
140 ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
144 TRANSMUTE_UNDEFINED_REPR,
146 &format!("transmute from `{from_ty_orig}` which has an undefined layout"),
148 if from_ty_orig.peel_refs() != from_ty {
149 diag.note(format!("the contained type `{from_ty}` has an undefined layout"));
156 ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
157 ReducedTy::UnorderedFields(to_ty),
161 TRANSMUTE_UNDEFINED_REPR,
163 &format!("transmute into `{to_ty_orig}` which has an undefined layout"),
165 if to_ty_orig.peel_refs() != to_ty {
166 diag.note(format!("the contained type `{to_ty}` has an undefined layout"));
173 ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true },
174 ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true },
176 | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => {
185 #[expect(clippy::struct_excessive_bools)]
186 struct ReducedTys<'tcx> {
195 /// Remove references so long as both types are references.
196 fn reduce_refs<'tcx>(cx: &LateContext<'tcx>, mut from_ty: Ty<'tcx>, mut to_ty: Ty<'tcx>) -> ReducedTys<'tcx> {
197 let mut from_raw_ptr = false;
198 let mut to_raw_ptr = false;
199 let (from_fat_ptr, to_fat_ptr) = loop {
200 break match (from_ty.kind(), to_ty.kind()) {
202 &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })),
203 &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })),
205 from_raw_ptr = matches!(*from_ty.kind(), ty::RawPtr(_));
206 from_ty = from_sub_ty;
207 to_raw_ptr = matches!(*to_ty.kind(), ty::RawPtr(_));
211 (&(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })), _)
212 if !unsized_ty.is_sized(cx.tcx, cx.param_env) =>
216 (_, &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })))
217 if !unsized_ty.is_sized(cx.tcx, cx.param_env) =>
234 enum ReducedTy<'tcx> {
235 /// The type can be used for type erasure.
236 TypeErasure { raw_ptr_only: bool },
237 /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero
238 /// sized fields with a defined order.
239 /// The second value is the first non-zero sized type.
240 OrderedFields(Ty<'tcx>, Option<Ty<'tcx>>),
241 /// The type is a struct containing multiple non-zero sized fields with no defined order.
242 UnorderedFields(Ty<'tcx>),
247 /// Reduce structs containing a single non-zero sized field to it's contained type.
248 fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> {
250 ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
251 return match *ty.kind() {
252 ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => {
253 ReducedTy::TypeErasure { raw_ptr_only: false }
255 ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
259 ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure { raw_ptr_only: false },
261 let mut iter = args.iter();
262 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
263 return ReducedTy::OrderedFields(ty, None);
265 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
269 ReducedTy::UnorderedFields(ty)
271 ty::Adt(def, substs) if def.is_struct() => {
276 .map(|f| cx.tcx.bound_type_of(f.did).subst(cx.tcx, substs));
277 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
278 return ReducedTy::TypeErasure { raw_ptr_only: false };
280 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
284 if def.repr().inhibit_struct_field_reordering_opt() {
285 ReducedTy::OrderedFields(ty, Some(sized_ty))
287 ReducedTy::UnorderedFields(ty)
290 ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => {
291 ReducedTy::TypeErasure { raw_ptr_only: false }
293 // TODO: Check if the conversion to or from at least one of a union's fields is valid.
294 ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure { raw_ptr_only: false },
295 ty::Foreign(_) | ty::Param(_) => ReducedTy::TypeErasure { raw_ptr_only: false },
296 ty::Int(_) | ty::Uint(_) => ReducedTy::TypeErasure { raw_ptr_only: true },
297 _ => ReducedTy::Other(ty),
302 fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
304 if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty);
305 if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty));
307 layout.layout.size().bytes() == 0
314 fn is_size_pair(ty: Ty<'_>) -> bool {
315 if let ty::Tuple(tys) = *ty.kind()
316 && let [ty1, ty2] = &**tys
318 matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
319 && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
325 fn same_except_params<'tcx>(subs1: SubstsRef<'tcx>, subs2: SubstsRef<'tcx>) -> bool {
326 // TODO: check const parameters as well. Currently this will consider `Array<5>` the same as
328 for (ty1, ty2) in subs1.types().zip(subs2.types()).filter(|(ty1, ty2)| ty1 != ty2) {
329 match (ty1.kind(), ty2.kind()) {
330 (ty::Param(_), _) | (_, ty::Param(_)) => (),
331 (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2)) if adt1 == adt2 && same_except_params(subs1, subs2) => (),