Skip to content

Commit 2655036

Browse files
authored
Rollup merge of #144531 - Urgau:int_to_ptr_transmutes, r=jackh726
Add lint against integer to pointer transmutes # `integer_to_ptr_transmutes` *warn-by-default* The `integer_to_ptr_transmutes` lint detects integer to pointer transmutes where the resulting pointers are undefined behavior to dereference. ### Example ```rust fn foo(a: usize) -> *const u8 { unsafe { std::mem::transmute::<usize, *const u8>(a) } } ``` ``` warning: transmuting an integer to a pointer creates a pointer without provenance --> a.rs:1:9 | 158 | std::mem::transmute::<usize, *const u8>(a) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this is dangerous because dereferencing the resulting pointer is undefined behavior = note: exposed provenance semantics can be used to create a pointer based on some previously exposed provenance = help: if you truly mean to create a pointer without provenance, use `std::ptr::without_provenance_mut` = help: for more information about transmute, see <https://doc.rust-lang.org/std/mem/fn.transmute.html#transmutation-between-pointers-and-integers> = help: for more information about exposed provenance, see <https://doc.rust-lang.org/std/ptr/index.html#exposed-provenance> = note: `#[warn(integer_to_ptr_transmutes)]` on by default help: use `std::ptr::with_exposed_provenance` instead to use a previously exposed provenance | 158 - std::mem::transmute::<usize, *const u8>(a) 158 + std::ptr::with_exposed_provenance::<u8>(a) | ``` ### Explanation Any attempt to use the resulting pointers are undefined behavior as the resulting pointers won't have any provenance. Alternatively, `std::ptr::with_exposed_provenance` should be used, as they do not carry the provenance requirement or if the wanting to create pointers without provenance `std::ptr::without_provenance_mut` should be used. See [std::mem::transmute] in the reference for more details. [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html -------- People are getting tripped up on this, see #128409 and #141220. There are >90 cases like these on [GitHub search](https://github.com/search?q=lang%3Arust+%2Ftransmute%3A%3A%3Cu%5B0-9%5D*.*%2C+%5C*const%2F&type=code). Fixes rust-lang/rust-clippy#13140 Fixes #141220 Fixes #145523 `@rustbot` labels +I-lang-nominated +T-lang cc `@traviscross` r? compiler
2 parents f6d2341 + 1da4959 commit 2655036

20 files changed

+490
-68
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,14 @@ lint_invalid_reference_casting_note_book = for more information, visit <https://
462462
463463
lint_invalid_reference_casting_note_ty_has_interior_mutability = even for types with interior mutability, the only legal way to obtain a mutable pointer from a shared reference is through `UnsafeCell::get`
464464
465+
lint_int_to_ptr_transmutes = transmuting an integer to a pointer creates a pointer without provenance
466+
.note = this is dangerous because dereferencing the resulting pointer is undefined behavior
467+
.note_exposed_provenance = exposed provenance semantics can be used to create a pointer based on some previously exposed provenance
468+
.help_transmute = for more information about transmute, see <https://doc.rust-lang.org/std/mem/fn.transmute.html#transmutation-between-pointers-and-integers>
469+
.help_exposed_provenance = for more information about exposed provenance, see <https://doc.rust-lang.org/std/ptr/index.html#exposed-provenance>
470+
.suggestion_with_exposed_provenance = use `std::ptr::with_exposed_provenance{$suffix}` instead to use a previously exposed provenance
471+
.suggestion_without_provenance_mut = if you truly mean to create a pointer without provenance, use `std::ptr::without_provenance_mut`
472+
465473
lint_legacy_derive_helpers = derive helper attribute is used before it is introduced
466474
.label = the attribute is introduced here
467475

compiler/rustc_lint/src/lints.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// ignore-tidy-filelength
2+
13
#![allow(rustc::untranslatable_diagnostic)]
24
use std::num::NonZero;
35

@@ -1542,6 +1544,48 @@ impl<'a> LintDiagnostic<'a, ()> for DropGlue<'_> {
15421544
}
15431545
}
15441546

1547+
// transmute.rs
1548+
#[derive(LintDiagnostic)]
1549+
#[diag(lint_int_to_ptr_transmutes)]
1550+
#[note]
1551+
#[note(lint_note_exposed_provenance)]
1552+
#[help(lint_suggestion_without_provenance_mut)]
1553+
#[help(lint_help_transmute)]
1554+
#[help(lint_help_exposed_provenance)]
1555+
pub(crate) struct IntegerToPtrTransmutes<'tcx> {
1556+
#[subdiagnostic]
1557+
pub suggestion: IntegerToPtrTransmutesSuggestion<'tcx>,
1558+
}
1559+
1560+
#[derive(Subdiagnostic)]
1561+
pub(crate) enum IntegerToPtrTransmutesSuggestion<'tcx> {
1562+
#[multipart_suggestion(
1563+
lint_suggestion_with_exposed_provenance,
1564+
applicability = "machine-applicable",
1565+
style = "verbose"
1566+
)]
1567+
ToPtr {
1568+
dst: Ty<'tcx>,
1569+
suffix: &'static str,
1570+
#[suggestion_part(code = "std::ptr::with_exposed_provenance{suffix}::<{dst}>(")]
1571+
start_call: Span,
1572+
},
1573+
#[multipart_suggestion(
1574+
lint_suggestion_with_exposed_provenance,
1575+
applicability = "machine-applicable",
1576+
style = "verbose"
1577+
)]
1578+
ToRef {
1579+
dst: Ty<'tcx>,
1580+
suffix: &'static str,
1581+
ref_mutbl: &'static str,
1582+
#[suggestion_part(
1583+
code = "&{ref_mutbl}*std::ptr::with_exposed_provenance{suffix}::<{dst}>("
1584+
)]
1585+
start_call: Span,
1586+
},
1587+
}
1588+
15451589
// types.rs
15461590
#[derive(LintDiagnostic)]
15471591
#[diag(lint_range_endpoint_out_of_range)]

compiler/rustc_lint/src/transmute.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use rustc_ast::LitKind;
12
use rustc_errors::Applicability;
23
use rustc_hir::def::{DefKind, Res};
34
use rustc_hir::def_id::LocalDefId;
@@ -7,6 +8,7 @@ use rustc_middle::ty::{self, Ty};
78
use rustc_session::{declare_lint, impl_lint_pass};
89
use rustc_span::sym;
910

11+
use crate::lints::{IntegerToPtrTransmutes, IntegerToPtrTransmutesSuggestion};
1012
use crate::{LateContext, LateLintPass};
1113

1214
declare_lint! {
@@ -67,9 +69,44 @@ declare_lint! {
6769
"detects transmutes that can also be achieved by other operations"
6870
}
6971

72+
declare_lint! {
73+
/// The `integer_to_ptr_transmutes` lint detects integer to pointer
74+
/// transmutes where the resulting pointers are undefined behavior to dereference.
75+
///
76+
/// ### Example
77+
///
78+
/// ```rust
79+
/// fn foo(a: usize) -> *const u8 {
80+
/// unsafe {
81+
/// std::mem::transmute::<usize, *const u8>(a)
82+
/// }
83+
/// }
84+
/// ```
85+
///
86+
/// {{produces}}
87+
///
88+
/// ### Explanation
89+
///
90+
/// Any attempt to use the resulting pointers are undefined behavior as the resulting
91+
/// pointers won't have any provenance.
92+
///
93+
/// Alternatively, [`std::ptr::with_exposed_provenance`] should be used, as they do not
94+
/// carry the provenance requirement. If wanting to create pointers without provenance
95+
/// [`std::ptr::without_provenance`] should be used instead.
96+
///
97+
/// See [`std::mem::transmute`] in the reference for more details.
98+
///
99+
/// [`std::mem::transmute`]: https://doc.rust-lang.org/std/mem/fn.transmute.html
100+
/// [`std::ptr::with_exposed_provenance`]: https://doc.rust-lang.org/std/ptr/fn.with_exposed_provenance.html
101+
/// [`std::ptr::without_provenance`]: https://doc.rust-lang.org/std/ptr/fn.without_provenance.html
102+
pub INTEGER_TO_PTR_TRANSMUTES,
103+
Warn,
104+
"detects integer to pointer transmutes",
105+
}
106+
70107
pub(crate) struct CheckTransmutes;
71108

72-
impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]);
109+
impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES, INTEGER_TO_PTR_TRANSMUTES]);
73110

74111
impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
75112
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
@@ -94,9 +131,62 @@ impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
94131

95132
check_ptr_transmute_in_const(cx, expr, body_owner_def_id, const_context, src, dst);
96133
check_unnecessary_transmute(cx, expr, callee, arg, const_context, src, dst);
134+
check_int_to_ptr_transmute(cx, expr, arg, src, dst);
97135
}
98136
}
99137

138+
/// Check for transmutes from integer to pointers (*const/*mut and &/&mut).
139+
///
140+
/// Using the resulting pointers would be undefined behavior.
141+
fn check_int_to_ptr_transmute<'tcx>(
142+
cx: &LateContext<'tcx>,
143+
expr: &'tcx hir::Expr<'tcx>,
144+
arg: &'tcx hir::Expr<'tcx>,
145+
src: Ty<'tcx>,
146+
dst: Ty<'tcx>,
147+
) {
148+
if !matches!(src.kind(), ty::Uint(_) | ty::Int(_)) {
149+
return;
150+
}
151+
let (ty::Ref(_, inner_ty, mutbl) | ty::RawPtr(inner_ty, mutbl)) = dst.kind() else {
152+
return;
153+
};
154+
// bail-out if the argument is literal 0 as we have other lints for those cases
155+
if matches!(arg.kind, hir::ExprKind::Lit(hir::Lit { node: LitKind::Int(v, _), .. }) if v == 0) {
156+
return;
157+
}
158+
// bail-out if the inner type is a ZST
159+
let Ok(layout_inner_ty) = cx.tcx.layout_of(cx.typing_env().as_query_input(*inner_ty)) else {
160+
return;
161+
};
162+
if layout_inner_ty.is_1zst() {
163+
return;
164+
}
165+
166+
let suffix = if mutbl.is_mut() { "_mut" } else { "" };
167+
cx.tcx.emit_node_span_lint(
168+
INTEGER_TO_PTR_TRANSMUTES,
169+
expr.hir_id,
170+
expr.span,
171+
IntegerToPtrTransmutes {
172+
suggestion: if dst.is_ref() {
173+
IntegerToPtrTransmutesSuggestion::ToRef {
174+
dst: *inner_ty,
175+
suffix,
176+
ref_mutbl: mutbl.prefix_str(),
177+
start_call: expr.span.shrink_to_lo().until(arg.span),
178+
}
179+
} else {
180+
IntegerToPtrTransmutesSuggestion::ToPtr {
181+
dst: *inner_ty,
182+
suffix,
183+
start_call: expr.span.shrink_to_lo().until(arg.span),
184+
}
185+
},
186+
},
187+
);
188+
}
189+
100190
/// Check for transmutes that exhibit undefined behavior.
101191
/// For example, transmuting pointers to integers in a const context.
102192
///

library/core/src/ptr/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,7 @@ pub const fn dangling<T>() -> *const T {
914914
#[must_use]
915915
#[stable(feature = "strict_provenance", since = "1.84.0")]
916916
#[rustc_const_stable(feature = "strict_provenance", since = "1.84.0")]
917+
#[allow(integer_to_ptr_transmutes)] // Expected semantics here.
917918
pub const fn without_provenance_mut<T>(addr: usize) -> *mut T {
918919
// An int-to-pointer transmute currently has exactly the intended semantics: it creates a
919920
// pointer without provenance. Note that this is *not* a stable guarantee about transmute

src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,7 @@ pub(super) fn check<'tcx>(
4949
true
5050
},
5151
(ty::Int(_) | ty::Uint(_), ty::RawPtr(_, _)) => {
52-
span_lint_and_then(
53-
cx,
54-
USELESS_TRANSMUTE,
55-
e.span,
56-
"transmute from an integer to a pointer",
57-
|diag| {
58-
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
59-
diag.span_suggestion(e.span, "try", arg.as_ty(to_ty.to_string()), Applicability::Unspecified);
60-
}
61-
},
62-
);
52+
// Handled by the upstream rustc `integer_to_ptr_transmutes` lint
6353
true
6454
},
6555
_ => false,

src/tools/clippy/tests/ui/transmute.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
dead_code,
55
clippy::borrow_as_ptr,
66
unnecessary_transmutes,
7+
integer_to_ptr_transmutes,
78
clippy::needless_lifetimes,
89
clippy::missing_transmute_annotations
910
)]
@@ -60,12 +61,10 @@ fn useless() {
6061
//~^ useless_transmute
6162

6263
let _: *const usize = std::mem::transmute(5_isize);
63-
//~^ useless_transmute
6464

6565
let _ = std::ptr::dangling::<usize>();
6666

6767
let _: *const usize = std::mem::transmute(1 + 1usize);
68-
//~^ useless_transmute
6968

7069
let _ = (1 + 1_usize) as *const usize;
7170
}
Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: transmute from a reference to a pointer
2-
--> tests/ui/transmute.rs:33:27
2+
--> tests/ui/transmute.rs:34:27
33
|
44
LL | let _: *const T = core::mem::transmute(t);
55
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T`
@@ -8,61 +8,49 @@ LL | let _: *const T = core::mem::transmute(t);
88
= help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]`
99

1010
error: transmute from a reference to a pointer
11-
--> tests/ui/transmute.rs:36:25
11+
--> tests/ui/transmute.rs:37:25
1212
|
1313
LL | let _: *mut T = core::mem::transmute(t);
1414
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *mut T`
1515

1616
error: transmute from a reference to a pointer
17-
--> tests/ui/transmute.rs:39:27
17+
--> tests/ui/transmute.rs:40:27
1818
|
1919
LL | let _: *const U = core::mem::transmute(t);
2020
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *const U`
2121

2222
error: transmute from a type (`std::vec::Vec<i32>`) to itself
23-
--> tests/ui/transmute.rs:47:27
23+
--> tests/ui/transmute.rs:48:27
2424
|
2525
LL | let _: Vec<i32> = core::mem::transmute(my_vec());
2626
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2727

2828
error: transmute from a type (`std::vec::Vec<i32>`) to itself
29-
--> tests/ui/transmute.rs:50:27
29+
--> tests/ui/transmute.rs:51:27
3030
|
3131
LL | let _: Vec<i32> = core::mem::transmute(my_vec());
3232
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3333

3434
error: transmute from a type (`std::vec::Vec<i32>`) to itself
35-
--> tests/ui/transmute.rs:53:27
35+
--> tests/ui/transmute.rs:54:27
3636
|
3737
LL | let _: Vec<i32> = std::mem::transmute(my_vec());
3838
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3939

4040
error: transmute from a type (`std::vec::Vec<i32>`) to itself
41-
--> tests/ui/transmute.rs:56:27
41+
--> tests/ui/transmute.rs:57:27
4242
|
4343
LL | let _: Vec<i32> = std::mem::transmute(my_vec());
4444
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4545

4646
error: transmute from a type (`std::vec::Vec<i32>`) to itself
47-
--> tests/ui/transmute.rs:59:27
47+
--> tests/ui/transmute.rs:60:27
4848
|
4949
LL | let _: Vec<i32> = my_transmute(my_vec());
5050
| ^^^^^^^^^^^^^^^^^^^^^^
5151

52-
error: transmute from an integer to a pointer
53-
--> tests/ui/transmute.rs:62:31
54-
|
55-
LL | let _: *const usize = std::mem::transmute(5_isize);
56-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `5_isize as *const usize`
57-
58-
error: transmute from an integer to a pointer
59-
--> tests/ui/transmute.rs:67:31
60-
|
61-
LL | let _: *const usize = std::mem::transmute(1 + 1usize);
62-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(1 + 1usize) as *const usize`
63-
6452
error: transmute from a type (`*const Usize`) to the type that it points to (`Usize`)
65-
--> tests/ui/transmute.rs:99:24
53+
--> tests/ui/transmute.rs:98:24
6654
|
6755
LL | let _: Usize = core::mem::transmute(int_const_ptr);
6856
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -71,25 +59,25 @@ LL | let _: Usize = core::mem::transmute(int_const_ptr);
7159
= help: to override `-D warnings` add `#[allow(clippy::crosspointer_transmute)]`
7260

7361
error: transmute from a type (`*mut Usize`) to the type that it points to (`Usize`)
74-
--> tests/ui/transmute.rs:102:24
62+
--> tests/ui/transmute.rs:101:24
7563
|
7664
LL | let _: Usize = core::mem::transmute(int_mut_ptr);
7765
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7866

7967
error: transmute from a type (`Usize`) to a pointer to that type (`*const Usize`)
80-
--> tests/ui/transmute.rs:105:31
68+
--> tests/ui/transmute.rs:104:31
8169
|
8270
LL | let _: *const Usize = core::mem::transmute(my_int());
8371
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8472

8573
error: transmute from a type (`Usize`) to a pointer to that type (`*mut Usize`)
86-
--> tests/ui/transmute.rs:108:29
74+
--> tests/ui/transmute.rs:107:29
8775
|
8876
LL | let _: *mut Usize = core::mem::transmute(my_int());
8977
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9078

9179
error: transmute from a `u8` to a `bool`
92-
--> tests/ui/transmute.rs:115:28
80+
--> tests/ui/transmute.rs:114:28
9381
|
9482
LL | let _: bool = unsafe { std::mem::transmute(0_u8) };
9583
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `0_u8 != 0`
@@ -98,7 +86,7 @@ LL | let _: bool = unsafe { std::mem::transmute(0_u8) };
9886
= help: to override `-D warnings` add `#[allow(clippy::transmute_int_to_bool)]`
9987

10088
error: transmute from a `&[u8]` to a `&str`
101-
--> tests/ui/transmute.rs:122:28
89+
--> tests/ui/transmute.rs:121:28
10290
|
10391
LL | let _: &str = unsafe { std::mem::transmute(B) };
10492
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8(B).unwrap()`
@@ -107,16 +95,16 @@ LL | let _: &str = unsafe { std::mem::transmute(B) };
10795
= help: to override `-D warnings` add `#[allow(clippy::transmute_bytes_to_str)]`
10896

10997
error: transmute from a `&mut [u8]` to a `&mut str`
110-
--> tests/ui/transmute.rs:125:32
98+
--> tests/ui/transmute.rs:124:32
11199
|
112100
LL | let _: &mut str = unsafe { std::mem::transmute(mb) };
113101
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_mut(mb).unwrap()`
114102

115103
error: transmute from a `&[u8]` to a `&str`
116-
--> tests/ui/transmute.rs:128:30
104+
--> tests/ui/transmute.rs:127:30
117105
|
118106
LL | const _: &str = unsafe { std::mem::transmute(B) };
119107
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_unchecked(B)`
120108

121-
error: aborting due to 18 previous errors
109+
error: aborting due to 16 previous errors
122110

src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ fn main() {
1313
// We should see an error message for each transmute, and no error messages for
1414
// the casts, since the casts are the recommended fixes.
1515

16-
// e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast
17-
let _ptr_i32_transmute = unsafe { usize::MAX as *const i32 };
18-
//~^ useless_transmute
1916
let ptr_i32 = usize::MAX as *const i32;
2017

2118
// e has type *T, U is *U_0, and either U_0: Sized ...

src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ fn main() {
1313
// We should see an error message for each transmute, and no error messages for
1414
// the casts, since the casts are the recommended fixes.
1515

16-
// e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast
17-
let _ptr_i32_transmute = unsafe { transmute::<usize, *const i32>(usize::MAX) };
18-
//~^ useless_transmute
1916
let ptr_i32 = usize::MAX as *const i32;
2017

2118
// e has type *T, U is *U_0, and either U_0: Sized ...

0 commit comments

Comments
 (0)