Skip to content

Commit c1cb9c5

Browse files
committed
Add lint against integer to pointer transmutes
1 parent 3f9f20f commit c1cb9c5

File tree

6 files changed

+263
-1
lines changed

6 files changed

+263
-1
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,14 @@ lint_invalid_reference_casting_note_book = for more information, visit <https://
476476
477477
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`
478478
479+
lint_int_to_ptr_transmutes = transmuting a integer to a pointer without provenance is dangerous
480+
.note = dereferencing the resulting pointer is Undefined Behavior as it doesn't have any provenance
481+
.note_exposed_provenance = exposed provenance semantics can be used to create a pointer based on some previously exposed provenance
482+
.help_transmute = for more information about transmute, see <https://doc.rust-lang.org/std/mem/fn.transmute.html#transmutation-between-pointers-and-integers>
483+
.help_exposed_provenance = for more information about exposed provenance, see <https://doc.rust-lang.org/std/ptr/index.html#exposed-provenance>
484+
.suggestion_as = use `as` cast instead to use a previously exposed provenance
485+
.suggestion_without_provenance_mut = use `std::ptr::without_provenance_mut` to create a pointer without provenance
486+
479487
lint_legacy_derive_helpers = derive helper attribute is used before it is introduced
480488
.label = the attribute is introduced here
481489

compiler/rustc_lint/src/lints.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,6 +1603,45 @@ impl<'a> LintDiagnostic<'a, ()> for DropGlue<'_> {
16031603
}
16041604
}
16051605

1606+
// transmute.rs
1607+
#[derive(LintDiagnostic)]
1608+
#[diag(lint_int_to_ptr_transmutes)]
1609+
#[note]
1610+
#[note(lint_note_exposed_provenance)]
1611+
#[help(lint_help_transmute)]
1612+
#[help(lint_help_exposed_provenance)]
1613+
#[help(lint_suggestion_without_provenance_mut)]
1614+
pub(crate) struct IntegerToPtrTransmutes<'tcx> {
1615+
#[subdiagnostic]
1616+
pub suggestion: IntegerToPtrTransmutesSuggestion<'tcx>,
1617+
}
1618+
1619+
#[derive(Subdiagnostic)]
1620+
pub(crate) enum IntegerToPtrTransmutesSuggestion<'tcx> {
1621+
#[multipart_suggestion(lint_suggestion_as, applicability = "machine-applicable")]
1622+
ToPtr {
1623+
dst: Ty<'tcx>,
1624+
paren_left: &'static str,
1625+
paren_right: &'static str,
1626+
#[suggestion_part(code = "{paren_left}")]
1627+
start_call: Span,
1628+
#[suggestion_part(code = "{paren_right} as {dst}")]
1629+
end_call: Span,
1630+
},
1631+
#[multipart_suggestion(lint_suggestion_as, applicability = "machine-applicable")]
1632+
ToRef {
1633+
dst: Ty<'tcx>,
1634+
ptr_mutbl: &'static str,
1635+
ref_mutbl: &'static str,
1636+
paren_left: &'static str,
1637+
paren_right: &'static str,
1638+
#[suggestion_part(code = "&{ref_mutbl}*({paren_left}")]
1639+
start_call: Span,
1640+
#[suggestion_part(code = "{paren_right} as *{ptr_mutbl} {dst})")]
1641+
end_call: Span,
1642+
},
1643+
}
1644+
16061645
// types.rs
16071646
#[derive(LintDiagnostic)]
16081647
#[diag(lint_range_endpoint_out_of_range)]

compiler/rustc_lint/src/transmute.rs

Lines changed: 93 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,42 @@ 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, `as` casts should be used, as they do not carry the provenance
94+
/// requirement or if the wanting to create pointers without provenance
95+
/// `ptr::without_provenance_mut` should be used.
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+
pub INTEGER_TO_PTR_TRANSMUTES,
101+
Warn,
102+
"detects integer to pointer transmutes",
103+
}
104+
70105
pub(crate) struct CheckTransmutes;
71106

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

74109
impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
75110
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
@@ -94,6 +129,63 @@ impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
94129

95130
check_ptr_transmute_in_const(cx, expr, body_owner_def_id, const_context, src, dst);
96131
check_unnecessary_transmute(cx, expr, callee, arg, const_context, src, dst);
132+
check_int_to_ptr_transmute(cx, expr, arg, src, dst);
133+
}
134+
}
135+
136+
/// Check for transmutes from integer to pointers (*const/*mut, &/&mut and fn()).
137+
///
138+
/// Using the resulting pointers would be undefined behavior.
139+
fn check_int_to_ptr_transmute<'tcx>(
140+
cx: &LateContext<'tcx>,
141+
expr: &'tcx hir::Expr<'tcx>,
142+
arg: &'tcx hir::Expr<'tcx>,
143+
src: Ty<'tcx>,
144+
dst: Ty<'tcx>,
145+
) {
146+
if matches!(src.kind(), ty::Uint(_) | ty::Int(_))
147+
&& let ty::Ref(_, inner_ty, mutbl) | ty::RawPtr(inner_ty, mutbl) = dst.kind()
148+
// bail-out if the argument is literal 0 as we have other lints for those cases
149+
&& !matches!(arg.kind, hir::ExprKind::Lit(hir::Lit { node: LitKind::Int(v, _), .. }) if v == 0)
150+
// bail-out if the inner type if a ZST
151+
&& cx.tcx
152+
.layout_of(cx.typing_env().as_query_input(*inner_ty))
153+
.is_ok_and(|layout| !layout.is_1zst())
154+
{
155+
// does the argument needs parenthesis
156+
let mut paren_left = "";
157+
let mut paren_right = "";
158+
if matches!(arg.kind, hir::ExprKind::Binary(..)) {
159+
paren_left = "(";
160+
paren_right = ")";
161+
}
162+
163+
cx.tcx.emit_node_span_lint(
164+
INTEGER_TO_PTR_TRANSMUTES,
165+
expr.hir_id,
166+
expr.span,
167+
IntegerToPtrTransmutes {
168+
suggestion: if dst.is_ref() {
169+
IntegerToPtrTransmutesSuggestion::ToRef {
170+
dst: *inner_ty,
171+
ref_mutbl: mutbl.prefix_str(),
172+
ptr_mutbl: mutbl.ptr_str(),
173+
paren_left,
174+
paren_right,
175+
start_call: expr.span.shrink_to_lo().until(arg.span),
176+
end_call: arg.span.shrink_to_hi().until(expr.span.shrink_to_hi()),
177+
}
178+
} else {
179+
IntegerToPtrTransmutesSuggestion::ToPtr {
180+
dst,
181+
paren_left,
182+
paren_right,
183+
start_call: expr.span.shrink_to_lo().until(arg.span),
184+
end_call: arg.span.shrink_to_hi().until(expr.span.shrink_to_hi()),
185+
}
186+
},
187+
},
188+
);
97189
}
98190
}
99191

tests/ui/lint/int_to_ptr.fixed

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Checks for the `integer_to_pointer_transmutes` lint
2+
3+
//@ check-pass
4+
//@ run-rustfix
5+
6+
#![allow(unused_unsafe)]
7+
#![allow(dead_code)]
8+
9+
unsafe fn should_lint(a: usize) {
10+
let _ptr = unsafe { a as *const u8 };
11+
//~^ WARN transmuting a integer to a pointer
12+
let _ptr = unsafe { &*(a as *const u8) };
13+
//~^ WARN transmuting a integer to a pointer
14+
let _ptr = unsafe { 42usize as *const u8 };
15+
//~^ WARN transmuting a integer to a pointer
16+
let _ptr = unsafe { (a + a) as *const u8 };
17+
//~^ WARN transmuting a integer to a pointer
18+
}
19+
20+
unsafe fn should_not_lint(a: usize) {
21+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(0usize) }; // linted by other lints
22+
let _ptr = unsafe { std::mem::transmute::<usize, *const ()>(a) }; // inner type is a ZST
23+
let _ptr = unsafe { std::mem::transmute::<usize, fn()>(a) }; // omit fn-ptr for now
24+
}
25+
26+
fn main() {}

tests/ui/lint/int_to_ptr.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Checks for the `integer_to_pointer_transmutes` lint
2+
3+
//@ check-pass
4+
//@ run-rustfix
5+
6+
#![allow(unused_unsafe)]
7+
#![allow(dead_code)]
8+
9+
unsafe fn should_lint(a: usize) {
10+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(a) };
11+
//~^ WARN transmuting a integer to a pointer
12+
let _ptr = unsafe { std::mem::transmute::<usize, &'static u8>(a) };
13+
//~^ WARN transmuting a integer to a pointer
14+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(42usize) };
15+
//~^ WARN transmuting a integer to a pointer
16+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(a + a) };
17+
//~^ WARN transmuting a integer to a pointer
18+
}
19+
20+
unsafe fn should_not_lint(a: usize) {
21+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(0usize) }; // linted by other lints
22+
let _ptr = unsafe { std::mem::transmute::<usize, *const ()>(a) }; // inner type is a ZST
23+
let _ptr = unsafe { std::mem::transmute::<usize, fn()>(a) }; // omit fn-ptr for now
24+
}
25+
26+
fn main() {}

tests/ui/lint/int_to_ptr.stderr

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
warning: transmuting a integer to a pointer without provenance is dangerous
2+
--> $DIR/int_to_ptr.rs:10:25
3+
|
4+
LL | let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(a) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: dereferencing the resulting pointer is Undefined Behavior as it doesn't have any provenance
8+
= note: exposed provenance semantics can be used to create a pointer based on some previously exposed provenance
9+
= help: for more information about transmute, see <https://doc.rust-lang.org/std/mem/fn.transmute.html#transmutation-between-pointers-and-integers>
10+
= help: for more information about exposed provenance, see <https://doc.rust-lang.org/std/ptr/index.html#exposed-provenance>
11+
= help: use `std::ptr::without_provenance_mut` to create a pointer without provenance
12+
= note: `#[warn(integer_to_ptr_transmutes)]` on by default
13+
help: use `as` cast instead to use a previously exposed provenance
14+
|
15+
LL - let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(a) };
16+
LL + let _ptr = unsafe { a as *const u8 };
17+
|
18+
19+
warning: transmuting a integer to a pointer without provenance is dangerous
20+
--> $DIR/int_to_ptr.rs:12:25
21+
|
22+
LL | let _ptr = unsafe { std::mem::transmute::<usize, &'static u8>(a) };
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24+
|
25+
= note: dereferencing the resulting pointer is Undefined Behavior as it doesn't have any provenance
26+
= note: exposed provenance semantics can be used to create a pointer based on some previously exposed provenance
27+
= help: for more information about transmute, see <https://doc.rust-lang.org/std/mem/fn.transmute.html#transmutation-between-pointers-and-integers>
28+
= help: for more information about exposed provenance, see <https://doc.rust-lang.org/std/ptr/index.html#exposed-provenance>
29+
= help: use `std::ptr::without_provenance_mut` to create a pointer without provenance
30+
help: use `as` cast instead to use a previously exposed provenance
31+
|
32+
LL - let _ptr = unsafe { std::mem::transmute::<usize, &'static u8>(a) };
33+
LL + let _ptr = unsafe { &*(a as *const u8) };
34+
|
35+
36+
warning: transmuting a integer to a pointer without provenance is dangerous
37+
--> $DIR/int_to_ptr.rs:14:25
38+
|
39+
LL | let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(42usize) };
40+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41+
|
42+
= note: dereferencing the resulting pointer is Undefined Behavior as it doesn't have any provenance
43+
= note: exposed provenance semantics can be used to create a pointer based on some previously exposed provenance
44+
= help: for more information about transmute, see <https://doc.rust-lang.org/std/mem/fn.transmute.html#transmutation-between-pointers-and-integers>
45+
= help: for more information about exposed provenance, see <https://doc.rust-lang.org/std/ptr/index.html#exposed-provenance>
46+
= help: use `std::ptr::without_provenance_mut` to create a pointer without provenance
47+
help: use `as` cast instead to use a previously exposed provenance
48+
|
49+
LL - let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(42usize) };
50+
LL + let _ptr = unsafe { 42usize as *const u8 };
51+
|
52+
53+
warning: transmuting a integer to a pointer without provenance is dangerous
54+
--> $DIR/int_to_ptr.rs:16:25
55+
|
56+
LL | let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(a + a) };
57+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58+
|
59+
= note: dereferencing the resulting pointer is Undefined Behavior as it doesn't have any provenance
60+
= note: exposed provenance semantics can be used to create a pointer based on some previously exposed provenance
61+
= help: for more information about transmute, see <https://doc.rust-lang.org/std/mem/fn.transmute.html#transmutation-between-pointers-and-integers>
62+
= help: for more information about exposed provenance, see <https://doc.rust-lang.org/std/ptr/index.html#exposed-provenance>
63+
= help: use `std::ptr::without_provenance_mut` to create a pointer without provenance
64+
help: use `as` cast instead to use a previously exposed provenance
65+
|
66+
LL - let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(a + a) };
67+
LL + let _ptr = unsafe { (a + a) as *const u8 };
68+
|
69+
70+
warning: 4 warnings emitted
71+

0 commit comments

Comments
 (0)