|
| 1 | +use rustc_middle::mir::visit::Visitor; |
| 2 | +use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind}; |
| 3 | +use rustc_middle::ty::{AssocItem, AssocKind, TyCtxt}; |
| 4 | +use rustc_session::lint::builtin::PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS; |
| 5 | +use rustc_span::sym; |
| 6 | + |
| 7 | +use crate::errors; |
| 8 | + |
| 9 | +/// Check for transmutes that exhibit undefined behavior. |
| 10 | +/// For example, transmuting pointers to integers in a const context. |
| 11 | +pub(super) struct CheckUndefinedTransmutes; |
| 12 | + |
| 13 | +impl<'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 | + |
| 20 | +struct UndefinedTransmutesChecker<'a, 'tcx> { |
| 21 | + body: &'a Body<'tcx>, |
| 22 | + tcx: TyCtxt<'tcx>, |
| 23 | +} |
| 24 | + |
| 25 | +impl<'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_unsafe_ptr() && fn_sig.output().is_integral(); |
| 51 | + } |
| 52 | + } |
| 53 | + false |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +impl<'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 | +} |
0 commit comments