]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/transmute/transmute_undefined_repr.rs
remove the `Subst` trait, always use `EarlyBinder`
[rust.git] / clippy_lints / src / transmute / transmute_undefined_repr.rs
1 use super::TRANSMUTE_UNDEFINED_REPR;
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::ty::is_c_void;
4 use rustc_hir::Expr;
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;
9
10 #[expect(clippy::too_many_lines)]
11 pub(super) fn check<'tcx>(
12     cx: &LateContext<'tcx>,
13     e: &'tcx Expr<'_>,
14     from_ty_orig: Ty<'tcx>,
15     to_ty_orig: Ty<'tcx>,
16 ) -> bool {
17     let mut from_ty = cx.tcx.erase_regions(from_ty_orig);
18     let mut to_ty = cx.tcx.erase_regions(to_ty_orig);
19
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,
28
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;
34                 to_ty = to_sub_ty;
35                 continue;
36             },
37             (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => {
38                 from_ty = from_sub_ty;
39                 to_ty = to_sub_ty;
40                 continue;
41             },
42             (ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty)))
43                 if reduced_tys.from_fat_ptr =>
44             {
45                 from_ty = from_sub_ty;
46                 to_ty = to_sub_ty;
47                 continue;
48             },
49
50             // ptr <-> ptr
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(_)) =>
54             {
55                 from_ty = from_sub_ty;
56                 to_ty = to_sub_ty;
57                 continue;
58             },
59
60             // fat ptr <-> (*size, *size)
61             (ReducedTy::Other(_), ReducedTy::UnorderedFields(to_ty))
62                 if reduced_tys.from_fat_ptr && is_size_pair(to_ty) =>
63             {
64                 return false;
65             },
66             (ReducedTy::UnorderedFields(from_ty), ReducedTy::Other(_))
67                 if reduced_tys.to_fat_ptr && is_size_pair(from_ty) =>
68             {
69                 return false;
70             },
71
72             // fat ptr -> some struct | some struct -> fat ptr
73             (ReducedTy::Other(_), _) if reduced_tys.from_fat_ptr => {
74                 span_lint_and_then(
75                     cx,
76                     TRANSMUTE_UNDEFINED_REPR,
77                     e.span,
78                     &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
79                     |diag| {
80                         if from_ty_orig.peel_refs() != from_ty.peel_refs() {
81                             diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
82                         }
83                     },
84                 );
85                 return true;
86             },
87             (_, ReducedTy::Other(_)) if reduced_tys.to_fat_ptr => {
88                 span_lint_and_then(
89                     cx,
90                     TRANSMUTE_UNDEFINED_REPR,
91                     e.span,
92                     &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
93                     |diag| {
94                         if to_ty_orig.peel_refs() != to_ty.peel_refs() {
95                             diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
96                         }
97                     },
98                 );
99                 return true;
100             },
101
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
106                     {
107                         if same_except_params(from_subs, to_subs) {
108                             return false;
109                         }
110                         Some(from_def.did())
111                     } else {
112                         None
113                     };
114                 span_lint_and_then(
115                     cx,
116                     TRANSMUTE_UNDEFINED_REPR,
117                     e.span,
118                     &format!(
119                         "transmute from `{}` to `{}`, both of which have an undefined layout",
120                         from_ty_orig, to_ty_orig
121                     ),
122                     |diag| {
123                         if let Some(same_adt_did) = same_adt_did {
124                             diag.note(&format!(
125                                 "two instances of the same generic type (`{}`) may have different layouts",
126                                 cx.tcx.item_name(same_adt_did)
127                             ));
128                         } else {
129                             if from_ty_orig.peel_refs() != from_ty {
130                                 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
131                             }
132                             if to_ty_orig.peel_refs() != to_ty {
133                                 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
134                             }
135                         }
136                     },
137                 );
138                 return true;
139             },
140             (
141                 ReducedTy::UnorderedFields(from_ty),
142                 ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
143             ) => {
144                 span_lint_and_then(
145                     cx,
146                     TRANSMUTE_UNDEFINED_REPR,
147                     e.span,
148                     &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
149                     |diag| {
150                         if from_ty_orig.peel_refs() != from_ty {
151                             diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
152                         }
153                     },
154                 );
155                 return true;
156             },
157             (
158                 ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
159                 ReducedTy::UnorderedFields(to_ty),
160             ) => {
161                 span_lint_and_then(
162                     cx,
163                     TRANSMUTE_UNDEFINED_REPR,
164                     e.span,
165                     &format!("transmute into `{}` which has an undefined layout", to_ty_orig),
166                     |diag| {
167                         if to_ty_orig.peel_refs() != to_ty {
168                             diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
169                         }
170                     },
171                 );
172                 return true;
173             },
174             (
175                 ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true },
176                 ReducedTy::OrderedFields(..) | ReducedTy::Other(_) | ReducedTy::TypeErasure { raw_ptr_only: true },
177             )
178             | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => {
179                 break;
180             },
181         }
182     }
183
184     false
185 }
186
187 #[expect(clippy::struct_excessive_bools)]
188 struct ReducedTys<'tcx> {
189     from_ty: Ty<'tcx>,
190     to_ty: Ty<'tcx>,
191     from_raw_ptr: bool,
192     to_raw_ptr: bool,
193     from_fat_ptr: bool,
194     to_fat_ptr: bool,
195 }
196
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()) {
203             (
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, .. })),
206             ) => {
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(_));
210                 to_ty = to_sub_ty;
211                 continue;
212             },
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) =>
215             {
216                 (true, false)
217             },
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) =>
220             {
221                 (false, true)
222             },
223             _ => (false, false),
224         };
225     };
226     ReducedTys {
227         from_ty,
228         to_ty,
229         from_raw_ptr,
230         to_raw_ptr,
231         from_fat_ptr,
232         to_fat_ptr,
233     }
234 }
235
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>),
245     /// Any other type.
246     Other(Ty<'tcx>),
247 }
248
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> {
251     loop {
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 }
256             },
257             ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
258                 ty = sub_ty;
259                 continue;
260             },
261             ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure { raw_ptr_only: false },
262             ty::Tuple(args) => {
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);
266                 };
267                 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
268                     ty = sized_ty;
269                     continue;
270                 }
271                 ReducedTy::UnorderedFields(ty)
272             },
273             ty::Adt(def, substs) if def.is_struct() => {
274                 let mut iter = def
275                     .non_enum_variant()
276                     .fields
277                     .iter()
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 };
281                 };
282                 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
283                     ty = sized_ty;
284                     continue;
285                 }
286                 if def.repr().inhibit_struct_field_reordering_opt() {
287                     ReducedTy::OrderedFields(ty, Some(sized_ty))
288                 } else {
289                     ReducedTy::UnorderedFields(ty)
290                 }
291             },
292             ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => {
293                 ReducedTy::TypeErasure { raw_ptr_only: false }
294             },
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),
300         };
301     }
302 }
303
304 fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
305     if_chain! {
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));
308         then {
309             layout.layout.size().bytes() == 0
310         } else {
311             false
312         }
313     }
314 }
315
316 fn is_size_pair(ty: Ty<'_>) -> bool {
317     if let ty::Tuple(tys) = *ty.kind()
318         && let [ty1, ty2] = &**tys
319     {
320         matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
321             && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
322     } else {
323         false
324     }
325 }
326
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
329     // `Array<6>`
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) => (),
334             _ => return false,
335         }
336     }
337     true
338 }