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