rustc_mir_transform/
check_undefined_transmutes.rs

1use rustc_middle::mir::visit::Visitor;
2use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind};
3use rustc_middle::ty::{AssocItem, AssocKind, TyCtxt};
4use rustc_session::lint::builtin::PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS;
5use rustc_span::sym;
6
7use crate::errors;
8
9/// Check for transmutes that exhibit undefined behavior.
10/// For example, transmuting pointers to integers in a const context.
11pub(super) struct CheckUndefinedTransmutes;
12
13impl<'tcx> crate::MirLint<'tcx> for CheckUndefinedTransmutes {
14    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
15        let mut checker = UndefinedTransmutesChecker { body, tcx };
16        checker.visit_body(body);
17    }
18}
19
20struct UndefinedTransmutesChecker<'a, 'tcx> {
21    body: &'a Body<'tcx>,
22    tcx: TyCtxt<'tcx>,
23}
24
25impl<'a, 'tcx> UndefinedTransmutesChecker<'a, 'tcx> {
26    // This functions checks two things:
27    // 1. `function` takes a raw pointer as input and returns an integer as output.
28    // 2. `function` is called from a const function or an associated constant.
29    //
30    // Why do we consider const functions and associated constants only?
31    //
32    // Generally, undefined behavior in const items are handled by the evaluator.
33    // But, const functions and associated constants are evaluated only when referenced.
34    // This can result in undefined behavior in a library going unnoticed until
35    // the function or constant is actually used.
36    //
37    // Therefore, we only consider const functions and associated constants here and leave
38    // other const items to be handled by the evaluator.
39    fn is_ptr_to_int_in_const(&self, function: &Operand<'tcx>) -> bool {
40        let def_id = self.body.source.def_id();
41
42        if self.tcx.is_const_fn(def_id)
43            || matches!(
44                self.tcx.opt_associated_item(def_id),
45                Some(AssocItem { kind: AssocKind::Const, .. })
46            )
47        {
48            let fn_sig = function.ty(self.body, self.tcx).fn_sig(self.tcx).skip_binder();
49            if let [input] = fn_sig.inputs() {
50                return input.is_raw_ptr() && fn_sig.output().is_integral();
51            }
52        }
53        false
54    }
55}
56
57impl<'tcx> Visitor<'tcx> for UndefinedTransmutesChecker<'_, 'tcx> {
58    // Check each block's terminator for calls to pointer to integer transmutes
59    // in const functions or associated constants and emit a lint.
60    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
61        if let TerminatorKind::Call { func, .. } = &terminator.kind
62            && let Some((func_def_id, _)) = func.const_fn_def()
63            && self.tcx.is_intrinsic(func_def_id, sym::transmute)
64            && self.is_ptr_to_int_in_const(func)
65            && let Some(call_id) = self.body.source.def_id().as_local()
66        {
67            let hir_id = self.tcx.local_def_id_to_hir_id(call_id);
68            let span = self.body.source_info(location).span;
69            self.tcx.emit_node_span_lint(
70                PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
71                hir_id,
72                span,
73                errors::UndefinedTransmute,
74            );
75        }
76    }
77}