Skip to content

Commit 722d284

Browse files
committed
Suggest {to,from}_ne_bytes for transmutations between arrays and integers, etc
1 parent f7cc13a commit 722d284

File tree

7 files changed

+150
-0
lines changed

7 files changed

+150
-0
lines changed

compiler/rustc_lint_defs/src/builtin.rs

+8
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ declare_lint_pass! {
8383
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
8484
PTR_CAST_ADD_AUTO_TO_OBJECT,
8585
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
86+
REDUNDANT_TRANSMUTATION,
8687
PUB_USE_OF_PRIVATE_EXTERN_CRATE,
8788
REDUNDANT_IMPORTS,
8889
REDUNDANT_LIFETIMES,
@@ -5034,6 +5035,13 @@ declare_lint! {
50345035
"detects pointer to integer transmutes in const functions and associated constants",
50355036
}
50365037

5038+
declare_lint! {
5039+
/// Wow such docs.
5040+
pub REDUNDANT_TRANSMUTATION,
5041+
Warn,
5042+
"detects transmutes that are shadowed by std methods"
5043+
}
5044+
50375045
declare_lint! {
50385046
/// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location,
50395047
/// that runs a custom `Drop` destructor.

compiler/rustc_mir_transform/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,6 @@ mir_transform_undefined_transmute = pointers cannot be transmuted to integers du
8383
.note2 = avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
8484
.help = for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
8585
86+
mir_transform_redundant_transmute = this transmute could be performed safely
87+
8688
mir_transform_unknown_pass_name = MIR pass `{$name}` is unknown and will be ignored
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use rustc_middle::mir::visit::Visitor;
2+
use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind};
3+
use rustc_middle::ty::{Ty, TyCtxt, UintTy};
4+
use rustc_session::lint::builtin::REDUNDANT_TRANSMUTATION;
5+
use rustc_span::source_map::Spanned;
6+
use rustc_span::{Span, sym};
7+
use rustc_type_ir::TyKind::*;
8+
9+
use crate::errors;
10+
11+
/// Check for transmutes that overlap with stdlib methods.
12+
/// For example, transmuting `[u8; 4]` to `u32`.
13+
pub(super) struct CheckRedundantTransmutes;
14+
15+
impl<'tcx> crate::MirLint<'tcx> for CheckRedundantTransmutes {
16+
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
17+
let mut checker = RedundantTransmutesChecker { body, tcx };
18+
checker.visit_body(body);
19+
}
20+
}
21+
22+
struct RedundantTransmutesChecker<'a, 'tcx> {
23+
body: &'a Body<'tcx>,
24+
tcx: TyCtxt<'tcx>,
25+
}
26+
27+
impl<'a, 'tcx> RedundantTransmutesChecker<'a, 'tcx> {
28+
/// This functions checks many things:
29+
/// 1. if the source (`transmute::<$x, _>`) is `[u8; _]`, check if the output is a `{uif}xx`
30+
/// 2. swap and do the same check.
31+
/// 3. in the case of `char` → `u32` suggest `to_u32` and `from_u32_unchecked`
32+
/// 4. `uxx` → `ixx` should be `as`
33+
/// Returns the replacement.
34+
fn is_redundant_transmute(
35+
&self,
36+
function: &Operand<'tcx>,
37+
arg: String,
38+
span: Span,
39+
) -> Option<errors::RedundantTransmute> {
40+
let fn_sig = function.ty(self.body, self.tcx).fn_sig(self.tcx).skip_binder();
41+
let [input] = fn_sig.inputs() else { return None };
42+
43+
// Checks if `x` is `[u8; _]`
44+
let is_u8s = |x: Ty<'tcx>| matches!(x.kind(), Array(t, _) if *t.kind() == Uint(UintTy::U8));
45+
// dont check the length; transmute does that for us.
46+
if is_u8s(*input) && matches!(fn_sig.output().kind(), Uint(..) | Float(_) | Int(_)) {
47+
// FIXME: get the whole expression out?
48+
return Some(errors::RedundantTransmute {
49+
sugg: format!("{}::from_ne_bytes({arg})", fn_sig.output()),
50+
help: Some(
51+
"there's also `from_le_bytes` and `from_ne_bytes` if you expect a particular byte order",
52+
),
53+
span,
54+
});
55+
}
56+
if is_u8s(fn_sig.output()) && matches!(input.kind(), Uint(..) | Float(_) | Int(_)) {
57+
return Some(errors::RedundantTransmute {
58+
sugg: format!("{input}::to_ne_bytes({arg})"),
59+
help: Some(
60+
"there's also `to_le_bytes` and `to_ne_bytes` if you expect a particular byte order",
61+
),
62+
span,
63+
});
64+
}
65+
return match input.kind() {
66+
// char → u32
67+
Char => matches!(fn_sig.output().kind(), Uint(UintTy::U32)).then(|| {
68+
errors::RedundantTransmute { sugg: format!("({arg}) as u32"), help: None, span }
69+
}),
70+
// u32 → char
71+
Uint(UintTy::U32) if *fn_sig.output().kind() == Char => {
72+
Some(errors::RedundantTransmute {
73+
sugg: format!("char::from_u32_unchecked({arg})"),
74+
help: Some("consider `char::from_u32(…).unwrap()`"),
75+
span,
76+
})
77+
}
78+
// bool → (uNN ↔ iNN)
79+
Bool | Uint(..) | Int(..) => {
80+
matches!(fn_sig.output().kind(), Int(..) | Uint(..)).then(|| {
81+
errors::RedundantTransmute {
82+
sugg: format!("({arg}) as {}", fn_sig.output()),
83+
help: None,
84+
span,
85+
}
86+
})
87+
}
88+
_ => None,
89+
};
90+
}
91+
}
92+
93+
impl<'tcx> Visitor<'tcx> for RedundantTransmutesChecker<'_, 'tcx> {
94+
// Check each block's terminator for calls to pointer to integer transmutes
95+
// in const functions or associated constants and emit a lint.
96+
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
97+
if let TerminatorKind::Call { func, args, .. } = &terminator.kind
98+
&& let [Spanned { span: arg, .. }] = **args
99+
&& let Some((func_def_id, _)) = func.const_fn_def()
100+
&& self.tcx.is_intrinsic(func_def_id, sym::transmute)
101+
&& let span = self.body.source_info(location).span
102+
&& let Some(lint) = self.is_redundant_transmute(
103+
func,
104+
self.tcx.sess.source_map().span_to_snippet(arg).expect("ok"),
105+
span,
106+
)
107+
&& let Some(call_id) = self.body.source.def_id().as_local()
108+
{
109+
let hir_id = self.tcx.local_def_id_to_hir_id(call_id);
110+
111+
self.tcx.emit_node_span_lint(REDUNDANT_TRANSMUTATION, hir_id, span, lint);
112+
}
113+
}
114+
}

compiler/rustc_mir_transform/src/errors.rs

+20
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,26 @@ pub(crate) struct MustNotSuspendReason {
158158
pub reason: String,
159159
}
160160

161+
pub(crate) struct RedundantTransmute {
162+
pub span: Span,
163+
pub sugg: String,
164+
pub help: Option<&'static str>,
165+
}
166+
167+
// Needed for def_path_str
168+
impl<'a> LintDiagnostic<'a, ()> for RedundantTransmute {
169+
fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
170+
diag.primary_message(fluent::mir_transform_redundant_transmute);
171+
diag.span_suggestion(
172+
self.span,
173+
"replace `transmute`",
174+
self.sugg,
175+
lint::Applicability::MachineApplicable,
176+
);
177+
self.help.map(|help| diag.help(help));
178+
}
179+
}
180+
161181
#[derive(LintDiagnostic)]
162182
#[diag(mir_transform_undefined_transmute)]
163183
#[note]

compiler/rustc_mir_transform/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ declare_passes! {
120120
mod check_const_item_mutation : CheckConstItemMutation;
121121
mod check_packed_ref : CheckPackedRef;
122122
mod check_undefined_transmutes : CheckUndefinedTransmutes;
123+
mod check_redundant_transmutes: CheckRedundantTransmutes;
123124
// This pass is public to allow external drivers to perform MIR cleanup
124125
pub mod cleanup_post_borrowck : CleanupPostBorrowck;
125126

@@ -383,6 +384,7 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
383384
&Lint(check_const_item_mutation::CheckConstItemMutation),
384385
&Lint(function_item_references::FunctionItemReferences),
385386
&Lint(check_undefined_transmutes::CheckUndefinedTransmutes),
387+
&Lint(check_redundant_transmutes::CheckRedundantTransmutes),
386388
// What we need to do constant evaluation.
387389
&simplify::SimplifyCfg::Initial,
388390
&Lint(sanity_check::SanityCheck),

library/core/src/num/int_macros.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3632,6 +3632,7 @@ macro_rules! int_impl {
36323632
/// ```
36333633
#[stable(feature = "int_to_from_bytes", since = "1.32.0")]
36343634
#[rustc_const_stable(feature = "const_int_conversion", since = "1.44.0")]
3635+
#[cfg_attr(not(bootstrap), allow(redundant_transmutation))]
36353636
// SAFETY: const sound because integers are plain old datatypes so we can always
36363637
// transmute them to arrays of bytes
36373638
#[must_use = "this returns the result of the operation, \
@@ -3735,6 +3736,7 @@ macro_rules! int_impl {
37353736
/// ```
37363737
#[stable(feature = "int_to_from_bytes", since = "1.32.0")]
37373738
#[rustc_const_stable(feature = "const_int_conversion", since = "1.44.0")]
3739+
#[cfg_attr(not(bootstrap), allow(redundant_transmutation))]
37383740
#[must_use]
37393741
// SAFETY: const sound because integers are plain old datatypes so we can always
37403742
// transmute to them

library/core/src/num/uint_macros.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3422,6 +3422,7 @@ macro_rules! uint_impl {
34223422
#[rustc_const_stable(feature = "const_int_conversion", since = "1.44.0")]
34233423
#[must_use = "this returns the result of the operation, \
34243424
without modifying the original"]
3425+
#[cfg_attr(not(bootstrap), allow(redundant_transmutation))]
34253426
// SAFETY: const sound because integers are plain old datatypes so we can always
34263427
// transmute them to arrays of bytes
34273428
#[inline]
@@ -3523,6 +3524,7 @@ macro_rules! uint_impl {
35233524
/// ```
35243525
#[stable(feature = "int_to_from_bytes", since = "1.32.0")]
35253526
#[rustc_const_stable(feature = "const_int_conversion", since = "1.44.0")]
3527+
#[cfg_attr(not(bootstrap), allow(redundant_transmutation))]
35263528
#[must_use]
35273529
// SAFETY: const sound because integers are plain old datatypes so we can always
35283530
// transmute to them

0 commit comments

Comments
 (0)