]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs
Auto merge of #104673 - matthiaskrgr:rollup-85f65ov, r=matthiaskrgr
[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             (
31                 ReducedTy::OrderedFields(_, Some(from_sub_ty)),
32                 ReducedTy::UnorderedFields(to_sub_ty),
33             )
34             | (
35                 ReducedTy::UnorderedFields(from_sub_ty),
36                 ReducedTy::OrderedFields(_, Some(to_sub_ty)),
37             ) => {
38                 from_ty = from_sub_ty;
39                 to_ty = to_sub_ty;
40                 continue;
41             }
42             (ReducedTy::OrderedFields(_, Some(from_sub_ty)), ReducedTy::Other(to_sub_ty))
43                 if reduced_tys.to_fat_ptr =>
44             {
45                 from_ty = from_sub_ty;
46                 to_ty = to_sub_ty;
47                 continue;
48             }
49             (ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(_, Some(to_sub_ty)))
50                 if reduced_tys.from_fat_ptr =>
51             {
52                 from_ty = from_sub_ty;
53                 to_ty = to_sub_ty;
54                 continue;
55             }
56
57             // ptr <-> ptr
58             (ReducedTy::Other(from_sub_ty), ReducedTy::Other(to_sub_ty))
59                 if matches!(from_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_))
60                     && matches!(to_sub_ty.kind(), ty::Ref(..) | ty::RawPtr(_)) =>
61             {
62                 from_ty = from_sub_ty;
63                 to_ty = to_sub_ty;
64                 continue;
65             }
66
67             // fat ptr <-> (*size, *size)
68             (ReducedTy::Other(_), ReducedTy::UnorderedFields(to_ty))
69                 if reduced_tys.from_fat_ptr && is_size_pair(to_ty) =>
70             {
71                 return false;
72             }
73             (ReducedTy::UnorderedFields(from_ty), ReducedTy::Other(_))
74                 if reduced_tys.to_fat_ptr && is_size_pair(from_ty) =>
75             {
76                 return false;
77             }
78
79             // fat ptr -> some struct | some struct -> fat ptr
80             (ReducedTy::Other(_), _) if reduced_tys.from_fat_ptr => {
81                 span_lint_and_then(
82                     cx,
83                     TRANSMUTE_UNDEFINED_REPR,
84                     e.span,
85                     &format!("transmute from `{from_ty_orig}` which has an undefined layout"),
86                     |diag| {
87                         if from_ty_orig.peel_refs() != from_ty.peel_refs() {
88                             diag.note(&format!(
89                                 "the contained type `{from_ty}` has an undefined layout"
90                             ));
91                         }
92                     },
93                 );
94                 return true;
95             }
96             (_, ReducedTy::Other(_)) if reduced_tys.to_fat_ptr => {
97                 span_lint_and_then(
98                     cx,
99                     TRANSMUTE_UNDEFINED_REPR,
100                     e.span,
101                     &format!("transmute to `{to_ty_orig}` which has an undefined layout"),
102                     |diag| {
103                         if to_ty_orig.peel_refs() != to_ty.peel_refs() {
104                             diag.note(&format!(
105                                 "the contained type `{to_ty}` has an undefined layout"
106                             ));
107                         }
108                     },
109                 );
110                 return true;
111             }
112
113             (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty))
114                 if from_ty != to_ty =>
115             {
116                 let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs))
117                         = (from_ty.kind(), to_ty.kind())
118                         && from_def == to_def
119                     {
120                         if same_except_params(from_subs, to_subs) {
121                             return false;
122                         }
123                         Some(from_def.did())
124                     } else {
125                         None
126                     };
127                 span_lint_and_then(
128                     cx,
129                     TRANSMUTE_UNDEFINED_REPR,
130                     e.span,
131                     &format!(
132                         "transmute from `{from_ty_orig}` to `{to_ty_orig}`, both of which have an undefined layout"
133                     ),
134                     |diag| {
135                         if let Some(same_adt_did) = same_adt_did {
136                             diag.note(&format!(
137                                 "two instances of the same generic type (`{}`) may have different layouts",
138                                 cx.tcx.item_name(same_adt_did)
139                             ));
140                         } else {
141                             if from_ty_orig.peel_refs() != from_ty {
142                                 diag.note(&format!(
143                                     "the contained type `{from_ty}` has an undefined layout"
144                                 ));
145                             }
146                             if to_ty_orig.peel_refs() != to_ty {
147                                 diag.note(&format!(
148                                     "the contained type `{to_ty}` has an undefined layout"
149                                 ));
150                             }
151                         }
152                     },
153                 );
154                 return true;
155             }
156             (
157                 ReducedTy::UnorderedFields(from_ty),
158                 ReducedTy::Other(_)
159                 | ReducedTy::OrderedFields(..)
160                 | ReducedTy::TypeErasure { raw_ptr_only: true },
161             ) => {
162                 span_lint_and_then(
163                     cx,
164                     TRANSMUTE_UNDEFINED_REPR,
165                     e.span,
166                     &format!("transmute from `{from_ty_orig}` which has an undefined layout"),
167                     |diag| {
168                         if from_ty_orig.peel_refs() != from_ty {
169                             diag.note(&format!(
170                                 "the contained type `{from_ty}` has an undefined layout"
171                             ));
172                         }
173                     },
174                 );
175                 return true;
176             }
177             (
178                 ReducedTy::Other(_)
179                 | ReducedTy::OrderedFields(..)
180                 | ReducedTy::TypeErasure { raw_ptr_only: true },
181                 ReducedTy::UnorderedFields(to_ty),
182             ) => {
183                 span_lint_and_then(
184                     cx,
185                     TRANSMUTE_UNDEFINED_REPR,
186                     e.span,
187                     &format!("transmute into `{to_ty_orig}` which has an undefined layout"),
188                     |diag| {
189                         if to_ty_orig.peel_refs() != to_ty {
190                             diag.note(&format!(
191                                 "the contained type `{to_ty}` has an undefined layout"
192                             ));
193                         }
194                     },
195                 );
196                 return true;
197             }
198             (
199                 ReducedTy::OrderedFields(..)
200                 | ReducedTy::Other(_)
201                 | ReducedTy::TypeErasure { raw_ptr_only: true },
202                 ReducedTy::OrderedFields(..)
203                 | ReducedTy::Other(_)
204                 | ReducedTy::TypeErasure { raw_ptr_only: true },
205             )
206             | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => {
207                 break;
208             }
209         }
210     }
211
212     false
213 }
214
215 #[expect(clippy::struct_excessive_bools)]
216 struct ReducedTys<'tcx> {
217     from_ty: Ty<'tcx>,
218     to_ty: Ty<'tcx>,
219     from_raw_ptr: bool,
220     to_raw_ptr: bool,
221     from_fat_ptr: bool,
222     to_fat_ptr: bool,
223 }
224
225 /// Remove references so long as both types are references.
226 fn reduce_refs<'tcx>(
227     cx: &LateContext<'tcx>,
228     mut from_ty: Ty<'tcx>,
229     mut to_ty: Ty<'tcx>,
230 ) -> ReducedTys<'tcx> {
231     let mut from_raw_ptr = false;
232     let mut to_raw_ptr = false;
233     let (from_fat_ptr, to_fat_ptr) =
234         loop {
235             break match (from_ty.kind(), to_ty.kind()) {
236                 (
237                     &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })),
238                     &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })),
239                 ) => {
240                     from_raw_ptr = matches!(*from_ty.kind(), ty::RawPtr(_));
241                     from_ty = from_sub_ty;
242                     to_raw_ptr = matches!(*to_ty.kind(), ty::RawPtr(_));
243                     to_ty = to_sub_ty;
244                     continue;
245                 }
246                 (
247                     &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })),
248                     _,
249                 ) if !unsized_ty.is_sized(cx.tcx, cx.param_env) => (true, false),
250                 (
251                     _,
252                     &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })),
253                 ) if !unsized_ty.is_sized(cx.tcx, cx.param_env) => (false, true),
254                 _ => (false, false),
255             };
256         };
257     ReducedTys { from_ty, to_ty, from_raw_ptr, to_raw_ptr, from_fat_ptr, to_fat_ptr }
258 }
259
260 enum ReducedTy<'tcx> {
261     /// The type can be used for type erasure.
262     TypeErasure { raw_ptr_only: bool },
263     /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero
264     /// sized fields with a defined order.
265     /// The second value is the first non-zero sized type.
266     OrderedFields(Ty<'tcx>, Option<Ty<'tcx>>),
267     /// The type is a struct containing multiple non-zero sized fields with no defined order.
268     UnorderedFields(Ty<'tcx>),
269     /// Any other type.
270     Other(Ty<'tcx>),
271 }
272
273 /// Reduce structs containing a single non-zero sized field to it's contained type.
274 fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> {
275     loop {
276         ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
277         return match *ty.kind() {
278             ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => {
279                 ReducedTy::TypeErasure { raw_ptr_only: false }
280             }
281             ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
282                 ty = sub_ty;
283                 continue;
284             }
285             ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure { raw_ptr_only: false },
286             ty::Tuple(args) => {
287                 let mut iter = args.iter();
288                 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
289                     return ReducedTy::OrderedFields(ty, None);
290                 };
291                 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
292                     ty = sized_ty;
293                     continue;
294                 }
295                 ReducedTy::UnorderedFields(ty)
296             }
297             ty::Adt(def, substs) if def.is_struct() => {
298                 let mut iter = def
299                     .non_enum_variant()
300                     .fields
301                     .iter()
302                     .map(|f| cx.tcx.bound_type_of(f.did).subst(cx.tcx, substs));
303                 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
304                     return ReducedTy::TypeErasure { raw_ptr_only: false };
305                 };
306                 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
307                     ty = sized_ty;
308                     continue;
309                 }
310                 if def.repr().inhibit_struct_field_reordering_opt() {
311                     ReducedTy::OrderedFields(ty, Some(sized_ty))
312                 } else {
313                     ReducedTy::UnorderedFields(ty)
314                 }
315             }
316             ty::Adt(def, _)
317                 if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) =>
318             {
319                 ReducedTy::TypeErasure { raw_ptr_only: false }
320             }
321             // TODO: Check if the conversion to or from at least one of a union's fields is valid.
322             ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure { raw_ptr_only: false },
323             ty::Foreign(_) | ty::Param(_) => ReducedTy::TypeErasure { raw_ptr_only: false },
324             ty::Int(_) | ty::Uint(_) => ReducedTy::TypeErasure { raw_ptr_only: true },
325             _ => ReducedTy::Other(ty),
326         };
327     }
328 }
329
330 fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
331     if_chain! {
332         if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty);
333         if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty));
334         then {
335             layout.layout.size().bytes() == 0
336         } else {
337             false
338         }
339     }
340 }
341
342 fn is_size_pair(ty: Ty<'_>) -> bool {
343     if let ty::Tuple(tys) = *ty.kind()
344         && let [ty1, ty2] = &**tys
345     {
346         matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
347             && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
348     } else {
349         false
350     }
351 }
352
353 fn same_except_params<'tcx>(subs1: SubstsRef<'tcx>, subs2: SubstsRef<'tcx>) -> bool {
354     // TODO: check const parameters as well. Currently this will consider `Array<5>` the same as
355     // `Array<6>`
356     for (ty1, ty2) in subs1.types().zip(subs2.types()).filter(|(ty1, ty2)| ty1 != ty2) {
357         match (ty1.kind(), ty2.kind()) {
358             (ty::Param(_), _) | (_, ty::Param(_)) => (),
359             (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2))
360                 if adt1 == adt2 && same_except_params(subs1, subs2) =>
361             {
362                 ()
363             }
364             _ => return false,
365         }
366     }
367     true
368 }