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::subst::{Subst, SubstsRef};
7 use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy};
10 #[allow(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 match reduce_refs(cx, e.span, from_ty, to_ty) {
22 ReducedTys::FromFatPtr {
25 } => match reduce_ty(cx, to_sub_ty) {
26 ReducedTy::TypeErasure => break,
27 ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break,
28 ReducedTy::Ref(to_sub_ty) => {
36 TRANSMUTE_UNDEFINED_REPR,
38 &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
40 if from_ty_orig.peel_refs() != unsized_ty {
41 diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
48 ReducedTys::ToFatPtr {
51 } => match reduce_ty(cx, from_sub_ty) {
52 ReducedTy::TypeErasure => break,
53 ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break,
54 ReducedTy::Ref(from_sub_ty) => {
55 from_ty = from_sub_ty;
62 TRANSMUTE_UNDEFINED_REPR,
64 &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
66 if to_ty_orig.peel_refs() != unsized_ty {
67 diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
77 } => match reduce_ty(cx, from_sub_ty) {
78 ReducedTy::UnorderedFields(from_ty) => {
81 TRANSMUTE_UNDEFINED_REPR,
83 &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
85 if from_ty_orig.peel_refs() != from_ty {
86 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
92 ReducedTy::Ref(from_sub_ty) => {
93 from_ty = from_sub_ty;
100 from_ty: from_sub_ty,
102 } => match reduce_ty(cx, to_sub_ty) {
103 ReducedTy::UnorderedFields(to_ty) => {
106 TRANSMUTE_UNDEFINED_REPR,
108 &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
110 if to_ty_orig.peel_refs() != to_ty {
111 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
117 ReducedTy::Ref(to_sub_ty) => {
118 from_ty = from_sub_ty;
125 from_ty: from_sub_ty,
127 } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) {
128 (ReducedTy::TypeErasure, _) | (_, ReducedTy::TypeErasure) => return false,
129 (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => {
130 let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs))
131 = (from_ty.kind(), to_ty.kind())
132 && from_def == to_def
134 if same_except_params(from_subs, to_subs) {
143 TRANSMUTE_UNDEFINED_REPR,
146 "transmute from `{}` to `{}`, both of which have an undefined layout",
147 from_ty_orig, to_ty_orig
150 if let Some(same_adt_did) = same_adt_did {
152 "two instances of the same generic type (`{}`) may have different layouts",
153 cx.tcx.item_name(same_adt_did)
156 if from_ty_orig.peel_refs() != from_ty {
157 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
159 if to_ty_orig.peel_refs() != to_ty {
160 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
168 ReducedTy::UnorderedFields(from_ty),
169 ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
173 TRANSMUTE_UNDEFINED_REPR,
175 &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
177 if from_ty_orig.peel_refs() != from_ty {
178 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
185 ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
186 ReducedTy::UnorderedFields(to_ty),
190 TRANSMUTE_UNDEFINED_REPR,
192 &format!("transmute into `{}` which has an undefined layout", to_ty_orig),
194 if to_ty_orig.peel_refs() != to_ty {
195 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
201 (ReducedTy::Ref(from_sub_ty), ReducedTy::Ref(to_sub_ty)) => {
202 from_ty = from_sub_ty;
207 ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param,
208 ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param,
211 ReducedTy::UnorderedFields(_) | ReducedTy::Param,
212 ReducedTy::UnorderedFields(_) | ReducedTy::Param,
221 enum ReducedTys<'tcx> {
222 FromFatPtr { unsized_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
223 ToFatPtr { unsized_ty: Ty<'tcx>, from_ty: Ty<'tcx> },
224 ToPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
225 FromPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
226 Other { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
229 /// Remove references so long as both types are references.
230 fn reduce_refs<'tcx>(
231 cx: &LateContext<'tcx>,
233 mut from_ty: Ty<'tcx>,
235 ) -> ReducedTys<'tcx> {
237 return match (from_ty.kind(), to_ty.kind()) {
239 &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })),
240 &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })),
242 from_ty = from_sub_ty;
246 (&(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })), _)
247 if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
249 ReducedTys::FromFatPtr { unsized_ty, to_ty }
251 (_, &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })))
252 if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
254 ReducedTys::ToFatPtr { unsized_ty, from_ty }
256 (&(ty::Ref(_, from_ty, _) | ty::RawPtr(TypeAndMut { ty: from_ty, .. })), _) => {
257 ReducedTys::FromPtr { from_ty, to_ty }
259 (_, &(ty::Ref(_, to_ty, _) | ty::RawPtr(TypeAndMut { ty: to_ty, .. }))) => {
260 ReducedTys::ToPtr { from_ty, to_ty }
262 _ => ReducedTys::Other { from_ty, to_ty },
267 enum ReducedTy<'tcx> {
268 /// The type can be used for type erasure.
270 /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero
271 /// sized fields with a defined order.
272 OrderedFields(Ty<'tcx>),
273 /// The type is a struct containing multiple non-zero sized fields with no defined order.
274 UnorderedFields(Ty<'tcx>),
275 /// The type is a reference to the contained type.
277 /// The type is a generic parameter.
283 /// Reduce structs containing a single non-zero sized field to it's contained type.
284 fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> {
286 ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
287 return match *ty.kind() {
288 ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::TypeErasure,
289 ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
293 ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure,
295 let mut iter = args.iter();
296 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
297 return ReducedTy::OrderedFields(ty);
299 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
303 ReducedTy::UnorderedFields(ty)
305 ty::Adt(def, substs) if def.is_struct() => {
310 .map(|f| cx.tcx.type_of(f.did).subst(cx.tcx, substs));
311 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
312 return ReducedTy::TypeErasure;
314 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
318 if def.repr().inhibit_struct_field_reordering_opt() {
319 ReducedTy::OrderedFields(ty)
321 ReducedTy::UnorderedFields(ty)
324 ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => {
325 ReducedTy::TypeErasure
327 // TODO: Check if the conversion to or from at least one of a union's fields is valid.
328 ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure,
329 ty::Foreign(_) => ReducedTy::TypeErasure,
330 ty::Ref(_, ty, _) => ReducedTy::Ref(ty),
331 ty::RawPtr(ty) => ReducedTy::Ref(ty.ty),
332 ty::Param(_) => ReducedTy::Param,
333 _ => ReducedTy::Other(ty),
338 fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
340 if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty);
341 if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty));
343 layout.layout.size().bytes() == 0
350 fn is_size_pair(ty: Ty<'_>) -> bool {
351 if let ty::Tuple(tys) = *ty.kind()
352 && let [ty1, ty2] = &**tys
354 matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
355 && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
361 fn same_except_params(subs1: SubstsRef<'_>, subs2: SubstsRef<'_>) -> bool {
362 // TODO: check const parameters as well. Currently this will consider `Array<5>` the same as
364 for (ty1, ty2) in subs1.types().zip(subs2.types()).filter(|(ty1, ty2)| ty1 != ty2) {
365 match (ty1.kind(), ty2.kind()) {
366 (ty::Param(_), _) | (_, ty::Param(_)) => (),
367 (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2)) if adt1 == adt2 && same_except_params(subs1, subs2) => (),