1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use hir::HirId;
use rustc_errors::codes::*;
use rustc_errors::struct_span_code_err;
use rustc_hir as hir;
use rustc_index::Idx;
use rustc_middle::bug;
use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
use rustc_target::abi::{Pointer, VariantIdx};
use tracing::trace;

use super::FnCtxt;

/// If the type is `Option<T>`, it will return `T`, otherwise
/// the type itself. Works on most `Option`-like types.
fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
    let ty::Adt(def, args) = *ty.kind() else { return ty };

    if def.variants().len() == 2 && !def.repr().c() && def.repr().int.is_none() {
        let data_idx;

        let one = VariantIdx::new(1);
        let zero = VariantIdx::ZERO;

        if def.variant(zero).fields.is_empty() {
            data_idx = one;
        } else if def.variant(one).fields.is_empty() {
            data_idx = zero;
        } else {
            return ty;
        }

        if def.variant(data_idx).fields.len() == 1 {
            return def.variant(data_idx).single_field().ty(tcx, args);
        }
    }

    ty
}

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
    pub(crate) fn check_transmute(&self, from: Ty<'tcx>, to: Ty<'tcx>, hir_id: HirId) {
        let tcx = self.tcx;
        let dl = &tcx.data_layout;
        let span = tcx.hir().span(hir_id);
        let normalize = |ty| {
            let ty = self.resolve_vars_if_possible(ty);
            self.tcx.normalize_erasing_regions(self.param_env, ty)
        };
        let from = normalize(from);
        let to = normalize(to);
        trace!(?from, ?to);
        if from.has_non_region_infer() || to.has_non_region_infer() {
            // Note: this path is currently not reached in any test, so any
            // example that triggers this would be worth minimizing and
            // converting into a test.
            self.dcx().span_bug(span, "argument to transmute has inference variables");
        }
        // Transmutes that are only changing lifetimes are always ok.
        if from == to {
            return;
        }

        let skel = |ty| SizeSkeleton::compute(ty, tcx, self.param_env);
        let sk_from = skel(from);
        let sk_to = skel(to);
        trace!(?sk_from, ?sk_to);

        // Check for same size using the skeletons.
        if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
            if sk_from.same_size(sk_to) {
                return;
            }

            // Special-case transmuting from `typeof(function)` and
            // `Option<typeof(function)>` to present a clearer error.
            let from = unpack_option_like(tcx, from);
            if let (&ty::FnDef(..), SizeSkeleton::Known(size_to, _)) = (from.kind(), sk_to)
                && size_to == Pointer(dl.instruction_address_space).size(&tcx)
            {
                struct_span_code_err!(self.dcx(), span, E0591, "can't transmute zero-sized type")
                    .with_note(format!("source type: {from}"))
                    .with_note(format!("target type: {to}"))
                    .with_help("cast with `as` to a pointer instead")
                    .emit();
                return;
            }
        }

        // Try to display a sensible error with as much information as possible.
        let skeleton_string = |ty: Ty<'tcx>, sk: Result<_, &_>| match sk {
            Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
            Ok(SizeSkeleton::Known(size, _)) => {
                if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
                    format!("{v} bits")
                } else {
                    // `u128` should definitely be able to hold the size of different architectures
                    // larger sizes should be reported as error `are too big for the current architecture`
                    // otherwise we have a bug somewhere
                    bug!("{:?} overflow for u128", size)
                }
            }
            Ok(SizeSkeleton::Generic(size)) => {
                if let Some(size) = size.try_eval_target_usize(tcx, self.param_env) {
                    format!("{size} bytes")
                } else {
                    format!("generic size {size}")
                }
            }
            Err(LayoutError::Unknown(bad)) => {
                if *bad == ty {
                    "this type does not have a fixed size".to_owned()
                } else {
                    format!("size can vary because of {bad}")
                }
            }
            Err(err) => err.to_string(),
        };

        let mut err = struct_span_code_err!(
            self.dcx(),
            span,
            E0512,
            "cannot transmute between types of different sizes, \
                                        or dependently-sized types"
        );
        if from == to {
            err.note(format!("`{from}` does not have a fixed size"));
            err.emit();
        } else {
            err.note(format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)))
                .note(format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
            if let Err(LayoutError::ReferencesError(_)) = sk_from {
                err.delay_as_bug();
            } else if let Err(LayoutError::ReferencesError(_)) = sk_to {
                err.delay_as_bug();
            } else {
                err.emit();
            }
        }
    }
}