Make PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS into a HIR lint

This commit is contained in:
Michael Goulet
2025-05-25 15:05:22 +00:00
parent a8ae2af967
commit 5370c5753f
10 changed files with 141 additions and 170 deletions

View File

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

View File

@@ -75,6 +75,7 @@ mod reference_casting;
mod shadowed_into_iter;
mod static_mut_refs;
mod traits;
mod transmute;
mod types;
mod unit_bindings;
mod unqualified_local_imports;
@@ -118,6 +119,7 @@ use shadowed_into_iter::ShadowedIntoIter;
pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER};
use static_mut_refs::*;
use traits::*;
use transmute::CheckTransmutes;
use types::*;
use unit_bindings::*;
use unqualified_local_imports::*;
@@ -245,6 +247,7 @@ late_lint_methods!(
IfLetRescope: IfLetRescope::default(),
StaticMutRefs: StaticMutRefs,
UnqualifiedLocalImports: UnqualifiedLocalImports,
CheckTransmutes: CheckTransmutes,
]
]
);

View File

@@ -0,0 +1,102 @@
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{self as hir};
use rustc_macros::LintDiagnostic;
use rustc_session::{declare_lint, impl_lint_pass};
use rustc_span::sym;
use crate::{LateContext, LateLintPass};
declare_lint! {
/// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer
/// transmute in const functions and associated constants.
///
/// ### Example
///
/// ```rust
/// const fn foo(ptr: *const u8) -> usize {
/// unsafe {
/// std::mem::transmute::<*const u8, usize>(ptr)
/// }
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Transmuting pointers to integers in a `const` context is undefined behavior.
/// Any attempt to use the resulting integer will abort const-evaluation.
///
/// But sometimes the compiler might not emit an error for pointer to integer transmutes
/// inside const functions and associated consts because they are evaluated only when referenced.
/// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior
/// from compiling without any warnings or errors.
///
/// See [std::mem::transmute] in the reference for more details.
///
/// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html
pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
Warn,
"detects pointer to integer transmutes in const functions and associated constants",
}
pub(crate) struct CheckTransmutes;
impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS]);
impl<'tcx> LateLintPass<'tcx> for CheckTransmutes {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
let hir::ExprKind::Call(callee, _) = expr.kind else {
return;
};
let hir::ExprKind::Path(qpath) = callee.kind else {
return;
};
let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id) else {
return;
};
if !cx.tcx.is_intrinsic(def_id, sym::transmute) {
return;
};
let body_owner_def_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id);
let Some(context) = cx.tcx.hir_body_const_context(body_owner_def_id) else {
return;
};
let args = cx.typeck_results().node_args(callee.hir_id);
let src = args.type_at(0);
let dst = args.type_at(1);
// Check for transmutes that exhibit undefined behavior.
// For example, transmuting pointers to integers in a const context.
//
// Why do we consider const functions and associated constants only?
//
// Generally, undefined behavior in const items are handled by the evaluator.
// But, const functions and associated constants are evaluated only when referenced.
// This can result in undefined behavior in a library going unnoticed until
// the function or constant is actually used.
//
// Therefore, we only consider const functions and associated constants here and leave
// other const items to be handled by the evaluator.
if matches!(context, hir::ConstContext::ConstFn)
|| matches!(cx.tcx.def_kind(body_owner_def_id), DefKind::AssocConst)
{
if src.is_raw_ptr() && dst.is_integral() {
cx.tcx.emit_node_span_lint(
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
expr.hir_id,
expr.span,
UndefinedTransmuteLint,
);
}
}
}
}
#[derive(LintDiagnostic)]
#[diag(lint_undefined_transmute)]
#[note]
#[note(lint_note2)]
#[help]
pub(crate) struct UndefinedTransmuteLint;

View File

@@ -79,7 +79,6 @@ declare_lint_pass! {
PRIVATE_BOUNDS,
PRIVATE_INTERFACES,
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
PUB_USE_OF_PRIVATE_EXTERN_CRATE,
REDUNDANT_IMPORTS,
REDUNDANT_LIFETIMES,
@@ -4851,40 +4850,6 @@ declare_lint! {
@feature_gate = supertrait_item_shadowing;
}
declare_lint! {
/// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer
/// transmute in const functions and associated constants.
///
/// ### Example
///
/// ```rust
/// const fn foo(ptr: *const u8) -> usize {
/// unsafe {
/// std::mem::transmute::<*const u8, usize>(ptr)
/// }
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Transmuting pointers to integers in a `const` context is undefined behavior.
/// Any attempt to use the resulting integer will abort const-evaluation.
///
/// But sometimes the compiler might not emit an error for pointer to integer transmutes
/// inside const functions and associated consts because they are evaluated only when referenced.
/// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior
/// from compiling without any warnings or errors.
///
/// See [std::mem::transmute] in the reference for more details.
///
/// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html
pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
Warn,
"detects pointer to integer transmutes in const functions and associated constants",
}
declare_lint! {
/// The `unnecessary_transmutes` lint detects transmutations that have safer alternatives.
///

View File

@@ -78,10 +78,5 @@ mir_transform_unconditional_recursion = function cannot return without recursing
mir_transform_unconditional_recursion_call_site_label = recursive call site
mir_transform_undefined_transmute = pointers cannot be transmuted to integers during const eval
.note = at compile-time, pointers do not have an integer value
.note2 = avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
.help = for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
mir_transform_unknown_pass_name = MIR pass `{$name}` is unknown and will be ignored
mir_transform_unnecessary_transmute = unnecessary transmute

View File

@@ -1,77 +0,0 @@
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind};
use rustc_middle::ty::{AssocItem, AssocKind, TyCtxt};
use rustc_session::lint::builtin::PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS;
use rustc_span::sym;
use crate::errors;
/// Check for transmutes that exhibit undefined behavior.
/// For example, transmuting pointers to integers in a const context.
pub(super) struct CheckUndefinedTransmutes;
impl<'tcx> crate::MirLint<'tcx> for CheckUndefinedTransmutes {
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
let mut checker = UndefinedTransmutesChecker { body, tcx };
checker.visit_body(body);
}
}
struct UndefinedTransmutesChecker<'a, 'tcx> {
body: &'a Body<'tcx>,
tcx: TyCtxt<'tcx>,
}
impl<'a, 'tcx> UndefinedTransmutesChecker<'a, 'tcx> {
// This functions checks two things:
// 1. `function` takes a raw pointer as input and returns an integer as output.
// 2. `function` is called from a const function or an associated constant.
//
// Why do we consider const functions and associated constants only?
//
// Generally, undefined behavior in const items are handled by the evaluator.
// But, const functions and associated constants are evaluated only when referenced.
// This can result in undefined behavior in a library going unnoticed until
// the function or constant is actually used.
//
// Therefore, we only consider const functions and associated constants here and leave
// other const items to be handled by the evaluator.
fn is_ptr_to_int_in_const(&self, function: &Operand<'tcx>) -> bool {
let def_id = self.body.source.def_id();
if self.tcx.is_const_fn(def_id)
|| matches!(
self.tcx.opt_associated_item(def_id),
Some(AssocItem { kind: AssocKind::Const { .. }, .. })
)
{
let fn_sig = function.ty(self.body, self.tcx).fn_sig(self.tcx).skip_binder();
if let [input] = fn_sig.inputs() {
return input.is_raw_ptr() && fn_sig.output().is_integral();
}
}
false
}
}
impl<'tcx> Visitor<'tcx> for UndefinedTransmutesChecker<'_, 'tcx> {
// Check each block's terminator for calls to pointer to integer transmutes
// in const functions or associated constants and emit a lint.
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
if let TerminatorKind::Call { func, .. } = &terminator.kind
&& let Some((func_def_id, _)) = func.const_fn_def()
&& self.tcx.is_intrinsic(func_def_id, sym::transmute)
&& self.is_ptr_to_int_in_const(func)
&& let Some(call_id) = self.body.source.def_id().as_local()
{
let hir_id = self.tcx.local_def_id_to_hir_id(call_id);
let span = self.body.source_info(location).span;
self.tcx.emit_node_span_lint(
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
hir_id,
span,
errors::UndefinedTransmute,
);
}
}
}

View File

@@ -178,13 +178,6 @@ impl<'a> LintDiagnostic<'a, ()> for UnnecessaryTransmute {
}
}
#[derive(LintDiagnostic)]
#[diag(mir_transform_undefined_transmute)]
#[note]
#[note(mir_transform_note2)]
#[help]
pub(crate) struct UndefinedTransmute;
#[derive(Diagnostic)]
#[diag(mir_transform_force_inline)]
#[note]

View File

@@ -123,7 +123,6 @@ declare_passes! {
mod check_const_item_mutation : CheckConstItemMutation;
mod check_null : CheckNull;
mod check_packed_ref : CheckPackedRef;
mod check_undefined_transmutes : CheckUndefinedTransmutes;
mod check_unnecessary_transmutes: CheckUnnecessaryTransmutes;
// This pass is public to allow external drivers to perform MIR cleanup
pub mod cleanup_post_borrowck : CleanupPostBorrowck;
@@ -390,7 +389,6 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
&Lint(check_packed_ref::CheckPackedRef),
&Lint(check_const_item_mutation::CheckConstItemMutation),
&Lint(function_item_references::FunctionItemReferences),
&Lint(check_undefined_transmutes::CheckUndefinedTransmutes),
&Lint(check_unnecessary_transmutes::CheckUnnecessaryTransmutes),
// What we need to do constant evaluation.
&simplify::SimplifyCfg::Initial,

View File

@@ -1,7 +1,9 @@
#![deny(ptr_to_integer_transmute_in_consts)]
const fn foo(ptr: *const u8) -> usize {
unsafe {
std::mem::transmute(ptr)
//~^ WARN pointers cannot be transmuted to integers
//~^ ERROR pointers cannot be transmuted to integers
}
}
@@ -11,7 +13,7 @@ trait Human {
let ptr: *const usize = &value;
unsafe {
std::mem::transmute(ptr)
//~^ WARN pointers cannot be transmuted to integers
//~^ ERROR pointers cannot be transmuted to integers
}
};
@@ -28,7 +30,7 @@ impl<T> Type<T> {
let ptr: *const usize = &value;
unsafe {
std::mem::transmute(ptr)
//~^ WARN pointers cannot be transmuted to integers
//~^ ERROR pointers cannot be transmuted to integers
}
};
@@ -38,9 +40,7 @@ impl<T> Type<T> {
}
fn control(ptr: *const u8) -> usize {
unsafe {
std::mem::transmute(ptr)
}
unsafe { std::mem::transmute(ptr) }
}
struct ControlStruct;
@@ -49,22 +49,15 @@ impl ControlStruct {
fn new() -> usize {
let value = 10;
let ptr: *const i32 = &value;
unsafe {
std::mem::transmute(ptr)
}
unsafe { std::mem::transmute(ptr) }
}
}
const fn zoom(ptr: *const u8) -> usize {
unsafe {
std::mem::transmute(ptr)
//~^ WARN pointers cannot be transmuted to integers
//~^ ERROR pointers cannot be transmuted to integers
}
}
fn main() {
const a: u8 = 10;
const value: usize = zoom(&a);
//~^ ERROR evaluation of constant value failed
}
fn main() {}

View File

@@ -1,5 +1,5 @@
warning: pointers cannot be transmuted to integers during const eval
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:61:9
error: pointers cannot be transmuted to integers during const eval
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:5:9
|
LL | std::mem::transmute(ptr)
| ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -7,19 +7,14 @@ LL | std::mem::transmute(ptr)
= note: at compile-time, pointers do not have an integer value
= note: avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
= help: for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
= note: `#[warn(ptr_to_integer_transmute_in_consts)]` on by default
error[E0080]: evaluation of constant value failed
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:68:26
note: the lint level is defined here
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:1:9
|
LL | const value: usize = zoom(&a);
| ^^^^^^^^ unable to turn pointer into integer
|
= help: this code performed an operation that depends on the underlying bytes representing a pointer
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
LL | #![deny(ptr_to_integer_transmute_in_consts)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: pointers cannot be transmuted to integers during const eval
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:3:9
error: pointers cannot be transmuted to integers during const eval
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:15:13
|
LL | std::mem::transmute(ptr)
| ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -28,8 +23,8 @@ LL | std::mem::transmute(ptr)
= note: avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
= help: for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
warning: pointers cannot be transmuted to integers during const eval
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:13:13
error: pointers cannot be transmuted to integers during const eval
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:32:13
|
LL | std::mem::transmute(ptr)
| ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -38,8 +33,8 @@ LL | std::mem::transmute(ptr)
= note: avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
= help: for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
warning: pointers cannot be transmuted to integers during const eval
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:30:13
error: pointers cannot be transmuted to integers during const eval
--> $DIR/ptr-to-int-transmute-in-consts-issue-87525.rs:58:9
|
LL | std::mem::transmute(ptr)
| ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -48,6 +43,5 @@ LL | std::mem::transmute(ptr)
= note: avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior
= help: for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html
error: aborting due to 1 previous error; 4 warnings emitted
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0080`.