|
| 1 | +use rustc_hir::def::{DefKind, Res}; |
| 2 | +use rustc_hir::{self as hir}; |
| 3 | +use rustc_macros::LintDiagnostic; |
| 4 | +use rustc_session::{declare_lint, impl_lint_pass}; |
| 5 | +use rustc_span::sym; |
| 6 | + |
| 7 | +use crate::{LateContext, LateLintPass}; |
| 8 | + |
| 9 | +declare_lint! { |
| 10 | + /// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer |
| 11 | + /// transmute in const functions and associated constants. |
| 12 | + /// |
| 13 | + /// ### Example |
| 14 | + /// |
| 15 | + /// ```rust |
| 16 | + /// const fn foo(ptr: *const u8) -> usize { |
| 17 | + /// unsafe { |
| 18 | + /// std::mem::transmute::<*const u8, usize>(ptr) |
| 19 | + /// } |
| 20 | + /// } |
| 21 | + /// ``` |
| 22 | + /// |
| 23 | + /// {{produces}} |
| 24 | + /// |
| 25 | + /// ### Explanation |
| 26 | + /// |
| 27 | + /// Transmuting pointers to integers in a `const` context is undefined behavior. |
| 28 | + /// Any attempt to use the resulting integer will abort const-evaluation. |
| 29 | + /// |
| 30 | + /// But sometimes the compiler might not emit an error for pointer to integer transmutes |
| 31 | + /// inside const functions and associated consts because they are evaluated only when referenced. |
| 32 | + /// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior |
| 33 | + /// from compiling without any warnings or errors. |
| 34 | + /// |
| 35 | + /// See [std::mem::transmute] in the reference for more details. |
| 36 | + /// |
| 37 | + /// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html |
| 38 | + pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, |
| 39 | + Warn, |
| 40 | + "detects pointer to integer transmutes in const functions and associated constants", |
| 41 | +} |
| 42 | + |
| 43 | +pub(crate) struct CheckTransmutes; |
| 44 | + |
| 45 | +impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS]); |
| 46 | + |
| 47 | +impl<'tcx> LateLintPass<'tcx> for CheckTransmutes { |
| 48 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { |
| 49 | + let hir::ExprKind::Call(callee, _) = expr.kind else { |
| 50 | + return; |
| 51 | + }; |
| 52 | + let hir::ExprKind::Path(qpath) = callee.kind else { |
| 53 | + return; |
| 54 | + }; |
| 55 | + let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id) else { |
| 56 | + return; |
| 57 | + }; |
| 58 | + if !cx.tcx.is_intrinsic(def_id, sym::transmute) { |
| 59 | + return; |
| 60 | + }; |
| 61 | + let body_owner_def_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id); |
| 62 | + let Some(context) = cx.tcx.hir_body_const_context(body_owner_def_id) else { |
| 63 | + return; |
| 64 | + }; |
| 65 | + let args = cx.typeck_results().node_args(callee.hir_id); |
| 66 | + |
| 67 | + let src = args.type_at(0); |
| 68 | + let dst = args.type_at(1); |
| 69 | + |
| 70 | + // Check for transmutes that exhibit undefined behavior. |
| 71 | + // For example, transmuting pointers to integers in a const context. |
| 72 | + // |
| 73 | + // Why do we consider const functions and associated constants only? |
| 74 | + // |
| 75 | + // Generally, undefined behavior in const items are handled by the evaluator. |
| 76 | + // But, const functions and associated constants are evaluated only when referenced. |
| 77 | + // This can result in undefined behavior in a library going unnoticed until |
| 78 | + // the function or constant is actually used. |
| 79 | + // |
| 80 | + // Therefore, we only consider const functions and associated constants here and leave |
| 81 | + // other const items to be handled by the evaluator. |
| 82 | + if matches!(context, hir::ConstContext::ConstFn) |
| 83 | + || matches!(cx.tcx.def_kind(body_owner_def_id), DefKind::AssocConst) |
| 84 | + { |
| 85 | + if src.is_raw_ptr() && dst.is_integral() { |
| 86 | + cx.tcx.emit_node_span_lint( |
| 87 | + PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, |
| 88 | + expr.hir_id, |
| 89 | + expr.span, |
| 90 | + UndefinedTransmuteLint, |
| 91 | + ); |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +#[derive(LintDiagnostic)] |
| 98 | +#[diag(lint_undefined_transmute)] |
| 99 | +#[note] |
| 100 | +#[note(lint_note2)] |
| 101 | +#[help] |
| 102 | +pub(crate) struct UndefinedTransmuteLint; |
0 commit comments