about summary refs log tree commit diff
path: root/compiler/rustc_lint
diff options
context:
space:
mode:
authorTrevor Gross <t.gross35@gmail.com>2025-05-27 20:28:32 -0400
committerGitHub <noreply@github.com>2025-05-27 20:28:32 -0400
commite0278ed5afa50b889c7f6c5f844726cd8c18f55c (patch)
treeded667dc59ec7abf2ac85da9cb945d2694cd9108 /compiler/rustc_lint
parenta8c7a1e29bc2a9ded5db661a04c4f91adaaf745e (diff)
parent295a8d56f5a7d200599d587fb52bf217b9aee363 (diff)
downloadrust-e0278ed5afa50b889c7f6c5f844726cd8c18f55c.tar.gz
rust-e0278ed5afa50b889c7f6c5f844726cd8c18f55c.zip
Rollup merge of #141551 - compiler-errors:hir-lints, r=BoxyUwU
Make two transmute-related MIR lints into HIR lint

Make `PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS` (rust-lang/rust#130540) and `UNNECESSARY_TRANSMUTES` (rust-lang/rust#136083) into "normal" HIR-based lints.

Funny enough this came up in the review of the latter (https://github.com/rust-lang/rust/pull/136083#issuecomment-2614301413), but I guess it just was overlooked.

But anywyas, there's no reason for these to be MIR lints; in fact, it makes the suggestions for them a bit more complicated than necessary.

Note that there's probably a few more simplifications and improvements to be done here. Follow-ups can be done in a separate PR, especially if they're about the messaging and suggestions themselves, which I didn't write.
Diffstat (limited to 'compiler/rustc_lint')
-rw-r--r--compiler/rustc_lint/messages.ftl5
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/transmute.rs278
3 files changed, 286 insertions, 0 deletions
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index b470dea5e82..6913fa3e60b 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -807,6 +807,11 @@ lint_type_ir_inherent_usage = do not use `rustc_type_ir::inherent` unless you're
 lint_type_ir_trait_usage = do not use `rustc_type_ir::Interner` or `rustc_type_ir::InferCtxtLike` unless you're inside of the trait solver
     .note = the method or struct you're looking for is likely defined somewhere else downstream in the compiler
 
+lint_undefined_transmute = pointers cannot be transmuted to integers during const eval
+    .note = at compile-time, pointers do not have an integer value
+    .note2 = avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
+    .help = for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
+
 lint_undropped_manually_drops = calls to `std::mem::drop` with `std::mem::ManuallyDrop` instead of the inner value does nothing
     .label = argument has type `{$arg_ty}`
     .suggestion = use `std::mem::ManuallyDrop::into_inner` to get the inner value
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 0de4c43a659..ce290eab8e9 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -75,6 +75,7 @@ mod reference_casting;
 mod shadowed_into_iter;
 mod static_mut_refs;
 mod traits;
+mod transmute;
 mod types;
 mod unit_bindings;
 mod unqualified_local_imports;
@@ -118,6 +119,7 @@ use shadowed_into_iter::ShadowedIntoIter;
 pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER};
 use static_mut_refs::*;
 use traits::*;
+use transmute::CheckTransmutes;
 use types::*;
 use unit_bindings::*;
 use unqualified_local_imports::*;
@@ -246,6 +248,7 @@ late_lint_methods!(
             IfLetRescope: IfLetRescope::default(),
             StaticMutRefs: StaticMutRefs,
             UnqualifiedLocalImports: UnqualifiedLocalImports,
+            CheckTransmutes: CheckTransmutes,
         ]
     ]
 );
diff --git a/compiler/rustc_lint/src/transmute.rs b/compiler/rustc_lint/src/transmute.rs
new file mode 100644
index 00000000000..bc1d4587d07
--- /dev/null
+++ b/compiler/rustc_lint/src/transmute.rs
@@ -0,0 +1,278 @@
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::{self as hir};
+use rustc_macros::LintDiagnostic;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint, impl_lint_pass};
+use rustc_span::sym;
+
+use crate::{LateContext, LateLintPass};
+
+declare_lint! {
+    /// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer
+    /// transmute in const functions and associated constants.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// const fn foo(ptr: *const u8) -> usize {
+    ///    unsafe {
+    ///        std::mem::transmute::<*const u8, usize>(ptr)
+    ///    }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Transmuting pointers to integers in a `const` context is undefined behavior.
+    /// Any attempt to use the resulting integer will abort const-evaluation.
+    ///
+    /// But sometimes the compiler might not emit an error for pointer to integer transmutes
+    /// inside const functions and associated consts because they are evaluated only when referenced.
+    /// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior
+    /// from compiling without any warnings or errors.
+    ///
+    /// See [std::mem::transmute] in the reference for more details.
+    ///
+    /// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html
+    pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
+    Warn,
+    "detects pointer to integer transmutes in const functions and associated constants",
+}
+
+declare_lint! {
+    /// The `unnecessary_transmutes` lint detects transmutations that have safer alternatives.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// fn bytes_at_home(x: [u8; 4]) -> u32 {
+    ///   unsafe { std::mem::transmute(x) }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Using an explicit method is preferable over calls to
+    /// [`transmute`](https://doc.rust-lang.org/std/mem/fn.transmute.html) as
+    /// they more clearly communicate the intent, are easier to review, and
+    /// are less likely to accidentally result in unsoundness.
+    pub UNNECESSARY_TRANSMUTES,
+    Warn,
+    "detects transmutes that can also be achieved by other operations"
+}
+
+pub(crate) struct CheckTransmutes;
+
+impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]);
+
+impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+        let hir::ExprKind::Call(callee, [arg]) = expr.kind else {
+            return;
+        };
+        let hir::ExprKind::Path(qpath) = callee.kind else {
+            return;
+        };
+        let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id) else {
+            return;
+        };
+        if !cx.tcx.is_intrinsic(def_id, sym::transmute) {
+            return;
+        };
+        let body_owner_def_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id);
+        let const_context = cx.tcx.hir_body_const_context(body_owner_def_id);
+        let args = cx.typeck_results().node_args(callee.hir_id);
+
+        let src = args.type_at(0);
+        let dst = args.type_at(1);
+
+        check_ptr_transmute_in_const(cx, expr, body_owner_def_id, const_context, src, dst);
+        check_unnecessary_transmute(cx, expr, callee, arg, const_context, src, dst);
+    }
+}
+
+/// Check for transmutes that exhibit undefined behavior.
+/// For example, transmuting pointers to integers in a const context.
+///
+/// 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 check_ptr_transmute_in_const<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx hir::Expr<'tcx>,
+    body_owner_def_id: LocalDefId,
+    const_context: Option<hir::ConstContext>,
+    src: Ty<'tcx>,
+    dst: Ty<'tcx>,
+) {
+    if matches!(const_context, Some(hir::ConstContext::ConstFn))
+        || matches!(cx.tcx.def_kind(body_owner_def_id), DefKind::AssocConst)
+    {
+        if src.is_raw_ptr() && dst.is_integral() {
+            cx.tcx.emit_node_span_lint(
+                PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
+                expr.hir_id,
+                expr.span,
+                UndefinedTransmuteLint,
+            );
+        }
+    }
+}
+
+/// Check for transmutes that overlap with stdlib methods.
+/// For example, transmuting `[u8; 4]` to `u32`.
+///
+/// We chose not to lint u8 -> bool transmutes, see #140431.
+fn check_unnecessary_transmute<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &'tcx hir::Expr<'tcx>,
+    callee: &'tcx hir::Expr<'tcx>,
+    arg: &'tcx hir::Expr<'tcx>,
+    const_context: Option<hir::ConstContext>,
+    src: Ty<'tcx>,
+    dst: Ty<'tcx>,
+) {
+    let callee_span = callee.span.find_ancestor_inside(expr.span).unwrap_or(callee.span);
+    let (sugg, help) = match (src.kind(), dst.kind()) {
+        // dont check the length; transmute does that for us.
+        // [u8; _] => primitive
+        (ty::Array(t, _), ty::Uint(_) | ty::Float(_) | ty::Int(_))
+            if *t.kind() == ty::Uint(ty::UintTy::U8) =>
+        {
+            (
+                Some(vec![(callee_span, format!("{dst}::from_ne_bytes"))]),
+                Some(
+                    "there's also `from_le_bytes` and `from_be_bytes` if you expect a particular byte order",
+                ),
+            )
+        }
+        // primitive => [u8; _]
+        (ty::Uint(_) | ty::Float(_) | ty::Int(_), ty::Array(t, _))
+            if *t.kind() == ty::Uint(ty::UintTy::U8) =>
+        {
+            (
+                Some(vec![(callee_span, format!("{src}::to_ne_bytes"))]),
+                Some(
+                    "there's also `to_le_bytes` and `to_be_bytes` if you expect a particular byte order",
+                ),
+            )
+        }
+        // char → u32
+        (ty::Char, ty::Uint(ty::UintTy::U32)) => {
+            (Some(vec![(callee_span, "u32::from".to_string())]), None)
+        }
+        // char (→ u32) → i32
+        (ty::Char, ty::Int(ty::IntTy::I32)) => (
+            Some(vec![
+                (callee_span, "u32::from".to_string()),
+                (expr.span.shrink_to_hi(), ".cast_signed()".to_string()),
+            ]),
+            None,
+        ),
+        // u32 → char
+        (ty::Uint(ty::UintTy::U32), ty::Char) => (
+            Some(vec![(callee_span, "char::from_u32_unchecked".to_string())]),
+            Some("consider using `char::from_u32(…).unwrap()`"),
+        ),
+        // i32 → char
+        (ty::Int(ty::IntTy::I32), ty::Char) => (
+            Some(vec![
+                (callee_span, "char::from_u32_unchecked(i32::cast_unsigned".to_string()),
+                (expr.span.shrink_to_hi(), ")".to_string()),
+            ]),
+            Some("consider using `char::from_u32(i32::cast_unsigned(…)).unwrap()`"),
+        ),
+        // uNN → iNN
+        (ty::Uint(_), ty::Int(_)) => {
+            (Some(vec![(callee_span, format!("{src}::cast_signed"))]), None)
+        }
+        // iNN → uNN
+        (ty::Int(_), ty::Uint(_)) => {
+            (Some(vec![(callee_span, format!("{src}::cast_unsigned"))]), None)
+        }
+        // fNN → usize, isize
+        (ty::Float(_), ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize)) => (
+            Some(vec![
+                (callee_span, format!("{src}::to_bits")),
+                (expr.span.shrink_to_hi(), format!(" as {dst}")),
+            ]),
+            None,
+        ),
+        // fNN (→ uNN) → iNN
+        (ty::Float(_), ty::Int(..)) => (
+            Some(vec![
+                (callee_span, format!("{src}::to_bits")),
+                (expr.span.shrink_to_hi(), ".cast_signed()".to_string()),
+            ]),
+            None,
+        ),
+        // fNN → uNN
+        (ty::Float(_), ty::Uint(..)) => {
+            (Some(vec![(callee_span, format!("{src}::to_bits"))]), None)
+        }
+        // xsize → fNN
+        (ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize), ty::Float(_)) => (
+            Some(vec![
+                (callee_span, format!("{dst}::from_bits")),
+                (arg.span.shrink_to_hi(), " as _".to_string()),
+            ]),
+            None,
+        ),
+        // iNN (→ uNN) → fNN
+        (ty::Int(_), ty::Float(_)) => (
+            Some(vec![
+                (callee_span, format!("{dst}::from_bits({src}::cast_unsigned")),
+                (expr.span.shrink_to_hi(), ")".to_string()),
+            ]),
+            None,
+        ),
+        // uNN → fNN
+        (ty::Uint(_), ty::Float(_)) => {
+            (Some(vec![(callee_span, format!("{dst}::from_bits"))]), None)
+        }
+        // bool → x8 in const context since `From::from` is not const yet
+        // FIXME: Consider arg expr's precedence to avoid parentheses.
+        // FIXME(const_traits): Remove this when `From::from` is constified.
+        (ty::Bool, ty::Int(..) | ty::Uint(..)) if const_context.is_some() => (
+            Some(vec![
+                (callee_span, "".to_string()),
+                (expr.span.shrink_to_hi(), format!(" as {dst}")),
+            ]),
+            None,
+        ),
+        // bool → x8 using `x8::from`
+        (ty::Bool, ty::Int(..) | ty::Uint(..)) => {
+            (Some(vec![(callee_span, format!("{dst}::from"))]), None)
+        }
+        _ => return,
+    };
+
+    cx.tcx.node_span_lint(UNNECESSARY_TRANSMUTES, expr.hir_id, expr.span, |diag| {
+        diag.primary_message("unnecessary transmute");
+        if let Some(sugg) = sugg {
+            diag.multipart_suggestion("replace this with", sugg, Applicability::MachineApplicable);
+        }
+        if let Some(help) = help {
+            diag.help(help);
+        }
+    });
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_undefined_transmute)]
+#[note]
+#[note(lint_note2)]
+#[help]
+pub(crate) struct UndefinedTransmuteLint;