• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use hir::HirId;
2 use rustc_errors::struct_span_err;
3 use rustc_hir as hir;
4 use rustc_index::Idx;
5 use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
6 use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
7 use rustc_target::abi::{Pointer, VariantIdx};
8 
9 use super::FnCtxt;
10 
11 /// If the type is `Option<T>`, it will return `T`, otherwise
12 /// the type itself. Works on most `Option`-like types.
unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx>13 fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
14     let ty::Adt(def, substs) = *ty.kind() else { return ty };
15 
16     if def.variants().len() == 2 && !def.repr().c() && def.repr().int.is_none() {
17         let data_idx;
18 
19         let one = VariantIdx::new(1);
20         let zero = VariantIdx::new(0);
21 
22         if def.variant(zero).fields.is_empty() {
23             data_idx = one;
24         } else if def.variant(one).fields.is_empty() {
25             data_idx = zero;
26         } else {
27             return ty;
28         }
29 
30         if def.variant(data_idx).fields.len() == 1 {
31             return def.variant(data_idx).single_field().ty(tcx, substs);
32         }
33     }
34 
35     ty
36 }
37 
38 impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
check_transmute(&self, from: Ty<'tcx>, to: Ty<'tcx>, hir_id: HirId)39     pub fn check_transmute(&self, from: Ty<'tcx>, to: Ty<'tcx>, hir_id: HirId) {
40         let tcx = self.tcx;
41         let dl = &tcx.data_layout;
42         let span = tcx.hir().span(hir_id);
43         let normalize = |ty| {
44             let ty = self.resolve_vars_if_possible(ty);
45             self.tcx.normalize_erasing_regions(self.param_env, ty)
46         };
47         let from = normalize(from);
48         let to = normalize(to);
49         trace!(?from, ?to);
50         if from.has_non_region_infer() || to.has_non_region_infer() {
51             tcx.sess.delay_span_bug(span, "argument to transmute has inference variables");
52             return;
53         }
54         // Transmutes that are only changing lifetimes are always ok.
55         if from == to {
56             return;
57         }
58 
59         let skel = |ty| SizeSkeleton::compute(ty, tcx, self.param_env);
60         let sk_from = skel(from);
61         let sk_to = skel(to);
62         trace!(?sk_from, ?sk_to);
63 
64         // Check for same size using the skeletons.
65         if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
66             if sk_from.same_size(sk_to) {
67                 return;
68             }
69 
70             // Special-case transmuting from `typeof(function)` and
71             // `Option<typeof(function)>` to present a clearer error.
72             let from = unpack_option_like(tcx, from);
73             if let (&ty::FnDef(..), SizeSkeleton::Known(size_to)) = (from.kind(), sk_to) && size_to == Pointer(dl.instruction_address_space).size(&tcx) {
74                 struct_span_err!(tcx.sess, span, E0591, "can't transmute zero-sized type")
75                     .note(format!("source type: {from}"))
76                     .note(format!("target type: {to}"))
77                     .help("cast with `as` to a pointer instead")
78                     .emit();
79                 return;
80             }
81         }
82 
83         // Try to display a sensible error with as much information as possible.
84         let skeleton_string = |ty: Ty<'tcx>, sk: Result<_, &_>| match sk {
85             Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
86             Ok(SizeSkeleton::Known(size)) => {
87                 if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
88                     format!("{} bits", v)
89                 } else {
90                     // `u128` should definitely be able to hold the size of different architectures
91                     // larger sizes should be reported as error `are too big for the current architecture`
92                     // otherwise we have a bug somewhere
93                     bug!("{:?} overflow for u128", size)
94                 }
95             }
96             Ok(SizeSkeleton::Generic(size)) => {
97                 if let Some(size) = size.try_eval_target_usize(tcx, self.param_env) {
98                     format!("{size} bytes")
99                 } else {
100                     format!("generic size {size}")
101                 }
102             }
103             Err(LayoutError::Unknown(bad)) => {
104                 if *bad == ty {
105                     "this type does not have a fixed size".to_owned()
106                 } else {
107                     format!("size can vary because of {bad}")
108                 }
109             }
110             Err(err) => err.to_string(),
111         };
112 
113         let mut err = struct_span_err!(
114             tcx.sess,
115             span,
116             E0512,
117             "cannot transmute between types of different sizes, \
118                                         or dependently-sized types"
119         );
120         if from == to {
121             err.note(format!("`{from}` does not have a fixed size"));
122         } else {
123             err.note(format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)))
124                 .note(format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
125             let mut should_delay_as_bug = false;
126             if let Err(LayoutError::Unknown(bad_from)) = sk_from && bad_from.references_error() {
127                 should_delay_as_bug = true;
128             }
129             if let Err(LayoutError::Unknown(bad_to)) = sk_to && bad_to.references_error() {
130                 should_delay_as_bug = true;
131             }
132             if should_delay_as_bug {
133                 err.delay_as_bug();
134             }
135         }
136         err.emit();
137     }
138 }
139