rustc_hir_typeck/
intrinsicck.rs

1use hir::HirId;
2use rustc_abi::Primitive::Pointer;
3use rustc_abi::VariantIdx;
4use rustc_errors::codes::*;
5use rustc_errors::struct_span_code_err;
6use rustc_hir as hir;
7use rustc_index::Idx;
8use rustc_middle::bug;
9use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
10use rustc_middle::ty::{self, Ty, TyCtxt};
11use rustc_span::def_id::LocalDefId;
12use tracing::trace;
13
14/// If the type is `Option<T>`, it will return `T`, otherwise
15/// the type itself. Works on most `Option`-like types.
16fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
17    let ty::Adt(def, args) = *ty.kind() else { return ty };
18
19    if def.variants().len() == 2 && !def.repr().c() && def.repr().int.is_none() {
20        let data_idx;
21
22        let one = VariantIdx::new(1);
23        let zero = VariantIdx::ZERO;
24
25        if def.variant(zero).fields.is_empty() {
26            data_idx = one;
27        } else if def.variant(one).fields.is_empty() {
28            data_idx = zero;
29        } else {
30            return ty;
31        }
32
33        if def.variant(data_idx).fields.len() == 1 {
34            return def.variant(data_idx).single_field().ty(tcx, args);
35        }
36    }
37
38    ty
39}
40
41/// Try to display a sensible error with as much information as possible.
42fn skeleton_string<'tcx>(
43    ty: Ty<'tcx>,
44    sk: Result<SizeSkeleton<'tcx>, &'tcx LayoutError<'tcx>>,
45) -> String {
46    match sk {
47        Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
48        Ok(SizeSkeleton::Known(size, _)) => {
49            if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
50                format!("{v} bits")
51            } else {
52                // `u128` should definitely be able to hold the size of different architectures
53                // larger sizes should be reported as error `are too big for the target architecture`
54                // otherwise we have a bug somewhere
55                bug!("{:?} overflow for u128", size)
56            }
57        }
58        Ok(SizeSkeleton::Generic(size)) => {
59            format!("generic size {size}")
60        }
61        Err(LayoutError::TooGeneric(bad)) => {
62            if *bad == ty {
63                "this type does not have a fixed size".to_owned()
64            } else {
65                format!("size can vary because of {bad}")
66            }
67        }
68        Err(err) => err.to_string(),
69    }
70}
71
72fn check_transmute<'tcx>(
73    tcx: TyCtxt<'tcx>,
74    typing_env: ty::TypingEnv<'tcx>,
75    from: Ty<'tcx>,
76    to: Ty<'tcx>,
77    hir_id: HirId,
78) {
79    let span = || tcx.hir_span(hir_id);
80    let normalize = |ty| {
81        if let Ok(ty) = tcx.try_normalize_erasing_regions(typing_env, ty) {
82            ty
83        } else {
84            Ty::new_error_with_message(
85                tcx,
86                span(),
87                format!("tried to normalize non-wf type {ty:#?} in check_transmute"),
88            )
89        }
90    };
91
92    let from = normalize(from);
93    let to = normalize(to);
94    trace!(?from, ?to);
95
96    // Transmutes that are only changing lifetimes are always ok.
97    if from == to {
98        return;
99    }
100
101    let sk_from = SizeSkeleton::compute(from, tcx, typing_env);
102    let sk_to = SizeSkeleton::compute(to, tcx, typing_env);
103    trace!(?sk_from, ?sk_to);
104
105    // Check for same size using the skeletons.
106    if let Ok(sk_from) = sk_from
107        && let Ok(sk_to) = sk_to
108    {
109        if sk_from.same_size(sk_to) {
110            return;
111        }
112
113        // Special-case transmuting from `typeof(function)` and
114        // `Option<typeof(function)>` to present a clearer error.
115        let from = unpack_option_like(tcx, from);
116        if let ty::FnDef(..) = from.kind()
117            && let SizeSkeleton::Known(size_to, _) = sk_to
118            && size_to == Pointer(tcx.data_layout.instruction_address_space).size(&tcx)
119        {
120            struct_span_code_err!(tcx.sess.dcx(), span(), E0591, "can't transmute zero-sized type")
121                .with_note(format!("source type: {from}"))
122                .with_note(format!("target type: {to}"))
123                .with_help("cast with `as` to a pointer instead")
124                .emit();
125            return;
126        }
127    }
128
129    let mut err = struct_span_code_err!(
130        tcx.sess.dcx(),
131        span(),
132        E0512,
133        "cannot transmute between types of different sizes, or dependently-sized types"
134    );
135    if from == to {
136        err.note(format!("`{from}` does not have a fixed size"));
137        err.emit();
138    } else {
139        err.note(format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)));
140        err.note(format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
141        err.emit();
142    }
143}
144
145pub(crate) fn check_transmutes(tcx: TyCtxt<'_>, owner: LocalDefId) {
146    assert!(!tcx.is_typeck_child(owner.to_def_id()));
147    let typeck_results = tcx.typeck(owner);
148    let None = typeck_results.tainted_by_errors else { return };
149
150    let typing_env = ty::TypingEnv::post_analysis(tcx, owner);
151    for &(from, to, hir_id) in &typeck_results.transmutes_to_check {
152        check_transmute(tcx, typing_env, from, to, hir_id);
153    }
154}