rustc_mir_transform/
check_unnecessary_transmutes.rs

1use rustc_middle::mir::visit::Visitor;
2use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind};
3use rustc_middle::ty::*;
4use rustc_session::lint::builtin::UNNECESSARY_TRANSMUTES;
5use rustc_span::source_map::Spanned;
6use rustc_span::{Span, sym};
7
8use crate::errors::UnnecessaryTransmute as Error;
9
10/// Check for transmutes that overlap with stdlib methods.
11/// For example, transmuting `[u8; 4]` to `u32`.
12/// We chose not to lint u8 -> bool transmutes, see #140431
13pub(super) struct CheckUnnecessaryTransmutes;
14
15impl<'tcx> crate::MirLint<'tcx> for CheckUnnecessaryTransmutes {
16    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
17        let mut checker = UnnecessaryTransmuteChecker { body, tcx };
18        checker.visit_body(body);
19    }
20}
21
22struct UnnecessaryTransmuteChecker<'a, 'tcx> {
23    body: &'a Body<'tcx>,
24    tcx: TyCtxt<'tcx>,
25}
26
27impl<'a, 'tcx> UnnecessaryTransmuteChecker<'a, 'tcx> {
28    fn is_unnecessary_transmute(
29        &self,
30        function: &Operand<'tcx>,
31        arg: String,
32        span: Span,
33        is_in_const: bool,
34    ) -> Option<Error> {
35        let fn_sig = function.ty(self.body, self.tcx).fn_sig(self.tcx).skip_binder();
36        let [input] = fn_sig.inputs() else { return None };
37
38        let err = |sugg| Error { span, sugg, help: None };
39
40        Some(match (input.kind(), fn_sig.output().kind()) {
41            // dont check the length; transmute does that for us.
42            // [u8; _] => primitive
43            (Array(t, _), Uint(_) | Float(_) | Int(_)) if *t.kind() == Uint(UintTy::U8) => Error {
44                sugg: format!("{}::from_ne_bytes({arg})", fn_sig.output()),
45                help: Some(
46                    "there's also `from_le_bytes` and `from_be_bytes` if you expect a particular byte order",
47                ),
48                span,
49            },
50            // primitive => [u8; _]
51            (Uint(_) | Float(_) | Int(_), Array(t, _)) if *t.kind() == Uint(UintTy::U8) => Error {
52                sugg: format!("{input}::to_ne_bytes({arg})"),
53                help: Some(
54                    "there's also `to_le_bytes` and `to_be_bytes` if you expect a particular byte order",
55                ),
56                span,
57            },
58            // char → u32
59            (Char, Uint(UintTy::U32)) => err(format!("u32::from({arg})")),
60            // char (→ u32) → i32
61            (Char, Int(IntTy::I32)) => err(format!("u32::from({arg}).cast_signed()")),
62            // u32 → char
63            (Uint(UintTy::U32), Char) => Error {
64                sugg: format!("char::from_u32_unchecked({arg})"),
65                help: Some("consider `char::from_u32(…).unwrap()`"),
66                span,
67            },
68            // i32 → char
69            (Int(IntTy::I32), Char) => Error {
70                sugg: format!("char::from_u32_unchecked(i32::cast_unsigned({arg}))"),
71                help: Some("consider `char::from_u32(i32::cast_unsigned(…)).unwrap()`"),
72                span,
73            },
74            // uNN → iNN
75            (Uint(ty), Int(_)) => err(format!("{}::cast_signed({arg})", ty.name_str())),
76            // iNN → uNN
77            (Int(ty), Uint(_)) => err(format!("{}::cast_unsigned({arg})", ty.name_str())),
78            // fNN → xsize
79            (Float(ty), Uint(UintTy::Usize)) => {
80                err(format!("{}::to_bits({arg}) as usize", ty.name_str()))
81            }
82            (Float(ty), Int(IntTy::Isize)) => {
83                err(format!("{}::to_bits({arg}) as isize", ty.name_str()))
84            }
85            // fNN (→ uNN) → iNN
86            (Float(ty), Int(..)) => err(format!("{}::to_bits({arg}).cast_signed()", ty.name_str())),
87            // fNN → uNN
88            (Float(ty), Uint(..)) => err(format!("{}::to_bits({arg})", ty.name_str())),
89            // xsize → fNN
90            (Uint(UintTy::Usize) | Int(IntTy::Isize), Float(ty)) => {
91                err(format!("{}::from_bits({arg} as _)", ty.name_str(),))
92            }
93            // iNN (→ uNN) → fNN
94            (Int(int_ty), Float(ty)) => err(format!(
95                "{}::from_bits({}::cast_unsigned({arg}))",
96                ty.name_str(),
97                int_ty.name_str()
98            )),
99            // uNN → fNN
100            (Uint(_), Float(ty)) => err(format!("{}::from_bits({arg})", ty.name_str())),
101            // bool → { x8 } in const context since `From::from` is not const yet
102            // FIXME: is it possible to know when the parentheses arent necessary?
103            // FIXME(const_traits): Remove this when From::from is constified?
104            (Bool, Int(..) | Uint(..)) if is_in_const => {
105                err(format!("({arg}) as {}", fn_sig.output()))
106            }
107            // " using `x8::from`
108            (Bool, Int(..) | Uint(..)) => err(format!("{}::from({arg})", fn_sig.output())),
109            _ => return None,
110        })
111    }
112}
113
114impl<'tcx> Visitor<'tcx> for UnnecessaryTransmuteChecker<'_, 'tcx> {
115    // Check each block's terminator for calls to pointer to integer transmutes
116    // in const functions or associated constants and emit a lint.
117    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
118        if let TerminatorKind::Call { func, args, .. } = &terminator.kind
119            && let [Spanned { span: arg, .. }] = **args
120            && let Some((func_def_id, _)) = func.const_fn_def()
121            && self.tcx.is_intrinsic(func_def_id, sym::transmute)
122            && let span = self.body.source_info(location).span
123            && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(arg)
124            && let def_id = self.body.source.def_id()
125            && let Some(lint) = self.is_unnecessary_transmute(
126                func,
127                snippet,
128                span,
129                self.tcx.hir_body_const_context(def_id.expect_local()).is_some(),
130            )
131            && let Some(hir_id) = terminator.source_info.scope.lint_root(&self.body.source_scopes)
132        {
133            self.tcx.emit_node_span_lint(UNNECESSARY_TRANSMUTES, hir_id, span, lint);
134        }
135    }
136}