Add a Lint for Pointer to Integer Transmutes in Consts

This commit is contained in:
Veera 2024-09-22 19:32:56 -04:00
parent 5d9b908571
commit ab8673501c
10 changed files with 192 additions and 18 deletions

View file

@ -0,0 +1,77 @@
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_unsafe_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

@ -121,3 +121,10 @@ pub(crate) struct MustNotSuspendReason {
pub span: Span,
pub reason: String,
}
#[derive(LintDiagnostic)]
#[diag(mir_transform_undefined_transmute)]
#[note]
#[note(mir_transform_note2)]
#[help]
pub(crate) struct UndefinedTransmute;

View file

@ -51,6 +51,7 @@ mod add_subtyping_projections;
mod check_alignment;
mod check_const_item_mutation;
mod check_packed_ref;
mod check_undefined_transmutes;
// This pass is public to allow external drivers to perform MIR cleanup
pub mod cleanup_post_borrowck;
mod copy_prop;
@ -293,6 +294,7 @@ 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),
// What we need to do constant evaluation.
&simplify::SimplifyCfg::Initial,
&Lint(sanity_check::SanityCheck),