Skip to content

Commit 295a8d5

Browse files
Make UNNECESSARY_TRANSMUTES into a HIR lint
1 parent 5370c57 commit 295a8d5

File tree

7 files changed

+374
-268
lines changed

7 files changed

+374
-268
lines changed

compiler/rustc_lint/src/transmute.rs

Lines changed: 204 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use rustc_errors::Applicability;
12
use rustc_hir::def::{DefKind, Res};
3+
use rustc_hir::def_id::LocalDefId;
24
use rustc_hir::{self as hir};
35
use rustc_macros::LintDiagnostic;
6+
use rustc_middle::ty::{self, Ty};
47
use rustc_session::{declare_lint, impl_lint_pass};
58
use rustc_span::sym;
69

@@ -40,13 +43,37 @@ declare_lint! {
4043
"detects pointer to integer transmutes in const functions and associated constants",
4144
}
4245

46+
declare_lint! {
47+
/// The `unnecessary_transmutes` lint detects transmutations that have safer alternatives.
48+
///
49+
/// ### Example
50+
///
51+
/// ```rust
52+
/// fn bytes_at_home(x: [u8; 4]) -> u32 {
53+
/// unsafe { std::mem::transmute(x) }
54+
/// }
55+
/// ```
56+
///
57+
/// {{produces}}
58+
///
59+
/// ### Explanation
60+
///
61+
/// Using an explicit method is preferable over calls to
62+
/// [`transmute`](https://doc.rust-lang.org/std/mem/fn.transmute.html) as
63+
/// they more clearly communicate the intent, are easier to review, and
64+
/// are less likely to accidentally result in unsoundness.
65+
pub UNNECESSARY_TRANSMUTES,
66+
Warn,
67+
"detects transmutes that can also be achieved by other operations"
68+
}
69+
4370
pub(crate) struct CheckTransmutes;
4471

45-
impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS]);
72+
impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]);
4673

4774
impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
4875
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
49-
let hir::ExprKind::Call(callee, _) = expr.kind else {
76+
let hir::ExprKind::Call(callee, [arg]) = expr.kind else {
5077
return;
5178
};
5279
let hir::ExprKind::Path(qpath) = callee.kind else {
@@ -59,41 +86,190 @@ impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
5986
return;
6087
};
6188
let body_owner_def_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id);
62-
let Some(context) = cx.tcx.hir_body_const_context(body_owner_def_id) else {
63-
return;
64-
};
89+
let const_context = cx.tcx.hir_body_const_context(body_owner_def_id);
6590
let args = cx.typeck_results().node_args(callee.hir_id);
6691

6792
let src = args.type_at(0);
6893
let dst = args.type_at(1);
6994

70-
// Check for transmutes that exhibit undefined behavior.
71-
// For example, transmuting pointers to integers in a const context.
72-
//
73-
// Why do we consider const functions and associated constants only?
74-
//
75-
// Generally, undefined behavior in const items are handled by the evaluator.
76-
// But, const functions and associated constants are evaluated only when referenced.
77-
// This can result in undefined behavior in a library going unnoticed until
78-
// the function or constant is actually used.
79-
//
80-
// Therefore, we only consider const functions and associated constants here and leave
81-
// other const items to be handled by the evaluator.
82-
if matches!(context, hir::ConstContext::ConstFn)
83-
|| matches!(cx.tcx.def_kind(body_owner_def_id), DefKind::AssocConst)
84-
{
85-
if src.is_raw_ptr() && dst.is_integral() {
86-
cx.tcx.emit_node_span_lint(
87-
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
88-
expr.hir_id,
89-
expr.span,
90-
UndefinedTransmuteLint,
91-
);
92-
}
95+
check_ptr_transmute_in_const(cx, expr, body_owner_def_id, const_context, src, dst);
96+
check_unnecessary_transmute(cx, expr, callee, arg, const_context, src, dst);
97+
}
98+
}
99+
100+
/// Check for transmutes that exhibit undefined behavior.
101+
/// For example, transmuting pointers to integers in a const context.
102+
///
103+
/// Why do we consider const functions and associated constants only?
104+
///
105+
/// Generally, undefined behavior in const items are handled by the evaluator.
106+
/// But, const functions and associated constants are evaluated only when referenced.
107+
/// This can result in undefined behavior in a library going unnoticed until
108+
/// the function or constant is actually used.
109+
///
110+
/// Therefore, we only consider const functions and associated constants here and leave
111+
/// other const items to be handled by the evaluator.
112+
fn check_ptr_transmute_in_const<'tcx>(
113+
cx: &LateContext<'tcx>,
114+
expr: &'tcx hir::Expr<'tcx>,
115+
body_owner_def_id: LocalDefId,
116+
const_context: Option<hir::ConstContext>,
117+
src: Ty<'tcx>,
118+
dst: Ty<'tcx>,
119+
) {
120+
if matches!(const_context, Some(hir::ConstContext::ConstFn))
121+
|| matches!(cx.tcx.def_kind(body_owner_def_id), DefKind::AssocConst)
122+
{
123+
if src.is_raw_ptr() && dst.is_integral() {
124+
cx.tcx.emit_node_span_lint(
125+
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
126+
expr.hir_id,
127+
expr.span,
128+
UndefinedTransmuteLint,
129+
);
93130
}
94131
}
95132
}
96133

134+
/// Check for transmutes that overlap with stdlib methods.
135+
/// For example, transmuting `[u8; 4]` to `u32`.
136+
///
137+
/// We chose not to lint u8 -> bool transmutes, see #140431.
138+
fn check_unnecessary_transmute<'tcx>(
139+
cx: &LateContext<'tcx>,
140+
expr: &'tcx hir::Expr<'tcx>,
141+
callee: &'tcx hir::Expr<'tcx>,
142+
arg: &'tcx hir::Expr<'tcx>,
143+
const_context: Option<hir::ConstContext>,
144+
src: Ty<'tcx>,
145+
dst: Ty<'tcx>,
146+
) {
147+
let callee_span = callee.span.find_ancestor_inside(expr.span).unwrap_or(callee.span);
148+
let (sugg, help) = match (src.kind(), dst.kind()) {
149+
// dont check the length; transmute does that for us.
150+
// [u8; _] => primitive
151+
(ty::Array(t, _), ty::Uint(_) | ty::Float(_) | ty::Int(_))
152+
if *t.kind() == ty::Uint(ty::UintTy::U8) =>
153+
{
154+
(
155+
Some(vec![(callee_span, format!("{dst}::from_ne_bytes"))]),
156+
Some(
157+
"there's also `from_le_bytes` and `from_be_bytes` if you expect a particular byte order",
158+
),
159+
)
160+
}
161+
// primitive => [u8; _]
162+
(ty::Uint(_) | ty::Float(_) | ty::Int(_), ty::Array(t, _))
163+
if *t.kind() == ty::Uint(ty::UintTy::U8) =>
164+
{
165+
(
166+
Some(vec![(callee_span, format!("{src}::to_ne_bytes"))]),
167+
Some(
168+
"there's also `to_le_bytes` and `to_be_bytes` if you expect a particular byte order",
169+
),
170+
)
171+
}
172+
// char → u32
173+
(ty::Char, ty::Uint(ty::UintTy::U32)) => {
174+
(Some(vec![(callee_span, "u32::from".to_string())]), None)
175+
}
176+
// char (→ u32) → i32
177+
(ty::Char, ty::Int(ty::IntTy::I32)) => (
178+
Some(vec![
179+
(callee_span, "u32::from".to_string()),
180+
(expr.span.shrink_to_hi(), ".cast_signed()".to_string()),
181+
]),
182+
None,
183+
),
184+
// u32 → char
185+
(ty::Uint(ty::UintTy::U32), ty::Char) => (
186+
Some(vec![(callee_span, "char::from_u32_unchecked".to_string())]),
187+
Some("consider using `char::from_u32(…).unwrap()`"),
188+
),
189+
// i32 → char
190+
(ty::Int(ty::IntTy::I32), ty::Char) => (
191+
Some(vec![
192+
(callee_span, "char::from_u32_unchecked(i32::cast_unsigned".to_string()),
193+
(expr.span.shrink_to_hi(), ")".to_string()),
194+
]),
195+
Some("consider using `char::from_u32(i32::cast_unsigned(…)).unwrap()`"),
196+
),
197+
// uNN → iNN
198+
(ty::Uint(_), ty::Int(_)) => {
199+
(Some(vec![(callee_span, format!("{src}::cast_signed"))]), None)
200+
}
201+
// iNN → uNN
202+
(ty::Int(_), ty::Uint(_)) => {
203+
(Some(vec![(callee_span, format!("{src}::cast_unsigned"))]), None)
204+
}
205+
// fNN → usize, isize
206+
(ty::Float(_), ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize)) => (
207+
Some(vec![
208+
(callee_span, format!("{src}::to_bits")),
209+
(expr.span.shrink_to_hi(), format!(" as {dst}")),
210+
]),
211+
None,
212+
),
213+
// fNN (→ uNN) → iNN
214+
(ty::Float(_), ty::Int(..)) => (
215+
Some(vec![
216+
(callee_span, format!("{src}::to_bits")),
217+
(expr.span.shrink_to_hi(), ".cast_signed()".to_string()),
218+
]),
219+
None,
220+
),
221+
// fNN → uNN
222+
(ty::Float(_), ty::Uint(..)) => {
223+
(Some(vec![(callee_span, format!("{src}::to_bits"))]), None)
224+
}
225+
// xsize → fNN
226+
(ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize), ty::Float(_)) => (
227+
Some(vec![
228+
(callee_span, format!("{dst}::from_bits")),
229+
(arg.span.shrink_to_hi(), " as _".to_string()),
230+
]),
231+
None,
232+
),
233+
// iNN (→ uNN) → fNN
234+
(ty::Int(_), ty::Float(_)) => (
235+
Some(vec![
236+
(callee_span, format!("{dst}::from_bits({src}::cast_unsigned")),
237+
(expr.span.shrink_to_hi(), ")".to_string()),
238+
]),
239+
None,
240+
),
241+
// uNN → fNN
242+
(ty::Uint(_), ty::Float(_)) => {
243+
(Some(vec![(callee_span, format!("{dst}::from_bits"))]), None)
244+
}
245+
// bool → x8 in const context since `From::from` is not const yet
246+
// FIXME: Consider arg expr's precedence to avoid parentheses.
247+
// FIXME(const_traits): Remove this when `From::from` is constified.
248+
(ty::Bool, ty::Int(..) | ty::Uint(..)) if const_context.is_some() => (
249+
Some(vec![
250+
(callee_span, "".to_string()),
251+
(expr.span.shrink_to_hi(), format!(" as {dst}")),
252+
]),
253+
None,
254+
),
255+
// bool → x8 using `x8::from`
256+
(ty::Bool, ty::Int(..) | ty::Uint(..)) => {
257+
(Some(vec![(callee_span, format!("{dst}::from"))]), None)
258+
}
259+
_ => return,
260+
};
261+
262+
cx.tcx.node_span_lint(UNNECESSARY_TRANSMUTES, expr.hir_id, expr.span, |diag| {
263+
diag.primary_message("unnecessary transmute");
264+
if let Some(sugg) = sugg {
265+
diag.multipart_suggestion("replace this with", sugg, Applicability::MachineApplicable);
266+
}
267+
if let Some(help) = help {
268+
diag.help(help);
269+
}
270+
});
271+
}
272+
97273
#[derive(LintDiagnostic)]
98274
#[diag(lint_undefined_transmute)]
99275
#[note]

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ declare_lint_pass! {
117117
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
118118
UNNAMEABLE_TEST_ITEMS,
119119
UNNAMEABLE_TYPES,
120-
UNNECESSARY_TRANSMUTES,
121120
UNREACHABLE_CODE,
122121
UNREACHABLE_PATTERNS,
123122
UNSAFE_ATTR_OUTSIDE_UNSAFE,
@@ -4850,30 +4849,6 @@ declare_lint! {
48504849
@feature_gate = supertrait_item_shadowing;
48514850
}
48524851

4853-
declare_lint! {
4854-
/// The `unnecessary_transmutes` lint detects transmutations that have safer alternatives.
4855-
///
4856-
/// ### Example
4857-
///
4858-
/// ```rust
4859-
/// fn bytes_at_home(x: [u8; 4]) -> u32 {
4860-
/// unsafe { std::mem::transmute(x) }
4861-
/// }
4862-
/// ```
4863-
///
4864-
/// {{produces}}
4865-
///
4866-
/// ### Explanation
4867-
///
4868-
/// Using an explicit method is preferable over calls to
4869-
/// [`transmute`](https://doc.rust-lang.org/std/mem/fn.transmute.html) as
4870-
/// they more clearly communicate the intent, are easier to review, and
4871-
/// are less likely to accidentally result in unsoundness.
4872-
pub UNNECESSARY_TRANSMUTES,
4873-
Warn,
4874-
"detects transmutes that are shadowed by std methods"
4875-
}
4876-
48774852
declare_lint! {
48784853
/// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location,
48794854
/// that runs a custom `Drop` destructor.

compiler/rustc_mir_transform/messages.ftl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,3 @@ mir_transform_unconditional_recursion = function cannot return without recursing
7979
mir_transform_unconditional_recursion_call_site_label = recursive call site
8080
8181
mir_transform_unknown_pass_name = MIR pass `{$name}` is unknown and will be ignored
82-
mir_transform_unnecessary_transmute = unnecessary transmute

0 commit comments

Comments
 (0)