Skip to content

Commit daebce4

Browse files
committed
Auto merge of #130540 - veera-sivarajan:fix-87525, r=estebank
Add a Lint for Pointer to Integer Transmutes in Consts Fixes #87525 This PR adds a MirLint for pointer to integer transmutes in const functions and associated consts. The implementation closely follows this comment: #85769 (comment). More details about the implementation can be found in the comments. Note: This could break some sound code as mentioned by RalfJung in #85769 (comment): > ... technically const-code could transmute/cast an int to a ptr and then transmute it back and that would be correct -- so the lint will deny some sound code. Does not seem terribly likely though. References: 1. https://doc.rust-lang.org/std/mem/fn.transmute.html 2. https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants
2 parents 85e2f55 + ab86735 commit daebce4

File tree

10 files changed

+262
-6
lines changed

10 files changed

+262
-6
lines changed

compiler/rustc_lint_defs/src/builtin.rs

+35
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ declare_lint_pass! {
8181
PRIVATE_INTERFACES,
8282
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
8383
PTR_CAST_ADD_AUTO_TO_OBJECT,
84+
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
8485
PUB_USE_OF_PRIVATE_EXTERN_CRATE,
8586
REDUNDANT_IMPORTS,
8687
REDUNDANT_LIFETIMES,
@@ -4998,3 +4999,37 @@ declare_lint! {
49984999
reference: "issue #124535 <https://github.com/rust-lang/rust/issues/124535>",
49995000
};
50005001
}
5002+
5003+
declare_lint! {
5004+
/// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer
5005+
/// transmute in const functions and associated constants.
5006+
///
5007+
/// ### Example
5008+
///
5009+
/// ```rust
5010+
/// const fn foo(ptr: *const u8) -> usize {
5011+
/// unsafe {
5012+
/// std::mem::transmute::<*const u8, usize>(ptr)
5013+
/// }
5014+
/// }
5015+
/// ```
5016+
///
5017+
/// {{produces}}
5018+
///
5019+
/// ### Explanation
5020+
///
5021+
/// Transmuting pointers to integers in a `const` context is undefined behavior.
5022+
/// Any attempt to use the resulting integer will abort const-evaluation.
5023+
///
5024+
/// But sometimes the compiler might not emit an error for pointer to integer transmutes
5025+
/// inside const functions and associated consts because they are evaluated only when referenced.
5026+
/// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior
5027+
/// from compiling without any warnings or errors.
5028+
///
5029+
/// See [std::mem::transmute] in the reference for more details.
5030+
///
5031+
/// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html
5032+
pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
5033+
Warn,
5034+
"detects pointer to integer transmutes in const functions and associated constants",
5035+
}

compiler/rustc_mir_transform/messages.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,8 @@ mir_transform_unaligned_packed_ref = reference to packed field is unaligned
2727
.note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses
2828
.note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
2929
.help = copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)
30+
31+
mir_transform_undefined_transmute = pointers cannot be transmuted to integers during const eval
32+
.note = at compile-time, pointers do not have an integer value
33+
.note2 = avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
34+
.help = for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use rustc_middle::mir::visit::Visitor;
2+
use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind};
3+
use rustc_middle::ty::{AssocItem, AssocKind, TyCtxt};
4+
use rustc_session::lint::builtin::PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS;
5+
use rustc_span::sym;
6+
7+
use crate::errors;
8+
9+
/// Check for transmutes that exhibit undefined behavior.
10+
/// For example, transmuting pointers to integers in a const context.
11+
pub(super) struct CheckUndefinedTransmutes;
12+
13+
impl<'tcx> crate::MirLint<'tcx> for CheckUndefinedTransmutes {
14+
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
15+
let mut checker = UndefinedTransmutesChecker { body, tcx };
16+
checker.visit_body(body);
17+
}
18+
}
19+
20+
struct UndefinedTransmutesChecker<'a, 'tcx> {
21+
body: &'a Body<'tcx>,
22+
tcx: TyCtxt<'tcx>,
23+
}
24+
25+
impl<'a, 'tcx> UndefinedTransmutesChecker<'a, 'tcx> {
26+
// This functions checks two things:
27+
// 1. `function` takes a raw pointer as input and returns an integer as output.
28+
// 2. `function` is called from a const function or an associated constant.
29+
//
30+
// Why do we consider const functions and associated constants only?
31+
//
32+
// Generally, undefined behavior in const items are handled by the evaluator.
33+
// But, const functions and associated constants are evaluated only when referenced.
34+
// This can result in undefined behavior in a library going unnoticed until
35+
// the function or constant is actually used.
36+
//
37+
// Therefore, we only consider const functions and associated constants here and leave
38+
// other const items to be handled by the evaluator.
39+
fn is_ptr_to_int_in_const(&self, function: &Operand<'tcx>) -> bool {
40+
let def_id = self.body.source.def_id();
41+
42+
if self.tcx.is_const_fn(def_id)
43+
|| matches!(
44+
self.tcx.opt_associated_item(def_id),
45+
Some(AssocItem { kind: AssocKind::Const, .. })
46+
)
47+
{
48+
let fn_sig = function.ty(self.body, self.tcx).fn_sig(self.tcx).skip_binder();
49+
if let [input] = fn_sig.inputs() {
50+
return input.is_unsafe_ptr() && fn_sig.output().is_integral();
51+
}
52+
}
53+
false
54+
}
55+
}
56+
57+
impl<'tcx> Visitor<'tcx> for UndefinedTransmutesChecker<'_, 'tcx> {
58+
// Check each block's terminator for calls to pointer to integer transmutes
59+
// in const functions or associated constants and emit a lint.
60+
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
61+
if let TerminatorKind::Call { func, .. } = &terminator.kind
62+
&& let Some((func_def_id, _)) = func.const_fn_def()
63+
&& self.tcx.is_intrinsic(func_def_id, sym::transmute)
64+
&& self.is_ptr_to_int_in_const(func)
65+
&& let Some(call_id) = self.body.source.def_id().as_local()
66+
{
67+
let hir_id = self.tcx.local_def_id_to_hir_id(call_id);
68+
let span = self.body.source_info(location).span;
69+
self.tcx.emit_node_span_lint(
70+
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
71+
hir_id,
72+
span,
73+
errors::UndefinedTransmute,
74+
);
75+
}
76+
}
77+
}

compiler/rustc_mir_transform/src/errors.rs

+7
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,10 @@ pub(crate) struct MustNotSuspendReason {
121121
pub span: Span,
122122
pub reason: String,
123123
}
124+
125+
#[derive(LintDiagnostic)]
126+
#[diag(mir_transform_undefined_transmute)]
127+
#[note]
128+
#[note(mir_transform_note2)]
129+
#[help]
130+
pub(crate) struct UndefinedTransmute;

compiler/rustc_mir_transform/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mod add_subtyping_projections;
5151
mod check_alignment;
5252
mod check_const_item_mutation;
5353
mod check_packed_ref;
54+
mod check_undefined_transmutes;
5455
// This pass is public to allow external drivers to perform MIR cleanup
5556
pub mod cleanup_post_borrowck;
5657
mod copy_prop;
@@ -298,6 +299,7 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
298299
&Lint(check_packed_ref::CheckPackedRef),
299300
&Lint(check_const_item_mutation::CheckConstItemMutation),
300301
&Lint(function_item_references::FunctionItemReferences),
302+
&Lint(check_undefined_transmutes::CheckUndefinedTransmutes),
301303
// What we need to do constant evaluation.
302304
&simplify::SimplifyCfg::Initial,
303305
&Lint(sanity_check::SanityCheck),

library/core/src/ptr/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1909,6 +1909,7 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
19091909
/// than trying to adapt this to accommodate that change.
19101910
///
19111911
/// Any questions go to @nagisa.
1912+
#[cfg_attr(not(bootstrap), allow(ptr_to_integer_transmute_in_consts))]
19121913
#[lang = "align_offset"]
19131914
pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
19141915
// FIXME(#75598): Direct use of these intrinsics improves codegen significantly at opt-level <=

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,11 @@ fn issue_10449() {
8484
}
8585

8686
// Pointers cannot be cast to integers in const contexts
87+
#[allow(ptr_to_integer_transmute_in_consts, reason = "This is tested in the compiler test suite")]
8788
const fn issue_12402<P>(ptr: *const P) {
88-
unsafe { transmute::<*const i32, usize>(&42i32) };
89-
unsafe { transmute::<fn(*const P), usize>(issue_12402) };
90-
let _ = unsafe { transmute::<_, usize>(ptr) };
89+
// This test exists even though the compiler lints against it
90+
// to test that clippy's transmute lints do not trigger on this.
91+
unsafe { std::mem::transmute::<*const i32, usize>(&42i32) };
92+
unsafe { std::mem::transmute::<fn(*const P), usize>(issue_12402) };
93+
let _ = unsafe { std::mem::transmute::<_, usize>(ptr) };
9194
}

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,11 @@ fn issue_10449() {
8484
}
8585

8686
// Pointers cannot be cast to integers in const contexts
87+
#[allow(ptr_to_integer_transmute_in_consts, reason = "This is tested in the compiler test suite")]
8788
const fn issue_12402<P>(ptr: *const P) {
88-
unsafe { transmute::<*const i32, usize>(&42i32) };
89-
unsafe { transmute::<fn(*const P), usize>(issue_12402) };
90-
let _ = unsafe { transmute::<_, usize>(ptr) };
89+
// This test exists even though the compiler lints against it
90+
// to test that clippy's transmute lints do not trigger on this.
91+
unsafe { std::mem::transmute::<*const i32, usize>(&42i32) };
92+
unsafe { std::mem::transmute::<fn(*const P), usize>(issue_12402) };
93+
let _ = unsafe { std::mem::transmute::<_, usize>(ptr) };
9194
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const fn foo(ptr: *const u8) -> usize {
2+
unsafe {
3+
std::mem::transmute(ptr)
4+
//~^ WARN pointers cannot be transmuted to integers
5+
}
6+
}
7+
8+
trait Human {
9+
const ID: usize = {
10+
let value = 10;
11+
let ptr: *const usize = &value;
12+
unsafe {
13+
std::mem::transmute(ptr)
14+
//~^ WARN pointers cannot be transmuted to integers
15+
}
16+
};
17+
18+
fn id_plus_one() -> usize {
19+
Self::ID + 1
20+
}
21+
}
22+
23+
struct Type<T>(T);
24+
25+
impl<T> Type<T> {
26+
const ID: usize = {
27+
let value = 10;
28+
let ptr: *const usize = &value;
29+
unsafe {
30+
std::mem::transmute(ptr)
31+
//~^ WARN pointers cannot be transmuted to integers
32+
}
33+
};
34+
35+
fn id_plus_one() -> usize {
36+
Self::ID + 1
37+
}
38+
}
39+
40+
fn control(ptr: *const u8) -> usize {
41+
unsafe {
42+
std::mem::transmute(ptr)
43+
}
44+
}
45+
46+
struct ControlStruct;
47+
48+
impl ControlStruct {
49+
fn new() -> usize {
50+
let value = 10;
51+
let ptr: *const i32 = &value;
52+
unsafe {
53+
std::mem::transmute(ptr)
54+
}
55+
}
56+
}
57+
58+
59+
const fn zoom(ptr: *const u8) -> usize {
60+
unsafe {
61+
std::mem::transmute(ptr)
62+
//~^ WARN pointers cannot be transmuted to integers
63+
}
64+
}
65+
66+
fn main() {
67+
const a: u8 = 10;
68+
const value: usize = zoom(&a);
69+
//~^ ERROR evaluation of constant value failed
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
warning: pointers cannot be transmuted to integers during const eval
2+
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:61:9
3+
|
4+
LL | std::mem::transmute(ptr)
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: at compile-time, pointers do not have an integer value
8+
= note: avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
9+
= help: for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
10+
= note: `#[warn(ptr_to_integer_transmute_in_consts)]` on by default
11+
12+
error[E0080]: evaluation of constant value failed
13+
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:68:26
14+
|
15+
LL | const value: usize = zoom(&a);
16+
| ^^^^^^^^ unable to turn pointer into integer
17+
|
18+
= help: this code performed an operation that depends on the underlying bytes representing a pointer
19+
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
20+
21+
warning: pointers cannot be transmuted to integers during const eval
22+
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:3:9
23+
|
24+
LL | std::mem::transmute(ptr)
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^
26+
|
27+
= note: at compile-time, pointers do not have an integer value
28+
= note: avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
29+
= help: for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
30+
31+
warning: pointers cannot be transmuted to integers during const eval
32+
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:13:13
33+
|
34+
LL | std::mem::transmute(ptr)
35+
| ^^^^^^^^^^^^^^^^^^^^^^^^
36+
|
37+
= note: at compile-time, pointers do not have an integer value
38+
= note: avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
39+
= help: for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
40+
41+
warning: pointers cannot be transmuted to integers during const eval
42+
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:30:13
43+
|
44+
LL | std::mem::transmute(ptr)
45+
| ^^^^^^^^^^^^^^^^^^^^^^^^
46+
|
47+
= note: at compile-time, pointers do not have an integer value
48+
= note: avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
49+
= help: for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
50+
51+
error: aborting due to 1 previous error; 4 warnings emitted
52+
53+
For more information about this error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)