about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2025-05-25 15:05:22 +0000
committerMichael Goulet <michael@errs.io>2025-05-25 15:57:48 +0000
commit5370c5753f3f769d27832f81ceefd4141ba1ee0c (patch)
treea274043429f8a7f039a6d1c512071237471cfbfb
parenta8ae2af9670635a9138429ccba1ffb76787afbb1 (diff)
downloadrust-5370c5753f3f769d27832f81ceefd4141ba1ee0c.tar.gz
rust-5370c5753f3f769d27832f81ceefd4141ba1ee0c.zip
Make PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS into a HIR 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.rs102
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs35
-rw-r--r--compiler/rustc_mir_transform/messages.ftl5
-rw-r--r--compiler/rustc_mir_transform/src/check_undefined_transmutes.rs77
-rw-r--r--compiler/rustc_mir_transform/src/errors.rs7
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs2
-rw-r--r--tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.rs25
-rw-r--r--tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.stderr40
10 files changed, 136 insertions, 165 deletions
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index 7fdf26bf3af..9bef1c5b877 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -805,6 +805,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 4ff586a79a6..e1c981b09e6 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::*;
@@ -245,6 +247,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..8395bcf7cad
--- /dev/null
+++ b/compiler/rustc_lint/src/transmute.rs
@@ -0,0 +1,102 @@
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{self as hir};
+use rustc_macros::LintDiagnostic;
+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",
+}
+
+pub(crate) struct CheckTransmutes;
+
+impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS]);
+
+impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+        let hir::ExprKind::Call(callee, _) = 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 Some(context) = cx.tcx.hir_body_const_context(body_owner_def_id) else {
+            return;
+        };
+        let args = cx.typeck_results().node_args(callee.hir_id);
+
+        let src = args.type_at(0);
+        let dst = args.type_at(1);
+
+        // 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.
+        if matches!(context, 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,
+                );
+            }
+        }
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_undefined_transmute)]
+#[note]
+#[note(lint_note2)]
+#[help]
+pub(crate) struct UndefinedTransmuteLint;
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index b8d242bad86..2e1aaeedba1 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -79,7 +79,6 @@ declare_lint_pass! {
         PRIVATE_BOUNDS,
         PRIVATE_INTERFACES,
         PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
-        PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
         PUB_USE_OF_PRIVATE_EXTERN_CRATE,
         REDUNDANT_IMPORTS,
         REDUNDANT_LIFETIMES,
@@ -4852,40 +4851,6 @@ declare_lint! {
 }
 
 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
diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl
index a1264471a2d..ada705a5163 100644
--- a/compiler/rustc_mir_transform/messages.ftl
+++ b/compiler/rustc_mir_transform/messages.ftl
@@ -78,10 +78,5 @@ mir_transform_unconditional_recursion = function cannot return without recursing
 
 mir_transform_unconditional_recursion_call_site_label = recursive call site
 
-mir_transform_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
-
 mir_transform_unknown_pass_name = MIR pass `{$name}` is unknown and will be ignored
 mir_transform_unnecessary_transmute = unnecessary transmute
diff --git a/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs b/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs
deleted file mode 100644
index daddb5dedbc..00000000000
--- a/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-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_raw_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,
-            );
-        }
-    }
-}
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 5b03a4987ed..9777cb56f13 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -178,13 +178,6 @@ impl<'a> LintDiagnostic<'a, ()> for UnnecessaryTransmute {
     }
 }
 
-#[derive(LintDiagnostic)]
-#[diag(mir_transform_undefined_transmute)]
-#[note]
-#[note(mir_transform_note2)]
-#[help]
-pub(crate) struct UndefinedTransmute;
-
 #[derive(Diagnostic)]
 #[diag(mir_transform_force_inline)]
 #[note]
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 10dbb3437dc..8d4e9e30f4f 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -123,7 +123,6 @@ declare_passes! {
     mod check_const_item_mutation : CheckConstItemMutation;
     mod check_null : CheckNull;
     mod check_packed_ref : CheckPackedRef;
-    mod check_undefined_transmutes : CheckUndefinedTransmutes;
     mod check_unnecessary_transmutes: CheckUnnecessaryTransmutes;
     // This pass is public to allow external drivers to perform MIR cleanup
     pub mod cleanup_post_borrowck : CleanupPostBorrowck;
@@ -390,7 +389,6 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
             &Lint(check_packed_ref::CheckPackedRef),
             &Lint(check_const_item_mutation::CheckConstItemMutation),
             &Lint(function_item_references::FunctionItemReferences),
-            &Lint(check_undefined_transmutes::CheckUndefinedTransmutes),
             &Lint(check_unnecessary_transmutes::CheckUnnecessaryTransmutes),
             // What we need to do constant evaluation.
             &simplify::SimplifyCfg::Initial,
diff --git a/tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.rs b/tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.rs
index 19c78f019aa..5fab075785a 100644
--- a/tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.rs
+++ b/tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.rs
@@ -1,7 +1,9 @@
+#![deny(ptr_to_integer_transmute_in_consts)]
+
 const fn foo(ptr: *const u8) -> usize {
     unsafe {
         std::mem::transmute(ptr)
-        //~^ WARN pointers cannot be transmuted to integers
+        //~^ ERROR pointers cannot be transmuted to integers
     }
 }
 
@@ -11,7 +13,7 @@ trait Human {
         let ptr: *const usize = &value;
         unsafe {
             std::mem::transmute(ptr)
-            //~^ WARN pointers cannot be transmuted to integers
+            //~^ ERROR pointers cannot be transmuted to integers
         }
     };
 
@@ -28,7 +30,7 @@ impl<T> Type<T> {
         let ptr: *const usize = &value;
         unsafe {
             std::mem::transmute(ptr)
-            //~^ WARN pointers cannot be transmuted to integers
+            //~^ ERROR pointers cannot be transmuted to integers
         }
     };
 
@@ -38,9 +40,7 @@ impl<T> Type<T> {
 }
 
 fn control(ptr: *const u8) -> usize {
-    unsafe {
-        std::mem::transmute(ptr)
-    }
+    unsafe { std::mem::transmute(ptr) }
 }
 
 struct ControlStruct;
@@ -49,22 +49,15 @@ impl ControlStruct {
     fn new() -> usize {
         let value = 10;
         let ptr: *const i32 = &value;
-        unsafe {
-            std::mem::transmute(ptr)
-        }
+        unsafe { std::mem::transmute(ptr) }
     }
 }
 
-
 const fn zoom(ptr: *const u8) -> usize {
     unsafe {
         std::mem::transmute(ptr)
-        //~^ WARN pointers cannot be transmuted to integers
+        //~^ ERROR pointers cannot be transmuted to integers
     }
 }
 
-fn main() {
-    const a: u8 = 10;
-    const value: usize = zoom(&a);
-    //~^ ERROR evaluation of constant value failed
-}
+fn main() {}
diff --git a/tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.stderr b/tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.stderr
index ca6ad9408ab..2a9d9b5cb96 100644
--- a/tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.stderr
+++ b/tests/ui/consts/const-eval/ptr-to-int-transmute-in-consts-issue-87525.stderr
@@ -1,5 +1,5 @@
-warning: pointers cannot be transmuted to integers during const eval
-  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:61:9
+error: pointers cannot be transmuted to integers during const eval
+  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:5:9
    |
 LL |         std::mem::transmute(ptr)
    |         ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -7,29 +7,24 @@ LL |         std::mem::transmute(ptr)
    = note: at compile-time, pointers do not have an integer value
    = note: 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
-   = note: `#[warn(ptr_to_integer_transmute_in_consts)]` on by default
-
-error[E0080]: evaluation of constant value failed
-  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:68:26
-   |
-LL |     const value: usize = zoom(&a);
-   |                          ^^^^^^^^ unable to turn pointer into integer
+note: the lint level is defined here
+  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:1:9
    |
-   = help: this code performed an operation that depends on the underlying bytes representing a pointer
-   = help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
+LL | #![deny(ptr_to_integer_transmute_in_consts)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-warning: pointers cannot be transmuted to integers during const eval
-  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:3:9
+error: pointers cannot be transmuted to integers during const eval
+  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:15:13
    |
-LL |         std::mem::transmute(ptr)
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^
+LL |             std::mem::transmute(ptr)
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: at compile-time, pointers do not have an integer value
    = note: 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
 
-warning: pointers cannot be transmuted to integers during const eval
-  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:13:13
+error: pointers cannot be transmuted to integers during const eval
+  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:32:13
    |
 LL |             std::mem::transmute(ptr)
    |             ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -38,16 +33,15 @@ LL |             std::mem::transmute(ptr)
    = note: 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
 
-warning: pointers cannot be transmuted to integers during const eval
-  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:30:13
+error: pointers cannot be transmuted to integers during const eval
+  --> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:58:9
    |
-LL |             std::mem::transmute(ptr)
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         std::mem::transmute(ptr)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: at compile-time, pointers do not have an integer value
    = note: 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
 
-error: aborting due to 1 previous error; 4 warnings emitted
+error: aborting due to 4 previous errors
 
-For more information about this error, try `rustc --explain E0080`.