about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorMatthias Krüger <476013+matthiaskrgr@users.noreply.github.com>2025-04-24 17:19:42 +0200
committerGitHub <noreply@github.com>2025-04-24 17:19:42 +0200
commit53afa97eb79041572e5c6f07297f4e40f27e972b (patch)
tree519be6cf3b8edf11ed53cbecf1122e4205c78a4d /compiler
parent3c877f6a477380ed61155d3bf816df09c9e05b9e (diff)
parent6921a51b4c30d495f8768340d22c44a89e63a782 (diff)
downloadrust-53afa97eb79041572e5c6f07297f4e40f27e972b.tar.gz
rust-53afa97eb79041572e5c6f07297f4e40f27e972b.zip
Rollup merge of #136083 - bend-n:⃤⃤, r=lcnr
Suggest {to,from}_ne_bytes for transmutations between arrays and integers, etc

implements #136067

Rust has helper methods for many kinds of safe transmutes, for example integer<->bytes. This is a lint against using transmute for these cases.

```rs
fn bytes_at_home(x: [u8; 4]) -> u32 {
   transmute(x)
}

// other examples
transmute::<[u8; 2], u16>();
transmute::<[u8; 8], f64>();
transmute::<u32, [u8; 4]>();
transmute::<char, u32>();
transmute::<u32, char>();
```
It would be handy to suggest `u32::from_ne_bytes(x)`.
This is implemented for `[u8; _]` -> `{float int}`

This also implements the cases:
`fXX` <-> `uXX` = `{from_bits, to_bits}`
`uXX` -> `iXX` via `cast_unsigned` and `cast_signed`
{`char` -> `u32`, `bool` -> `n8`} via `from`
`u32` -> `char` via `from_u32_unchecked` (note: notes `from_u32().unwrap()`) (contested)
`u8` -> `bool` via `==` (debatable)

---
try-job: aarch64-gnu
try-job: test-various
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_codegen_cranelift/example/example.rs2
-rw-r--r--compiler/rustc_codegen_gcc/example/example.rs8
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs25
-rw-r--r--compiler/rustc_mir_transform/messages.ftl1
-rw-r--r--compiler/rustc_mir_transform/src/check_unnecessary_transmutes.rs100
-rw-r--r--compiler/rustc_mir_transform/src/errors.rs20
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs2
7 files changed, 151 insertions, 7 deletions
diff --git a/compiler/rustc_codegen_cranelift/example/example.rs b/compiler/rustc_codegen_cranelift/example/example.rs
index 1ef2aa5dd8e..aeb38331edb 100644
--- a/compiler/rustc_codegen_cranelift/example/example.rs
+++ b/compiler/rustc_codegen_cranelift/example/example.rs
@@ -1,6 +1,6 @@
 #![feature(no_core, unboxed_closures)]
 #![no_core]
-#![allow(dead_code)]
+#![allow(dead_code, unnecessary_transmutes)]
 
 extern crate mini_core;
 
diff --git a/compiler/rustc_codegen_gcc/example/example.rs b/compiler/rustc_codegen_gcc/example/example.rs
index 03470b74d0a..888fa89201e 100644
--- a/compiler/rustc_codegen_gcc/example/example.rs
+++ b/compiler/rustc_codegen_gcc/example/example.rs
@@ -1,6 +1,6 @@
 #![feature(no_core, unboxed_closures)]
 #![no_core]
-#![allow(dead_code)]
+#![allow(dead_code, unnecessary_transmutes)]
 
 extern crate mini_core;
 
@@ -11,11 +11,7 @@ fn abc(a: u8) -> u8 {
 }
 
 fn bcd(b: bool, a: u8) -> u8 {
-    if b {
-        a * 2
-    } else {
-        a * 3
-    }
+    if b { a * 2 } else { a * 3 }
 }
 
 fn call() {
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index a49eb76734f..17d501c5730 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -117,6 +117,7 @@ declare_lint_pass! {
         UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
         UNNAMEABLE_TEST_ITEMS,
         UNNAMEABLE_TYPES,
+        UNNECESSARY_TRANSMUTES,
         UNREACHABLE_CODE,
         UNREACHABLE_PATTERNS,
         UNSAFE_ATTR_OUTSIDE_UNSAFE,
@@ -4910,6 +4911,30 @@ declare_lint! {
 }
 
 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 are shadowed by std methods"
+}
+
+declare_lint! {
     /// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location,
     /// that runs a custom `Drop` destructor.
     /// Some of them may be dropped earlier in Edition 2024 that they used to in Edition 2021 and prior.
diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl
index 5628f4c9381..a1264471a2d 100644
--- a/compiler/rustc_mir_transform/messages.ftl
+++ b/compiler/rustc_mir_transform/messages.ftl
@@ -84,3 +84,4 @@ mir_transform_undefined_transmute = pointers cannot be transmuted to integers du
     .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_unnecessary_transmutes.rs b/compiler/rustc_mir_transform/src/check_unnecessary_transmutes.rs
new file mode 100644
index 00000000000..8be782dcbf0
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/check_unnecessary_transmutes.rs
@@ -0,0 +1,100 @@
+use rustc_middle::mir::visit::Visitor;
+use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind};
+use rustc_middle::ty::*;
+use rustc_session::lint::builtin::UNNECESSARY_TRANSMUTES;
+use rustc_span::source_map::Spanned;
+use rustc_span::{Span, sym};
+
+use crate::errors::UnnecessaryTransmute as Error;
+
+/// Check for transmutes that overlap with stdlib methods.
+/// For example, transmuting `[u8; 4]` to `u32`.
+pub(super) struct CheckUnnecessaryTransmutes;
+
+impl<'tcx> crate::MirLint<'tcx> for CheckUnnecessaryTransmutes {
+    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
+        let mut checker = UnnecessaryTransmuteChecker { body, tcx };
+        checker.visit_body(body);
+    }
+}
+
+struct UnnecessaryTransmuteChecker<'a, 'tcx> {
+    body: &'a Body<'tcx>,
+    tcx: TyCtxt<'tcx>,
+}
+
+impl<'a, 'tcx> UnnecessaryTransmuteChecker<'a, 'tcx> {
+    fn is_unnecessary_transmute(
+        &self,
+        function: &Operand<'tcx>,
+        arg: String,
+        span: Span,
+    ) -> Option<Error> {
+        let fn_sig = function.ty(self.body, self.tcx).fn_sig(self.tcx).skip_binder();
+        let [input] = fn_sig.inputs() else { return None };
+
+        let err = |sugg| Error { span, sugg, help: None };
+
+        Some(match (input.kind(), fn_sig.output().kind()) {
+            // dont check the length; transmute does that for us.
+            // [u8; _] => primitive
+            (Array(t, _), Uint(_) | Float(_) | Int(_)) if *t.kind() == Uint(UintTy::U8) => Error {
+                sugg: format!("{}::from_ne_bytes({arg})", fn_sig.output()),
+                help: Some(
+                    "there's also `from_le_bytes` and `from_be_bytes` if you expect a particular byte order",
+                ),
+                span,
+            },
+            // primitive => [u8; _]
+            (Uint(_) | Float(_) | Int(_), Array(t, _)) if *t.kind() == Uint(UintTy::U8) => Error {
+                sugg: format!("{input}::to_ne_bytes({arg})"),
+                help: Some(
+                    "there's also `to_le_bytes` and `to_be_bytes` if you expect a particular byte order",
+                ),
+                span,
+            },
+            // char → u32
+            (Char, Uint(UintTy::U32)) => err(format!("u32::from({arg})")),
+            // u32 → char
+            (Uint(UintTy::U32), Char) => Error {
+                sugg: format!("char::from_u32_unchecked({arg})"),
+                help: Some("consider `char::from_u32(…).unwrap()`"),
+                span,
+            },
+            // uNN → iNN
+            (Uint(ty), Int(_)) => err(format!("{}::cast_signed({arg})", ty.name_str())),
+            // iNN → uNN
+            (Int(ty), Uint(_)) => err(format!("{}::cast_unsigned({arg})", ty.name_str())),
+            // fNN → uNN
+            (Float(ty), Uint(..)) => err(format!("{}::to_bits({arg})", ty.name_str())),
+            // uNN → fNN
+            (Uint(_), Float(ty)) => err(format!("{}::from_bits({arg})", ty.name_str())),
+            // bool → { x8 }
+            (Bool, Int(..) | Uint(..)) => err(format!("({arg}) as {}", fn_sig.output())),
+            // u8 → bool
+            (Uint(_), Bool) => err(format!("({arg} == 1)")),
+            _ => return None,
+        })
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for UnnecessaryTransmuteChecker<'_, '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, args, .. } = &terminator.kind
+            && let [Spanned { span: arg, .. }] = **args
+            && let Some((func_def_id, _)) = func.const_fn_def()
+            && self.tcx.is_intrinsic(func_def_id, sym::transmute)
+            && let span = self.body.source_info(location).span
+            && let Some(lint) = self.is_unnecessary_transmute(
+                func,
+                self.tcx.sess.source_map().span_to_snippet(arg).expect("ok"),
+                span,
+            )
+            && let Some(hir_id) = terminator.source_info.scope.lint_root(&self.body.source_scopes)
+        {
+            self.tcx.emit_node_span_lint(UNNECESSARY_TRANSMUTES, hir_id, span, lint);
+        }
+    }
+}
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 29698b0c2e4..5b03a4987ed 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -158,6 +158,26 @@ pub(crate) struct MustNotSuspendReason {
     pub reason: String,
 }
 
+pub(crate) struct UnnecessaryTransmute {
+    pub span: Span,
+    pub sugg: String,
+    pub help: Option<&'static str>,
+}
+
+// Needed for def_path_str
+impl<'a> LintDiagnostic<'a, ()> for UnnecessaryTransmute {
+    fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
+        diag.primary_message(fluent::mir_transform_unnecessary_transmute);
+        diag.span_suggestion(
+            self.span,
+            "replace this with",
+            self.sugg,
+            lint::Applicability::MachineApplicable,
+        );
+        self.help.map(|help| diag.help(help));
+    }
+}
+
 #[derive(LintDiagnostic)]
 #[diag(mir_transform_undefined_transmute)]
 #[note]
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 4d74ecddfb0..6977d23bd0e 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -125,6 +125,7 @@ declare_passes! {
     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;
 
@@ -391,6 +392,7 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
             &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,
             &Lint(sanity_check::SanityCheck),