Skip to content

Commit d4cbd9a

Browse files
committed
Add lint against integer to pointer transmutes
1 parent c5a6a7b commit d4cbd9a

File tree

6 files changed

+406
-1
lines changed

6 files changed

+406
-1
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: 42 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,46 @@ 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+
)]
1566+
ToPtr {
1567+
dst: Ty<'tcx>,
1568+
suffix: &'static str,
1569+
#[suggestion_part(code = "std::ptr::with_exposed_provenance{suffix}::<{dst}>(")]
1570+
start_call: Span,
1571+
},
1572+
#[multipart_suggestion(
1573+
lint_suggestion_with_exposed_provenance,
1574+
applicability = "machine-applicable"
1575+
)]
1576+
ToRef {
1577+
dst: Ty<'tcx>,
1578+
suffix: &'static str,
1579+
ref_mutbl: &'static str,
1580+
#[suggestion_part(
1581+
code = "&{ref_mutbl}*std::ptr::with_exposed_provenance{suffix}::<{dst}>("
1582+
)]
1583+
start_call: Span,
1584+
},
1585+
}
1586+
15451587
// types.rs
15461588
#[derive(LintDiagnostic)]
15471589
#[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
///

tests/ui/lint/int_to_ptr.fixed

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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: *const u8 = unsafe { std::ptr::with_exposed_provenance::<u8>(a) };
11+
//~^ WARN transmuting an integer to a pointer
12+
let _ptr: *mut u8 = unsafe { std::ptr::with_exposed_provenance_mut::<u8>(a) };
13+
//~^ WARN transmuting an integer to a pointer
14+
let _ref: &'static u8 = unsafe { &*std::ptr::with_exposed_provenance::<u8>(a) };
15+
//~^ WARN transmuting an integer to a pointer
16+
let _ref: &'static mut u8 = unsafe { &mut *std::ptr::with_exposed_provenance_mut::<u8>(a) };
17+
//~^ WARN transmuting an integer to a pointer
18+
19+
let _ptr = unsafe { std::ptr::with_exposed_provenance::<u8>(42usize) };
20+
//~^ WARN transmuting an integer to a pointer
21+
let _ptr = unsafe { std::ptr::with_exposed_provenance::<u8>(a + a) };
22+
//~^ WARN transmuting an integer to a pointer
23+
}
24+
25+
const unsafe fn should_lintin_const(a: usize) {
26+
let _ptr: *const u8 = unsafe { std::ptr::with_exposed_provenance::<u8>(a) };
27+
//~^ WARN transmuting an integer to a pointer
28+
let _ptr: *mut u8 = unsafe { std::ptr::with_exposed_provenance_mut::<u8>(a) };
29+
//~^ WARN transmuting an integer to a pointer
30+
let _ref: &'static u8 = unsafe { &*std::ptr::with_exposed_provenance::<u8>(a) };
31+
//~^ WARN transmuting an integer to a pointer
32+
let _ref: &'static mut u8 = unsafe { &mut *std::ptr::with_exposed_provenance_mut::<u8>(a) };
33+
//~^ WARN transmuting an integer to a pointer
34+
35+
let _ptr = unsafe { std::ptr::with_exposed_provenance::<u8>(42usize) };
36+
//~^ WARN transmuting an integer to a pointer
37+
let _ptr = unsafe { std::ptr::with_exposed_provenance::<u8>(a + a) };
38+
//~^ WARN transmuting an integer to a pointer
39+
}
40+
41+
unsafe fn should_not_lint(a: usize) {
42+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(0usize) }; // linted by other lints
43+
let _ptr = unsafe { std::mem::transmute::<usize, *const ()>(a) }; // inner type is a ZST
44+
let _ptr = unsafe { std::mem::transmute::<usize, fn()>(a) }; // omit fn-ptr for now
45+
}
46+
47+
fn main() {}

tests/ui/lint/int_to_ptr.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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: *const u8 = unsafe { std::mem::transmute::<usize, *const u8>(a) };
11+
//~^ WARN transmuting an integer to a pointer
12+
let _ptr: *mut u8 = unsafe { std::mem::transmute::<usize, *mut u8>(a) };
13+
//~^ WARN transmuting an integer to a pointer
14+
let _ref: &'static u8 = unsafe { std::mem::transmute::<usize, &'static u8>(a) };
15+
//~^ WARN transmuting an integer to a pointer
16+
let _ref: &'static mut u8 = unsafe { std::mem::transmute::<usize, &'static mut u8>(a) };
17+
//~^ WARN transmuting an integer to a pointer
18+
19+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(42usize) };
20+
//~^ WARN transmuting an integer to a pointer
21+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(a + a) };
22+
//~^ WARN transmuting an integer to a pointer
23+
}
24+
25+
const unsafe fn should_lintin_const(a: usize) {
26+
let _ptr: *const u8 = unsafe { std::mem::transmute::<usize, *const u8>(a) };
27+
//~^ WARN transmuting an integer to a pointer
28+
let _ptr: *mut u8 = unsafe { std::mem::transmute::<usize, *mut u8>(a) };
29+
//~^ WARN transmuting an integer to a pointer
30+
let _ref: &'static u8 = unsafe { std::mem::transmute::<usize, &'static u8>(a) };
31+
//~^ WARN transmuting an integer to a pointer
32+
let _ref: &'static mut u8 = unsafe { std::mem::transmute::<usize, &'static mut u8>(a) };
33+
//~^ WARN transmuting an integer to a pointer
34+
35+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(42usize) };
36+
//~^ WARN transmuting an integer to a pointer
37+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(a + a) };
38+
//~^ WARN transmuting an integer to a pointer
39+
}
40+
41+
unsafe fn should_not_lint(a: usize) {
42+
let _ptr = unsafe { std::mem::transmute::<usize, *const u8>(0usize) }; // linted by other lints
43+
let _ptr = unsafe { std::mem::transmute::<usize, *const ()>(a) }; // inner type is a ZST
44+
let _ptr = unsafe { std::mem::transmute::<usize, fn()>(a) }; // omit fn-ptr for now
45+
}
46+
47+
fn main() {}

0 commit comments

Comments
 (0)