rustc_mir_transform/
check_undefined_transmutes.rs

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
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind};
use rustc_middle::ty::{AssocItem, AssocKind, TyCtxt};
use rustc_session::lint::builtin::PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS;
use rustc_span::sym;

use crate::errors;

/// Check for transmutes that exhibit undefined behavior.
/// For example, transmuting pointers to integers in a const context.
pub(super) struct CheckUndefinedTransmutes;

impl<'tcx> crate::MirLint<'tcx> for CheckUndefinedTransmutes {
    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
        let mut checker = UndefinedTransmutesChecker { body, tcx };
        checker.visit_body(body);
    }
}

struct UndefinedTransmutesChecker<'a, 'tcx> {
    body: &'a Body<'tcx>,
    tcx: TyCtxt<'tcx>,
}

impl<'a, 'tcx> UndefinedTransmutesChecker<'a, 'tcx> {
    // This functions checks two things:
    // 1. `function` takes a raw pointer as input and returns an integer as output.
    // 2. `function` is called from a const function or an associated constant.
    //
    // Why do we consider const functions and associated constants only?
    //
    // Generally, undefined behavior in const items are handled by the evaluator.
    // But, const functions and associated constants are evaluated only when referenced.
    // This can result in undefined behavior in a library going unnoticed until
    // the function or constant is actually used.
    //
    // Therefore, we only consider const functions and associated constants here and leave
    // other const items to be handled by the evaluator.
    fn is_ptr_to_int_in_const(&self, function: &Operand<'tcx>) -> bool {
        let def_id = self.body.source.def_id();

        if self.tcx.is_const_fn(def_id)
            || matches!(
                self.tcx.opt_associated_item(def_id),
                Some(AssocItem { kind: AssocKind::Const, .. })
            )
        {
            let fn_sig = function.ty(self.body, self.tcx).fn_sig(self.tcx).skip_binder();
            if let [input] = fn_sig.inputs() {
                return input.is_unsafe_ptr() && fn_sig.output().is_integral();
            }
        }
        false
    }
}

impl<'tcx> Visitor<'tcx> for UndefinedTransmutesChecker<'_, 'tcx> {
    // Check each block's terminator for calls to pointer to integer transmutes
    // in const functions or associated constants and emit a lint.
    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
        if let TerminatorKind::Call { func, .. } = &terminator.kind
            && let Some((func_def_id, _)) = func.const_fn_def()
            && self.tcx.is_intrinsic(func_def_id, sym::transmute)
            && self.is_ptr_to_int_in_const(func)
            && let Some(call_id) = self.body.source.def_id().as_local()
        {
            let hir_id = self.tcx.local_def_id_to_hir_id(call_id);
            let span = self.body.source_info(location).span;
            self.tcx.emit_node_span_lint(
                PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
                hir_id,
                span,
                errors::UndefinedTransmute,
            );
        }
    }
}