1use rustc_hir::lang_items::LangItem;
2use rustc_index::IndexVec;
3use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
4use rustc_middle::mir::*;
5use rustc_middle::ty::{self, Ty, TyCtxt};
6use tracing::{debug, trace};
78/// Details of a pointer check, the condition on which we decide whether to
9/// fail the assert and an [AssertKind] that defines the behavior on failure.
10pub(crate) struct PointerCheck<'tcx> {
11pub(crate) cond: Operand<'tcx>,
12pub(crate) assert_kind: Box<AssertKind<Operand<'tcx>>>,
13}
1415/// When checking for borrows of field projections (`&(*ptr).a`), we might want
16/// to check for the field type (type of `.a` in the example). This enum defines
17/// the variations (pass the pointer [Ty] or the field [Ty]).
18#[derive(#[automatically_derived]
impl ::core::marker::Copy for BorrowedFieldProjectionMode { }Copy, #[automatically_derived]
impl ::core::clone::Clone for BorrowedFieldProjectionMode {
#[inline]
fn clone(&self) -> BorrowedFieldProjectionMode { *self }
}Clone)]
19pub(crate) enum BorrowedFieldProjectionMode {
20 FollowProjections,
21 NoFollowProjections,
22}
2324/// Utility for adding a check for read/write on every sized, raw pointer.
25///
26/// Visits every read/write access to a [Sized], raw pointer and inserts a
27/// new basic block directly before the pointer access. (Read/write accesses
28/// are determined by the `PlaceContext` of the MIR visitor.) Then calls
29/// `on_finding` to insert the actual logic for a pointer check (e.g. check for
30/// alignment). A check can choose to follow borrows of field projections via
31/// the `field_projection_mode` parameter.
32///
33/// This utility takes care of the right order of blocks, the only thing a
34/// caller must do in `on_finding` is:
35/// - Append [Statement]s to `stmts`.
36/// - Append [LocalDecl]s to `local_decls`.
37/// - Return a [PointerCheck] that contains the condition and an [AssertKind].
38/// The AssertKind must be a panic with `#[rustc_nounwind]`. The condition
39/// should always return the boolean `is_ok`, so evaluate to true in case of
40/// success and fail the check otherwise.
41/// This utility will insert a terminator block that asserts on the condition
42/// and panics on failure.
43pub(crate) fn check_pointers<'tcx, F>(
44 tcx: TyCtxt<'tcx>,
45 body: &mut Body<'tcx>,
46 excluded_pointees: &[Ty<'tcx>],
47 on_finding: F,
48 field_projection_mode: BorrowedFieldProjectionMode,
49) where
50F: Fn(
51/* tcx: */ TyCtxt<'tcx>,
52/* pointer: */ Place<'tcx>,
53/* pointee_ty: */ Ty<'tcx>,
54/* context: */ PlaceContext,
55/* local_decls: */ &mut IndexVec<Local, LocalDecl<'tcx>>,
56/* stmts: */ &mut Vec<Statement<'tcx>>,
57/* source_info: */ SourceInfo,
58 ) -> PointerCheck<'tcx>,
59{
60// This pass emits new panics. If for whatever reason we do not have a panic
61 // implementation, running this pass may cause otherwise-valid code to not compile.
62if tcx.lang_items().get(LangItem::PanicImpl).is_none() {
63return;
64 }
6566let typing_env = body.typing_env(tcx);
67let basic_blocks = body.basic_blocks.as_mut();
68let local_decls = &mut body.local_decls;
6970// This operation inserts new blocks. Each insertion changes the Location for all
71 // statements/blocks after. Iterating or visiting the MIR in order would require updating
72 // our current location after every insertion. By iterating backwards, we dodge this issue:
73 // The only Locations that an insertion changes have already been handled.
74for block in basic_blocks.indices().rev() {
75for statement_index in (0..basic_blocks[block].statements.len()).rev() {
76let location = Location { block, statement_index };
77let statement = &basic_blocks[block].statements[statement_index];
78let source_info = statement.source_info;
7980let mut finder = PointerFinder::new(
81 tcx,
82 local_decls,
83 typing_env,
84 excluded_pointees,
85 field_projection_mode,
86 );
87 finder.visit_statement(statement, location);
8889for (local, ty, context) in finder.into_found_pointers() {
90{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/check_pointers.rs:90",
"rustc_mir_transform::check_pointers",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/check_pointers.rs"),
::tracing_core::__macro_support::Option::Some(90u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::check_pointers"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("Inserting check for {0:?}",
ty) as &dyn Value))])
});
} else { ; }
};debug!("Inserting check for {:?}", ty);
91let new_block = split_block(basic_blocks, location);
9293// Invoke `on_finding` which appends to `local_decls` and the
94 // blocks statements. It returns information about the assert
95 // we're performing in the Terminator.
96let block_data = &mut basic_blocks[block];
97let pointer_check = on_finding(
98 tcx,
99 local,
100 ty,
101 context,
102 local_decls,
103&mut block_data.statements,
104 source_info,
105 );
106 block_data.terminator = Some(Terminator {
107 source_info,
108 kind: TerminatorKind::Assert {
109 cond: pointer_check.cond,
110 expected: true,
111 target: new_block,
112 msg: pointer_check.assert_kind,
113// This calls a panic function associated with the pointer check, which
114 // is #[rustc_nounwind]. We never want to insert an unwind into unsafe
115 // code, because unwinding could make a failing UB check turn into much
116 // worse UB when we start unwinding.
117unwind: UnwindAction::Unreachable,
118 },
119 });
120 }
121 }
122 }
123}
124125struct PointerFinder<'a, 'tcx> {
126 tcx: TyCtxt<'tcx>,
127 local_decls: &'a mut LocalDecls<'tcx>,
128 typing_env: ty::TypingEnv<'tcx>,
129 pointers: Vec<(Place<'tcx>, Ty<'tcx>, PlaceContext)>,
130 excluded_pointees: &'a [Ty<'tcx>],
131 field_projection_mode: BorrowedFieldProjectionMode,
132}
133134impl<'a, 'tcx> PointerFinder<'a, 'tcx> {
135fn new(
136 tcx: TyCtxt<'tcx>,
137 local_decls: &'a mut LocalDecls<'tcx>,
138 typing_env: ty::TypingEnv<'tcx>,
139 excluded_pointees: &'a [Ty<'tcx>],
140 field_projection_mode: BorrowedFieldProjectionMode,
141 ) -> Self {
142PointerFinder {
143tcx,
144local_decls,
145typing_env,
146excluded_pointees,
147 pointers: Vec::new(),
148field_projection_mode,
149 }
150 }
151152fn into_found_pointers(self) -> Vec<(Place<'tcx>, Ty<'tcx>, PlaceContext)> {
153self.pointers
154 }
155156/// Whether or not we should visit a [Place] with [PlaceContext].
157 ///
158 /// We generally only visit Reads/Writes to a place and only Borrows if
159 /// requested.
160fn should_visit_place(&self, context: PlaceContext) -> bool {
161match context {
162 PlaceContext::MutatingUse(
163 MutatingUseContext::Store164 | MutatingUseContext::Call165 | MutatingUseContext::Yield166 | MutatingUseContext::Drop167 | MutatingUseContext::Borrow,
168 ) => true,
169 PlaceContext::NonMutatingUse(
170 NonMutatingUseContext::Copy171 | NonMutatingUseContext::Move172 | NonMutatingUseContext::SharedBorrow,
173 ) => true,
174_ => false,
175 }
176 }
177}
178179impl<'a, 'tcx> Visitor<'tcx> for PointerFinder<'a, 'tcx> {
180fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
181if !self.should_visit_place(context) || !place.is_indirect() {
182return;
183 }
184185// Get the place and type we visit.
186let pointer = Place::from(place.local);
187let pointer_ty = pointer.ty(self.local_decls, self.tcx).ty;
188189// We only want to check places based on raw pointers
190let &ty::RawPtr(mut pointee_ty, _) = pointer_ty.kind() else {
191{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/check_pointers.rs:191",
"rustc_mir_transform::check_pointers",
::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/check_pointers.rs"),
::tracing_core::__macro_support::Option::Some(191u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::check_pointers"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("Indirect, but not based on an raw ptr, not checking {0:?}",
place) as &dyn Value))])
});
} else { ; }
};trace!("Indirect, but not based on an raw ptr, not checking {:?}", place);
192return;
193 };
194195// If we see a borrow of a field projection, we want to pass the field type to the
196 // check and not the pointee type.
197if #[allow(non_exhaustive_omitted_patterns)] match self.field_projection_mode {
BorrowedFieldProjectionMode::FollowProjections => true,
_ => false,
}matches!(self.field_projection_mode, BorrowedFieldProjectionMode::FollowProjections)198 && #[allow(non_exhaustive_omitted_patterns)] match context {
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) |
PlaceContext::MutatingUse(MutatingUseContext::Borrow) => true,
_ => false,
}matches!(
199 context,
200 PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow)
201 | PlaceContext::MutatingUse(MutatingUseContext::Borrow)
202 )203 {
204// Naturally, the field type is type of the initial place we look at.
205pointee_ty = place.ty(self.local_decls, self.tcx).ty;
206 }
207208// Ideally we'd support this in the future, but for now we are limited to sized types.
209if !pointee_ty.is_sized(self.tcx, self.typing_env) {
210{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/check_pointers.rs:210",
"rustc_mir_transform::check_pointers",
::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/check_pointers.rs"),
::tracing_core::__macro_support::Option::Some(210u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::check_pointers"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("Raw pointer, but pointee is not known to be sized: {0:?}",
pointer_ty) as &dyn Value))])
});
} else { ; }
};trace!("Raw pointer, but pointee is not known to be sized: {:?}", pointer_ty);
211return;
212 }
213214// We don't need to look for slices, we already rejected unsized types above.
215let element_ty = match pointee_ty.kind() {
216 ty::Array(ty, _) => *ty,
217_ => pointee_ty,
218 };
219// Check if we excluded this pointee type from the check.
220if self.excluded_pointees.contains(&element_ty) {
221{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/check_pointers.rs:221",
"rustc_mir_transform::check_pointers",
::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/check_pointers.rs"),
::tracing_core::__macro_support::Option::Some(221u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::check_pointers"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("Skipping pointer for type: {0:?}",
pointee_ty) as &dyn Value))])
});
} else { ; }
};trace!("Skipping pointer for type: {:?}", pointee_ty);
222return;
223 }
224225self.pointers.push((pointer, pointee_ty, context));
226227self.super_place(place, context, location);
228 }
229}
230231fn split_block(
232 basic_blocks: &mut IndexVec<BasicBlock, BasicBlockData<'_>>,
233 location: Location,
234) -> BasicBlock {
235let block_data = &mut basic_blocks[location.block];
236237// Drain every statement after this one and move the current terminator to a new basic block.
238let new_block = BasicBlockData::new_stmts(
239block_data.statements.split_off(location.statement_index),
240block_data.terminator.take(),
241block_data.is_cleanup,
242 );
243244basic_blocks.push(new_block)
245}