rustc_lint/
transmute.rs

1use rustc_errors::Applicability;
2use rustc_hir::def::{DefKind, Res};
3use rustc_hir::def_id::LocalDefId;
4use rustc_hir::{self as hir};
5use rustc_macros::LintDiagnostic;
6use rustc_middle::ty::{self, Ty};
7use rustc_session::{declare_lint, impl_lint_pass};
8use rustc_span::sym;
9
10use crate::{LateContext, LateLintPass};
11
12declare_lint! {
13    /// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer
14    /// transmute in const functions and associated constants.
15    ///
16    /// ### Example
17    ///
18    /// ```rust
19    /// const fn foo(ptr: *const u8) -> usize {
20    ///    unsafe {
21    ///        std::mem::transmute::<*const u8, usize>(ptr)
22    ///    }
23    /// }
24    /// ```
25    ///
26    /// {{produces}}
27    ///
28    /// ### Explanation
29    ///
30    /// Transmuting pointers to integers in a `const` context is undefined behavior.
31    /// Any attempt to use the resulting integer will abort const-evaluation.
32    ///
33    /// But sometimes the compiler might not emit an error for pointer to integer transmutes
34    /// inside const functions and associated consts because they are evaluated only when referenced.
35    /// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior
36    /// from compiling without any warnings or errors.
37    ///
38    /// See [std::mem::transmute] in the reference for more details.
39    ///
40    /// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html
41    pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
42    Warn,
43    "detects pointer to integer transmutes in const functions and associated constants",
44}
45
46declare_lint! {
47    /// The `unnecessary_transmutes` lint detects transmutations that have safer alternatives.
48    ///
49    /// ### Example
50    ///
51    /// ```rust
52    /// fn bytes_at_home(x: [u8; 4]) -> u32 {
53    ///   unsafe { std::mem::transmute(x) }
54    /// }
55    /// ```
56    ///
57    /// {{produces}}
58    ///
59    /// ### Explanation
60    ///
61    /// Using an explicit method is preferable over calls to
62    /// [`transmute`](https://doc.rust-lang.org/std/mem/fn.transmute.html) as
63    /// they more clearly communicate the intent, are easier to review, and
64    /// are less likely to accidentally result in unsoundness.
65    pub UNNECESSARY_TRANSMUTES,
66    Warn,
67    "detects transmutes that can also be achieved by other operations"
68}
69
70pub(crate) struct CheckTransmutes;
71
72impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]);
73
74impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
75    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
76        let hir::ExprKind::Call(callee, [arg]) = expr.kind else {
77            return;
78        };
79        let hir::ExprKind::Path(qpath) = callee.kind else {
80            return;
81        };
82        let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id) else {
83            return;
84        };
85        if !cx.tcx.is_intrinsic(def_id, sym::transmute) {
86            return;
87        };
88        let body_owner_def_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id);
89        let const_context = cx.tcx.hir_body_const_context(body_owner_def_id);
90        let args = cx.typeck_results().node_args(callee.hir_id);
91
92        let src = args.type_at(0);
93        let dst = args.type_at(1);
94
95        check_ptr_transmute_in_const(cx, expr, body_owner_def_id, const_context, src, dst);
96        check_unnecessary_transmute(cx, expr, callee, arg, const_context, src, dst);
97    }
98}
99
100/// Check for transmutes that exhibit undefined behavior.
101/// For example, transmuting pointers to integers in a const context.
102///
103/// Why do we consider const functions and associated constants only?
104///
105/// Generally, undefined behavior in const items are handled by the evaluator.
106/// But, const functions and associated constants are evaluated only when referenced.
107/// This can result in undefined behavior in a library going unnoticed until
108/// the function or constant is actually used.
109///
110/// Therefore, we only consider const functions and associated constants here and leave
111/// other const items to be handled by the evaluator.
112fn check_ptr_transmute_in_const<'tcx>(
113    cx: &LateContext<'tcx>,
114    expr: &'tcx hir::Expr<'tcx>,
115    body_owner_def_id: LocalDefId,
116    const_context: Option<hir::ConstContext>,
117    src: Ty<'tcx>,
118    dst: Ty<'tcx>,
119) {
120    if matches!(const_context, Some(hir::ConstContext::ConstFn))
121        || matches!(cx.tcx.def_kind(body_owner_def_id), DefKind::AssocConst)
122    {
123        if src.is_raw_ptr() && dst.is_integral() {
124            cx.tcx.emit_node_span_lint(
125                PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
126                expr.hir_id,
127                expr.span,
128                UndefinedTransmuteLint,
129            );
130        }
131    }
132}
133
134/// Check for transmutes that overlap with stdlib methods.
135/// For example, transmuting `[u8; 4]` to `u32`.
136///
137/// We chose not to lint u8 -> bool transmutes, see #140431.
138fn check_unnecessary_transmute<'tcx>(
139    cx: &LateContext<'tcx>,
140    expr: &'tcx hir::Expr<'tcx>,
141    callee: &'tcx hir::Expr<'tcx>,
142    arg: &'tcx hir::Expr<'tcx>,
143    const_context: Option<hir::ConstContext>,
144    src: Ty<'tcx>,
145    dst: Ty<'tcx>,
146) {
147    let callee_span = callee.span.find_ancestor_inside(expr.span).unwrap_or(callee.span);
148    let (sugg, help) = match (src.kind(), dst.kind()) {
149        // dont check the length; transmute does that for us.
150        // [u8; _] => primitive
151        (ty::Array(t, _), ty::Uint(_) | ty::Float(_) | ty::Int(_))
152            if *t.kind() == ty::Uint(ty::UintTy::U8) =>
153        {
154            (
155                Some(vec![(callee_span, format!("{dst}::from_ne_bytes"))]),
156                Some(
157                    "there's also `from_le_bytes` and `from_be_bytes` if you expect a particular byte order",
158                ),
159            )
160        }
161        // primitive => [u8; _]
162        (ty::Uint(_) | ty::Float(_) | ty::Int(_), ty::Array(t, _))
163            if *t.kind() == ty::Uint(ty::UintTy::U8) =>
164        {
165            (
166                Some(vec![(callee_span, format!("{src}::to_ne_bytes"))]),
167                Some(
168                    "there's also `to_le_bytes` and `to_be_bytes` if you expect a particular byte order",
169                ),
170            )
171        }
172        // char → u32
173        (ty::Char, ty::Uint(ty::UintTy::U32)) => {
174            (Some(vec![(callee_span, "u32::from".to_string())]), None)
175        }
176        // char (→ u32) → i32
177        (ty::Char, ty::Int(ty::IntTy::I32)) => (
178            Some(vec![
179                (callee_span, "u32::from".to_string()),
180                (expr.span.shrink_to_hi(), ".cast_signed()".to_string()),
181            ]),
182            None,
183        ),
184        // u32 → char
185        (ty::Uint(ty::UintTy::U32), ty::Char) => (
186            Some(vec![(callee_span, "char::from_u32_unchecked".to_string())]),
187            Some("consider using `char::from_u32(…).unwrap()`"),
188        ),
189        // i32 → char
190        (ty::Int(ty::IntTy::I32), ty::Char) => (
191            Some(vec![
192                (callee_span, "char::from_u32_unchecked(i32::cast_unsigned".to_string()),
193                (expr.span.shrink_to_hi(), ")".to_string()),
194            ]),
195            Some("consider using `char::from_u32(i32::cast_unsigned(…)).unwrap()`"),
196        ),
197        // uNN → iNN
198        (ty::Uint(_), ty::Int(_)) => {
199            (Some(vec![(callee_span, format!("{src}::cast_signed"))]), None)
200        }
201        // iNN → uNN
202        (ty::Int(_), ty::Uint(_)) => {
203            (Some(vec![(callee_span, format!("{src}::cast_unsigned"))]), None)
204        }
205        // fNN → usize, isize
206        (ty::Float(_), ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize)) => (
207            Some(vec![
208                (callee_span, format!("{src}::to_bits")),
209                (expr.span.shrink_to_hi(), format!(" as {dst}")),
210            ]),
211            None,
212        ),
213        // fNN (→ uNN) → iNN
214        (ty::Float(_), ty::Int(..)) => (
215            Some(vec![
216                (callee_span, format!("{src}::to_bits")),
217                (expr.span.shrink_to_hi(), ".cast_signed()".to_string()),
218            ]),
219            None,
220        ),
221        // fNN → uNN
222        (ty::Float(_), ty::Uint(..)) => {
223            (Some(vec![(callee_span, format!("{src}::to_bits"))]), None)
224        }
225        // xsize → fNN
226        (ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize), ty::Float(_)) => (
227            Some(vec![
228                (callee_span, format!("{dst}::from_bits")),
229                (arg.span.shrink_to_hi(), " as _".to_string()),
230            ]),
231            None,
232        ),
233        // iNN (→ uNN) → fNN
234        (ty::Int(_), ty::Float(_)) => (
235            Some(vec![
236                (callee_span, format!("{dst}::from_bits({src}::cast_unsigned")),
237                (expr.span.shrink_to_hi(), ")".to_string()),
238            ]),
239            None,
240        ),
241        // uNN → fNN
242        (ty::Uint(_), ty::Float(_)) => {
243            (Some(vec![(callee_span, format!("{dst}::from_bits"))]), None)
244        }
245        // bool → x8 in const context since `From::from` is not const yet
246        // FIXME: Consider arg expr's precedence to avoid parentheses.
247        // FIXME(const_traits): Remove this when `From::from` is constified.
248        (ty::Bool, ty::Int(..) | ty::Uint(..)) if const_context.is_some() => (
249            Some(vec![
250                (callee_span, "".to_string()),
251                (expr.span.shrink_to_hi(), format!(" as {dst}")),
252            ]),
253            None,
254        ),
255        // bool → x8 using `x8::from`
256        (ty::Bool, ty::Int(..) | ty::Uint(..)) => {
257            (Some(vec![(callee_span, format!("{dst}::from"))]), None)
258        }
259        _ => return,
260    };
261
262    cx.tcx.node_span_lint(UNNECESSARY_TRANSMUTES, expr.hir_id, expr.span, |diag| {
263        diag.primary_message("unnecessary transmute");
264        if let Some(sugg) = sugg {
265            diag.multipart_suggestion("replace this with", sugg, Applicability::MachineApplicable);
266        }
267        if let Some(help) = help {
268            diag.help(help);
269        }
270    });
271}
272
273#[derive(LintDiagnostic)]
274#[diag(lint_undefined_transmute)]
275#[note]
276#[note(lint_note2)]
277#[help]
278pub(crate) struct UndefinedTransmuteLint;