Skip to content

Commit b294c0b

Browse files
authored
Unrolled build for #141551
Rollup merge of #141551 - compiler-errors:hir-lints, r=BoxyUwU Make two transmute-related MIR lints into HIR lint Make `PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS` (#130540) and `UNNECESSARY_TRANSMUTES` (#136083) into "normal" HIR-based lints. Funny enough this came up in the review of the latter (#136083 (comment)), but I guess it just was overlooked. But anywyas, there's no reason for these to be MIR lints; in fact, it makes the suggestions for them a bit more complicated than necessary. Note that there's probably a few more simplifications and improvements to be done here. Follow-ups can be done in a separate PR, especially if they're about the messaging and suggestions themselves, which I didn't write.
2 parents be42293 + 295a8d5 commit b294c0b

14 files changed

+488
-407
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,11 @@ lint_type_ir_inherent_usage = do not use `rustc_type_ir::inherent` unless you're
807807
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
808808
.note = the method or struct you're looking for is likely defined somewhere else downstream in the compiler
809809
810+
lint_undefined_transmute = pointers cannot be transmuted to integers during const eval
811+
.note = at compile-time, pointers do not have an integer value
812+
.note2 = avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
813+
.help = for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
814+
810815
lint_undropped_manually_drops = calls to `std::mem::drop` with `std::mem::ManuallyDrop` instead of the inner value does nothing
811816
.label = argument has type `{$arg_ty}`
812817
.suggestion = use `std::mem::ManuallyDrop::into_inner` to get the inner value

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ mod reference_casting;
7575
mod shadowed_into_iter;
7676
mod static_mut_refs;
7777
mod traits;
78+
mod transmute;
7879
mod types;
7980
mod unit_bindings;
8081
mod unqualified_local_imports;
@@ -118,6 +119,7 @@ use shadowed_into_iter::ShadowedIntoIter;
118119
pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER};
119120
use static_mut_refs::*;
120121
use traits::*;
122+
use transmute::CheckTransmutes;
121123
use types::*;
122124
use unit_bindings::*;
123125
use unqualified_local_imports::*;
@@ -245,6 +247,7 @@ late_lint_methods!(
245247
IfLetRescope: IfLetRescope::default(),
246248
StaticMutRefs: StaticMutRefs,
247249
UnqualifiedLocalImports: UnqualifiedLocalImports,
250+
CheckTransmutes: CheckTransmutes,
248251
]
249252
]
250253
);

compiler/rustc_lint/src/transmute.rs

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
use rustc_errors::Applicability;
2+
use rustc_hir::def::{DefKind, Res};
3+
use rustc_hir::def_id::LocalDefId;
4+
use rustc_hir::{self as hir};
5+
use rustc_macros::LintDiagnostic;
6+
use rustc_middle::ty::{self, Ty};
7+
use rustc_session::{declare_lint, impl_lint_pass};
8+
use rustc_span::sym;
9+
10+
use crate::{LateContext, LateLintPass};
11+
12+
declare_lint! {
13+
/// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer
14+
/// transmute in const functions and associated constants.
15+
///
16+
/// ### Example
17+
///
18+
/// ```rust
19+
/// const fn foo(ptr: *const u8) -> usize {
20+
/// unsafe {
21+
/// std::mem::transmute::<*const u8, usize>(ptr)
22+
/// }
23+
/// }
24+
/// ```
25+
///
26+
/// {{produces}}
27+
///
28+
/// ### Explanation
29+
///
30+
/// Transmuting pointers to integers in a `const` context is undefined behavior.
31+
/// Any attempt to use the resulting integer will abort const-evaluation.
32+
///
33+
/// But sometimes the compiler might not emit an error for pointer to integer transmutes
34+
/// inside const functions and associated consts because they are evaluated only when referenced.
35+
/// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior
36+
/// from compiling without any warnings or errors.
37+
///
38+
/// See [std::mem::transmute] in the reference for more details.
39+
///
40+
/// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html
41+
pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
42+
Warn,
43+
"detects pointer to integer transmutes in const functions and associated constants",
44+
}
45+
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+
70+
pub(crate) struct CheckTransmutes;
71+
72+
impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]);
73+
74+
impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
75+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
76+
let hir::ExprKind::Call(callee, [arg]) = expr.kind else {
77+
return;
78+
};
79+
let hir::ExprKind::Path(qpath) = callee.kind else {
80+
return;
81+
};
82+
let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id) else {
83+
return;
84+
};
85+
if !cx.tcx.is_intrinsic(def_id, sym::transmute) {
86+
return;
87+
};
88+
let body_owner_def_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id);
89+
let const_context = cx.tcx.hir_body_const_context(body_owner_def_id);
90+
let args = cx.typeck_results().node_args(callee.hir_id);
91+
92+
let src = args.type_at(0);
93+
let dst = args.type_at(1);
94+
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+
);
130+
}
131+
}
132+
}
133+
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+
273+
#[derive(LintDiagnostic)]
274+
#[diag(lint_undefined_transmute)]
275+
#[note]
276+
#[note(lint_note2)]
277+
#[help]
278+
pub(crate) struct UndefinedTransmuteLint;

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ declare_lint_pass! {
7979
PRIVATE_BOUNDS,
8080
PRIVATE_INTERFACES,
8181
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
82-
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
8382
PUB_USE_OF_PRIVATE_EXTERN_CRATE,
8483
REDUNDANT_IMPORTS,
8584
REDUNDANT_LIFETIMES,
@@ -118,7 +117,6 @@ declare_lint_pass! {
118117
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
119118
UNNAMEABLE_TEST_ITEMS,
120119
UNNAMEABLE_TYPES,
121-
UNNECESSARY_TRANSMUTES,
122120
UNREACHABLE_CODE,
123121
UNREACHABLE_PATTERNS,
124122
UNSAFE_ATTR_OUTSIDE_UNSAFE,
@@ -4851,64 +4849,6 @@ declare_lint! {
48514849
@feature_gate = supertrait_item_shadowing;
48524850
}
48534851

4854-
declare_lint! {
4855-
/// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer
4856-
/// transmute in const functions and associated constants.
4857-
///
4858-
/// ### Example
4859-
///
4860-
/// ```rust
4861-
/// const fn foo(ptr: *const u8) -> usize {
4862-
/// unsafe {
4863-
/// std::mem::transmute::<*const u8, usize>(ptr)
4864-
/// }
4865-
/// }
4866-
/// ```
4867-
///
4868-
/// {{produces}}
4869-
///
4870-
/// ### Explanation
4871-
///
4872-
/// Transmuting pointers to integers in a `const` context is undefined behavior.
4873-
/// Any attempt to use the resulting integer will abort const-evaluation.
4874-
///
4875-
/// But sometimes the compiler might not emit an error for pointer to integer transmutes
4876-
/// inside const functions and associated consts because they are evaluated only when referenced.
4877-
/// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior
4878-
/// from compiling without any warnings or errors.
4879-
///
4880-
/// See [std::mem::transmute] in the reference for more details.
4881-
///
4882-
/// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html
4883-
pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
4884-
Warn,
4885-
"detects pointer to integer transmutes in const functions and associated constants",
4886-
}
4887-
4888-
declare_lint! {
4889-
/// The `unnecessary_transmutes` lint detects transmutations that have safer alternatives.
4890-
///
4891-
/// ### Example
4892-
///
4893-
/// ```rust
4894-
/// fn bytes_at_home(x: [u8; 4]) -> u32 {
4895-
/// unsafe { std::mem::transmute(x) }
4896-
/// }
4897-
/// ```
4898-
///
4899-
/// {{produces}}
4900-
///
4901-
/// ### Explanation
4902-
///
4903-
/// Using an explicit method is preferable over calls to
4904-
/// [`transmute`](https://doc.rust-lang.org/std/mem/fn.transmute.html) as
4905-
/// they more clearly communicate the intent, are easier to review, and
4906-
/// are less likely to accidentally result in unsoundness.
4907-
pub UNNECESSARY_TRANSMUTES,
4908-
Warn,
4909-
"detects transmutes that are shadowed by std methods"
4910-
}
4911-
49124852
declare_lint! {
49134853
/// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location,
49144854
/// that runs a custom `Drop` destructor.

0 commit comments

Comments
 (0)