about summary refs log tree commit diff
path: root/compiler/rustc_mir_transform/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-10-06 02:39:23 +0000
committerbors <bors@rust-lang.org>2024-10-06 02:39:23 +0000
commitdaebce42473ffbd5c7587b4a6cdd19ec1cc0a74d (patch)
treef462fec11a02670475fbd7a5edec4d962b079eca /compiler/rustc_mir_transform/src
parent85e2f55d8291e643b5b4c98ee09db301379d63a6 (diff)
parentab8673501ce13573c06b5989b179f5cfed85c771 (diff)
downloadrust-daebce42473ffbd5c7587b4a6cdd19ec1cc0a74d.tar.gz
rust-daebce42473ffbd5c7587b4a6cdd19ec1cc0a74d.zip
Auto merge of #130540 - veera-sivarajan:fix-87525, r=estebank
Add a Lint for Pointer to Integer Transmutes in Consts

Fixes #87525

This PR adds a MirLint for pointer to integer transmutes in const functions and associated consts. The implementation closely follows this comment: https://github.com/rust-lang/rust/pull/85769#issuecomment-880969112. More details about the implementation can be found in the comments.

Note: This could break some sound code as mentioned by RalfJung in https://github.com/rust-lang/rust/pull/85769#issuecomment-886491680:

> ... technically const-code could transmute/cast an int to a ptr and then transmute it back and that would be correct -- so the lint will deny some sound code. Does not seem terribly likely though.

References:
1. https://doc.rust-lang.org/std/mem/fn.transmute.html
2. https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants
Diffstat (limited to 'compiler/rustc_mir_transform/src')
-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
3 files changed, 86 insertions, 0 deletions
diff --git a/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs b/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs
new file mode 100644
index 00000000000..8ba14a1158e
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs
@@ -0,0 +1,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,
+            );
+        }
+    }
+}
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 84d44c2ab4c..fa279ace431 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -121,3 +121,10 @@ pub(crate) struct MustNotSuspendReason {
     pub span: Span,
     pub reason: String,
 }
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_undefined_transmute)]
+#[note]
+#[note(mir_transform_note2)]
+#[help]
+pub(crate) struct UndefinedTransmute;
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 4c090665992..d184328748f 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -51,6 +51,7 @@ mod add_subtyping_projections;
 mod check_alignment;
 mod check_const_item_mutation;
 mod check_packed_ref;
+mod check_undefined_transmutes;
 // This pass is public to allow external drivers to perform MIR cleanup
 pub mod cleanup_post_borrowck;
 mod copy_prop;
@@ -298,6 +299,7 @@ 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),
             // What we need to do constant evaluation.
             &simplify::SimplifyCfg::Initial,
             &Lint(sanity_check::SanityCheck),