]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs
Merge commit 'e329249b6a3a98830d860c74c8234a8dd9407436' 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;
7 use rustc_middle::ty::{self, Ty, TypeAndMut};
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::IntArray | ReducedTy::TypeErasure => break,
27                 ReducedTy::Ref(to_sub_ty) => {
28                     from_ty = unsized_ty;
29                     to_ty = to_sub_ty;
30                     continue;
31                 },
32                 _ => {
33                     span_lint_and_then(
34                         cx,
35                         TRANSMUTE_UNDEFINED_REPR,
36                         e.span,
37                         &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
38                         |diag| {
39                             if from_ty_orig.peel_refs() != unsized_ty {
40                                 diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
41                             }
42                         },
43                     );
44                     return true;
45                 },
46             },
47             ReducedTys::ToFatPtr {
48                 unsized_ty,
49                 from_ty: from_sub_ty,
50             } => match reduce_ty(cx, from_sub_ty) {
51                 ReducedTy::IntArray | ReducedTy::TypeErasure => break,
52                 ReducedTy::Ref(from_sub_ty) => {
53                     from_ty = from_sub_ty;
54                     to_ty = unsized_ty;
55                     continue;
56                 },
57                 _ => {
58                     span_lint_and_then(
59                         cx,
60                         TRANSMUTE_UNDEFINED_REPR,
61                         e.span,
62                         &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
63                         |diag| {
64                             if to_ty_orig.peel_refs() != unsized_ty {
65                                 diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty));
66                             }
67                         },
68                     );
69                     return true;
70                 },
71             },
72             ReducedTys::ToPtr {
73                 from_ty: from_sub_ty,
74                 to_ty: to_sub_ty,
75             } => match reduce_ty(cx, from_sub_ty) {
76                 ReducedTy::UnorderedFields(from_ty) => {
77                     span_lint_and_then(
78                         cx,
79                         TRANSMUTE_UNDEFINED_REPR,
80                         e.span,
81                         &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
82                         |diag| {
83                             if from_ty_orig.peel_refs() != from_ty {
84                                 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
85                             }
86                         },
87                     );
88                     return true;
89                 },
90                 ReducedTy::Ref(from_sub_ty) => {
91                     from_ty = from_sub_ty;
92                     to_ty = to_sub_ty;
93                     continue;
94                 },
95                 _ => break,
96             },
97             ReducedTys::FromPtr {
98                 from_ty: from_sub_ty,
99                 to_ty: to_sub_ty,
100             } => match reduce_ty(cx, to_sub_ty) {
101                 ReducedTy::UnorderedFields(to_ty) => {
102                     span_lint_and_then(
103                         cx,
104                         TRANSMUTE_UNDEFINED_REPR,
105                         e.span,
106                         &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
107                         |diag| {
108                             if to_ty_orig.peel_refs() != to_ty {
109                                 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
110                             }
111                         },
112                     );
113                     return true;
114                 },
115                 ReducedTy::Ref(to_sub_ty) => {
116                     from_ty = from_sub_ty;
117                     to_ty = to_sub_ty;
118                     continue;
119                 },
120                 _ => break,
121             },
122             ReducedTys::Other {
123                 from_ty: from_sub_ty,
124                 to_ty: to_sub_ty,
125             } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) {
126                 (ReducedTy::IntArray | ReducedTy::TypeErasure, _)
127                 | (_, ReducedTy::IntArray | ReducedTy::TypeErasure) => return false,
128                 (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => {
129                     span_lint_and_then(
130                         cx,
131                         TRANSMUTE_UNDEFINED_REPR,
132                         e.span,
133                         &format!(
134                             "transmute from `{}` to `{}`, both of which have an undefined layout",
135                             from_ty_orig, to_ty_orig
136                         ),
137                         |diag| {
138                             if_chain! {
139                                 if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def());
140                                 if from_def == to_def;
141                                 then {
142                                     diag.note(&format!(
143                                         "two instances of the same generic type (`{}`) may have different layouts",
144                                         cx.tcx.item_name(from_def.did)
145                                     ));
146                                 } else {
147                                     if from_ty_orig.peel_refs() != from_ty {
148                                         diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
149                                     }
150                                     if to_ty_orig.peel_refs() != to_ty {
151                                         diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
152                                     }
153                                 }
154                             }
155                         },
156                     );
157                     return true;
158                 },
159                 (
160                     ReducedTy::UnorderedFields(from_ty),
161                     ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
162                 ) => {
163                     span_lint_and_then(
164                         cx,
165                         TRANSMUTE_UNDEFINED_REPR,
166                         e.span,
167                         &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
168                         |diag| {
169                             if from_ty_orig.peel_refs() != from_ty {
170                                 diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
171                             }
172                         },
173                     );
174                     return true;
175                 },
176                 (
177                     ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_),
178                     ReducedTy::UnorderedFields(to_ty),
179                 ) => {
180                     span_lint_and_then(
181                         cx,
182                         TRANSMUTE_UNDEFINED_REPR,
183                         e.span,
184                         &format!("transmute into `{}` which has an undefined layout", to_ty_orig),
185                         |diag| {
186                             if to_ty_orig.peel_refs() != to_ty {
187                                 diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
188                             }
189                         },
190                     );
191                     return true;
192                 },
193                 (ReducedTy::Ref(from_sub_ty), ReducedTy::Ref(to_sub_ty)) => {
194                     from_ty = from_sub_ty;
195                     to_ty = to_sub_ty;
196                     continue;
197                 },
198                 (
199                     ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
200                     ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
201                 )
202                 | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => break,
203             },
204         }
205     }
206
207     false
208 }
209
210 enum ReducedTys<'tcx> {
211     FromFatPtr { unsized_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
212     ToFatPtr { unsized_ty: Ty<'tcx>, from_ty: Ty<'tcx> },
213     ToPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
214     FromPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
215     Other { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> },
216 }
217
218 /// Remove references so long as both types are references.
219 fn reduce_refs<'tcx>(
220     cx: &LateContext<'tcx>,
221     span: Span,
222     mut from_ty: Ty<'tcx>,
223     mut to_ty: Ty<'tcx>,
224 ) -> ReducedTys<'tcx> {
225     loop {
226         return match (from_ty.kind(), to_ty.kind()) {
227             (
228                 &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })),
229                 &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })),
230             ) => {
231                 from_ty = from_sub_ty;
232                 to_ty = to_sub_ty;
233                 continue;
234             },
235             (&(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })), _)
236                 if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
237             {
238                 ReducedTys::FromFatPtr { unsized_ty, to_ty }
239             },
240             (_, &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })))
241                 if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) =>
242             {
243                 ReducedTys::ToFatPtr { unsized_ty, from_ty }
244             },
245             (&(ty::Ref(_, from_ty, _) | ty::RawPtr(TypeAndMut { ty: from_ty, .. })), _) => {
246                 ReducedTys::FromPtr { from_ty, to_ty }
247             },
248             (_, &(ty::Ref(_, to_ty, _) | ty::RawPtr(TypeAndMut { ty: to_ty, .. }))) => {
249                 ReducedTys::ToPtr { from_ty, to_ty }
250             },
251             _ => ReducedTys::Other { from_ty, to_ty },
252         };
253     }
254 }
255
256 enum ReducedTy<'tcx> {
257     /// The type can be used for type erasure.
258     TypeErasure,
259     /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero
260     /// sized fields with a defined order.
261     OrderedFields(Ty<'tcx>),
262     /// The type is a struct containing multiple non-zero sized fields with no defined order.
263     UnorderedFields(Ty<'tcx>),
264     /// The type is a reference to the contained type.
265     Ref(Ty<'tcx>),
266     /// The type is an array of a primitive integer type. These can be used as storage for a value
267     /// of another type.
268     IntArray,
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(_)) => ReducedTy::IntArray,
279             ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
280                 ty = sub_ty;
281                 continue;
282             },
283             ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure,
284             ty::Tuple(args) => {
285                 let Some(sized_ty) = args.iter().find(|&ty| !is_zero_sized_ty(cx, ty)) else {
286                     return ReducedTy::OrderedFields(ty);
287                 };
288                 if args.iter().all(|ty| is_zero_sized_ty(cx, ty)) {
289                     ty = sized_ty;
290                     continue;
291                 }
292                 ReducedTy::UnorderedFields(ty)
293             },
294             ty::Adt(def, substs) if def.is_struct() => {
295                 let mut iter = def
296                     .non_enum_variant()
297                     .fields
298                     .iter()
299                     .map(|f| cx.tcx.type_of(f.did).subst(cx.tcx, substs));
300                 let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
301                     return ReducedTy::TypeErasure;
302                 };
303                 if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
304                     ty = sized_ty;
305                     continue;
306                 }
307                 if def.repr.inhibit_struct_field_reordering_opt() {
308                     ReducedTy::OrderedFields(ty)
309                 } else {
310                     ReducedTy::UnorderedFields(ty)
311                 }
312             },
313             ty::Adt(def, _) if def.is_enum() && (def.variants.is_empty() || is_c_void(cx, ty)) => {
314                 ReducedTy::TypeErasure
315             },
316             ty::Foreign(_) => ReducedTy::TypeErasure,
317             ty::Ref(_, ty, _) => ReducedTy::Ref(ty),
318             ty::RawPtr(ty) => ReducedTy::Ref(ty.ty),
319             _ => ReducedTy::Other(ty),
320         };
321     }
322 }
323
324 fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
325     if_chain! {
326         if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty);
327         if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty));
328         then {
329             layout.layout.size.bytes() == 0
330         } else {
331             false
332         }
333     }
334 }