]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs
Merge commit 'd0cf3481a84e3aa68c2f185c460e282af36ebc42' into clippyup
[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::subst::{Subst, SubstsRef};
7 use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy};
8 use rustc_span::Span;
9
10 #[allow(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         match reduce_refs(cx, e.span, from_ty, to_ty) {
22             ReducedTys::FromFatPtr {
23                 unsized_ty,
24                 to_ty: to_sub_ty,
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) => {
29                     from_ty = unsized_ty;
30                     to_ty = to_sub_ty;
31                     continue;
32                 },
33                 _ => {
34                     span_lint_and_then(
35                         cx,
36                         TRANSMUTE_UNDEFINED_REPR,
37                         e.span,
38                         &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
39                         |diag| {
40                             if from_ty_orig.peel_refs() != unsized_ty {
41                                 diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
42                             }
43                         },
44                     );
45                     return true;
46                 },
47             },
48             ReducedTys::ToFatPtr {
49                 unsized_ty,
50                 from_ty: from_sub_ty,
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;
56                     to_ty = unsized_ty;
57                     continue;
58                 },
59                 _ => {
60                     span_lint_and_then(
61                         cx,
62                         TRANSMUTE_UNDEFINED_REPR,
63                         e.span,
64                         &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
65                         |diag| {
66                             if to_ty_orig.peel_refs() != unsized_ty {
67                                 diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
68                             }
69                         },
70                     );
71                     return true;
72                 },
73             },
74             ReducedTys::ToPtr {
75                 from_ty: from_sub_ty,
76                 to_ty: to_sub_ty,
77             } => match reduce_ty(cx, from_sub_ty) {
78                 ReducedTy::UnorderedFields(from_ty) => {
79                     span_lint_and_then(
80                         cx,
81                         TRANSMUTE_UNDEFINED_REPR,
82                         e.span,
83                         &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
84                         |diag| {
85                             if from_ty_orig.peel_refs() != from_ty {
86                                 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
87                             }
88                         },
89                     );
90                     return true;
91                 },
92                 ReducedTy::Ref(from_sub_ty) => {
93                     from_ty = from_sub_ty;
94                     to_ty = to_sub_ty;
95                     continue;
96                 },
97                 _ => break,
98             },
99             ReducedTys::FromPtr {
100                 from_ty: from_sub_ty,
101                 to_ty: to_sub_ty,
102             } => match reduce_ty(cx, to_sub_ty) {
103                 ReducedTy::UnorderedFields(to_ty) => {
104                     span_lint_and_then(
105                         cx,
106                         TRANSMUTE_UNDEFINED_REPR,
107                         e.span,
108                         &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
109                         |diag| {
110                             if to_ty_orig.peel_refs() != to_ty {
111                                 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
112                             }
113                         },
114                     );
115                     return true;
116                 },
117                 ReducedTy::Ref(to_sub_ty) => {
118                     from_ty = from_sub_ty;
119                     to_ty = to_sub_ty;
120                     continue;
121                 },
122                 _ => break,
123             },
124             ReducedTys::Other {
125                 from_ty: from_sub_ty,
126                 to_ty: to_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
133                     {
134                         if same_except_params(from_subs, to_subs) {
135                             return false;
136                         }
137                         Some(from_def.did())
138                     } else {
139                         None
140                     };
141                     span_lint_and_then(
142                         cx,
143                         TRANSMUTE_UNDEFINED_REPR,
144                         e.span,
145                         &format!(
146                             "transmute from `{}` to `{}`, both of which have an undefined layout",
147                             from_ty_orig, to_ty_orig
148                         ),
149                         |diag| {
150                             if let Some(same_adt_did) = same_adt_did {
151                                 diag.note(&format!(
152                                     "two instances of the same generic type (`{}`) may have different layouts",
153                                     cx.tcx.item_name(same_adt_did)
154                                 ));
155                             } else {
156                                 if from_ty_orig.peel_refs() != from_ty {
157                                     diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
158                                 }
159                                 if to_ty_orig.peel_refs() != to_ty {
160                                     diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
161                                 }
162                             }
163                         },
164                     );
165                     return true;
166                 },
167                 (
168                     ReducedTy::UnorderedFields(from_ty),
169                     ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
170                 ) => {
171                     span_lint_and_then(
172                         cx,
173                         TRANSMUTE_UNDEFINED_REPR,
174                         e.span,
175                         &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
176                         |diag| {
177                             if from_ty_orig.peel_refs() != from_ty {
178                                 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
179                             }
180                         },
181                     );
182                     return true;
183                 },
184                 (
185                     ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
186                     ReducedTy::UnorderedFields(to_ty),
187                 ) => {
188                     span_lint_and_then(
189                         cx,
190                         TRANSMUTE_UNDEFINED_REPR,
191                         e.span,
192                         &format!("transmute into `{}` which has an undefined layout", to_ty_orig),
193                         |diag| {
194                             if to_ty_orig.peel_refs() != to_ty {
195                                 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
196                             }
197                         },
198                     );
199                     return true;
200                 },
201                 (ReducedTy::Ref(from_sub_ty), ReducedTy::Ref(to_sub_ty)) => {
202                     from_ty = from_sub_ty;
203                     to_ty = to_sub_ty;
204                     continue;
205                 },
206                 (
207                     ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param,
208                     ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param,
209                 )
210                 | (
211                     ReducedTy::UnorderedFields(_) | ReducedTy::Param,
212                     ReducedTy::UnorderedFields(_) | ReducedTy::Param,
213                 ) => break,
214             },
215         }
216     }
217
218     false
219 }
220
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> },
227 }
228
229 /// Remove references so long as both types are references.
230 fn reduce_refs<'tcx>(
231     cx: &LateContext<'tcx>,
232     span: Span,
233     mut from_ty: Ty<'tcx>,
234     mut to_ty: Ty<'tcx>,
235 ) -> ReducedTys<'tcx> {
236     loop {
237         return match (from_ty.kind(), to_ty.kind()) {
238             (
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, .. })),
241             ) => {
242                 from_ty = from_sub_ty;
243                 to_ty = to_sub_ty;
244                 continue;
245             },
246             (&(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })), _)
247                 if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
248             {
249                 ReducedTys::FromFatPtr { unsized_ty, to_ty }
250             },
251             (_, &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })))
252                 if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
253             {
254                 ReducedTys::ToFatPtr { unsized_ty, from_ty }
255             },
256             (&(ty::Ref(_, from_ty, _) | ty::RawPtr(TypeAndMut { ty: from_ty, .. })), _) => {
257                 ReducedTys::FromPtr { from_ty, to_ty }
258             },
259             (_, &(ty::Ref(_, to_ty, _) | ty::RawPtr(TypeAndMut { ty: to_ty, .. }))) => {
260                 ReducedTys::ToPtr { from_ty, to_ty }
261             },
262             _ => ReducedTys::Other { from_ty, to_ty },
263         };
264     }
265 }
266
267 enum ReducedTy<'tcx> {
268     /// The type can be used for type erasure.
269     TypeErasure,
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.
276     Ref(Ty<'tcx>),
277     /// The type is a generic parameter.
278     Param,
279     /// Any other type.
280     Other(Ty<'tcx>),
281 }
282
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> {
285     loop {
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) => {
290                 ty = sub_ty;
291                 continue;
292             },
293             ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure,
294             ty::Tuple(args) => {
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);
298                 };
299                 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
300                     ty = sized_ty;
301                     continue;
302                 }
303                 ReducedTy::UnorderedFields(ty)
304             },
305             ty::Adt(def, substs) if def.is_struct() => {
306                 let mut iter = def
307                     .non_enum_variant()
308                     .fields
309                     .iter()
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;
313                 };
314                 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
315                     ty = sized_ty;
316                     continue;
317                 }
318                 if def.repr().inhibit_struct_field_reordering_opt() {
319                     ReducedTy::OrderedFields(ty)
320                 } else {
321                     ReducedTy::UnorderedFields(ty)
322                 }
323             },
324             ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => {
325                 ReducedTy::TypeErasure
326             },
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),
334         };
335     }
336 }
337
338 fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
339     if_chain! {
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));
342         then {
343             layout.layout.size().bytes() == 0
344         } else {
345             false
346         }
347     }
348 }
349
350 fn is_size_pair(ty: Ty<'_>) -> bool {
351     if let ty::Tuple(tys) = *ty.kind()
352         && let [ty1, ty2] = &**tys
353     {
354         matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
355             && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
356     } else {
357         false
358     }
359 }
360
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
363     // `Array<6>`
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) => (),
368             _ => return false,
369         }
370     }
371     true
372 }