Skip to content

Commit 4c83e55

Browse files
committed
Auto merge of #137940 - 1c3t3a:alignment-borrows-check, r=saethlin
Extend the alignment check to borrows The current alignment check does not include checks for creating misaligned references from raw pointers, which is now added in this patch. When inserting the check we need to be careful with references to field projections (e.g. `&(*ptr).a`), in which case the resulting reference must be aligned according to the field type and not the type of the pointer. r? `@saethlin` cc `@RalfJung,` after our discussion in #134424
2 parents 1b8ab72 + 7082fa2 commit 4c83e55

File tree

7 files changed

+88
-32
lines changed

7 files changed

+88
-32
lines changed

compiler/rustc_mir_transform/src/check_alignment.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_middle::mir::*;
66
use rustc_middle::ty::{Ty, TyCtxt};
77
use rustc_session::Session;
88

9-
use crate::check_pointers::{BorrowCheckMode, PointerCheck, check_pointers};
9+
use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers};
1010

1111
pub(super) struct CheckAlignment;
1212

@@ -19,15 +19,15 @@ impl<'tcx> crate::MirPass<'tcx> for CheckAlignment {
1919
// Skip trivially aligned place types.
2020
let excluded_pointees = [tcx.types.bool, tcx.types.i8, tcx.types.u8];
2121

22-
// We have to exclude borrows here: in `&x.field`, the exact
23-
// requirement is that the final reference must be aligned, but
24-
// `check_pointers` would check that `x` is aligned, which would be wrong.
22+
// When checking the alignment of references to field projections (`&(*ptr).a`),
23+
// we need to make sure that the reference is aligned according to the field type
24+
// and not to the pointer type.
2525
check_pointers(
2626
tcx,
2727
body,
2828
&excluded_pointees,
2929
insert_alignment_check,
30-
BorrowCheckMode::ExcludeBorrows,
30+
BorrowedFieldProjectionMode::FollowProjections,
3131
);
3232
}
3333

compiler/rustc_mir_transform/src/check_null.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc_middle::mir::*;
44
use rustc_middle::ty::{Ty, TyCtxt};
55
use rustc_session::Session;
66

7-
use crate::check_pointers::{BorrowCheckMode, PointerCheck, check_pointers};
7+
use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers};
88

99
pub(super) struct CheckNull;
1010

@@ -14,7 +14,13 @@ impl<'tcx> crate::MirPass<'tcx> for CheckNull {
1414
}
1515

1616
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
17-
check_pointers(tcx, body, &[], insert_null_check, BorrowCheckMode::IncludeBorrows);
17+
check_pointers(
18+
tcx,
19+
body,
20+
&[],
21+
insert_null_check,
22+
BorrowedFieldProjectionMode::NoFollowProjections,
23+
);
1824
}
1925

2026
fn is_required(&self) -> bool {

compiler/rustc_mir_transform/src/check_pointers.rs

+35-25
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ pub(crate) struct PointerCheck<'tcx> {
1212
pub(crate) assert_kind: Box<AssertKind<Operand<'tcx>>>,
1313
}
1414

15-
/// Indicates whether we insert the checks for borrow places of a raw pointer.
16-
/// Concretely places with [MutatingUseContext::Borrow] or
17-
/// [NonMutatingUseContext::SharedBorrow].
15+
/// 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]).
1818
#[derive(Copy, Clone)]
19-
pub(crate) enum BorrowCheckMode {
20-
IncludeBorrows,
21-
ExcludeBorrows,
19+
pub(crate) enum BorrowedFieldProjectionMode {
20+
FollowProjections,
21+
NoFollowProjections,
2222
}
2323

2424
/// Utility for adding a check for read/write on every sized, raw pointer.
@@ -27,8 +27,8 @@ pub(crate) enum BorrowCheckMode {
2727
/// new basic block directly before the pointer access. (Read/write accesses
2828
/// are determined by the `PlaceContext` of the MIR visitor.) Then calls
2929
/// `on_finding` to insert the actual logic for a pointer check (e.g. check for
30-
/// alignment). A check can choose to be inserted for (mutable) borrows of
31-
/// raw pointers via the `borrow_check_mode` parameter.
30+
/// alignment). A check can choose to follow borrows of field projections via
31+
/// the `field_projection_mode` parameter.
3232
///
3333
/// This utility takes care of the right order of blocks, the only thing a
3434
/// caller must do in `on_finding` is:
@@ -45,7 +45,7 @@ pub(crate) fn check_pointers<'tcx, F>(
4545
body: &mut Body<'tcx>,
4646
excluded_pointees: &[Ty<'tcx>],
4747
on_finding: F,
48-
borrow_check_mode: BorrowCheckMode,
48+
field_projection_mode: BorrowedFieldProjectionMode,
4949
) where
5050
F: Fn(
5151
/* tcx: */ TyCtxt<'tcx>,
@@ -82,7 +82,7 @@ pub(crate) fn check_pointers<'tcx, F>(
8282
local_decls,
8383
typing_env,
8484
excluded_pointees,
85-
borrow_check_mode,
85+
field_projection_mode,
8686
);
8787
finder.visit_statement(statement, location);
8888

@@ -128,7 +128,7 @@ struct PointerFinder<'a, 'tcx> {
128128
typing_env: ty::TypingEnv<'tcx>,
129129
pointers: Vec<(Place<'tcx>, Ty<'tcx>, PlaceContext)>,
130130
excluded_pointees: &'a [Ty<'tcx>],
131-
borrow_check_mode: BorrowCheckMode,
131+
field_projection_mode: BorrowedFieldProjectionMode,
132132
}
133133

134134
impl<'a, 'tcx> PointerFinder<'a, 'tcx> {
@@ -137,15 +137,15 @@ impl<'a, 'tcx> PointerFinder<'a, 'tcx> {
137137
local_decls: &'a mut LocalDecls<'tcx>,
138138
typing_env: ty::TypingEnv<'tcx>,
139139
excluded_pointees: &'a [Ty<'tcx>],
140-
borrow_check_mode: BorrowCheckMode,
140+
field_projection_mode: BorrowedFieldProjectionMode,
141141
) -> Self {
142142
PointerFinder {
143143
tcx,
144144
local_decls,
145145
typing_env,
146146
excluded_pointees,
147147
pointers: Vec::new(),
148-
borrow_check_mode,
148+
field_projection_mode,
149149
}
150150
}
151151

@@ -163,15 +163,14 @@ impl<'a, 'tcx> PointerFinder<'a, 'tcx> {
163163
MutatingUseContext::Store
164164
| MutatingUseContext::Call
165165
| MutatingUseContext::Yield
166-
| MutatingUseContext::Drop,
166+
| MutatingUseContext::Drop
167+
| MutatingUseContext::Borrow,
167168
) => true,
168169
PlaceContext::NonMutatingUse(
169-
NonMutatingUseContext::Copy | NonMutatingUseContext::Move,
170+
NonMutatingUseContext::Copy
171+
| NonMutatingUseContext::Move
172+
| NonMutatingUseContext::SharedBorrow,
170173
) => true,
171-
PlaceContext::MutatingUse(MutatingUseContext::Borrow)
172-
| PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
173-
matches!(self.borrow_check_mode, BorrowCheckMode::IncludeBorrows)
174-
}
175174
_ => false,
176175
}
177176
}
@@ -183,19 +182,29 @@ impl<'a, 'tcx> Visitor<'tcx> for PointerFinder<'a, 'tcx> {
183182
return;
184183
}
185184

186-
// Since Deref projections must come first and only once, the pointer for an indirect place
187-
// is the Local that the Place is based on.
185+
// Get the place and type we visit.
188186
let pointer = Place::from(place.local);
189-
let pointer_ty = self.local_decls[place.local].ty;
187+
let pointer_ty = pointer.ty(self.local_decls, self.tcx).ty;
190188

191189
// We only want to check places based on raw pointers
192-
if !pointer_ty.is_raw_ptr() {
190+
let &ty::RawPtr(mut pointee_ty, _) = pointer_ty.kind() else {
193191
trace!("Indirect, but not based on an raw ptr, not checking {:?}", place);
194192
return;
193+
};
194+
195+
// 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.
197+
if matches!(self.field_projection_mode, BorrowedFieldProjectionMode::FollowProjections)
198+
&& 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.
205+
pointee_ty = place.ty(self.local_decls, self.tcx).ty;
195206
}
196207

197-
let pointee_ty =
198-
pointer_ty.builtin_deref(true).expect("no builtin_deref for an raw pointer");
199208
// Ideally we'd support this in the future, but for now we are limited to sized types.
200209
if !pointee_ty.is_sized(self.tcx, self.typing_env) {
201210
trace!("Raw pointer, but pointee is not known to be sized: {:?}", pointer_ty);
@@ -207,6 +216,7 @@ impl<'a, 'tcx> Visitor<'tcx> for PointerFinder<'a, 'tcx> {
207216
ty::Array(ty, _) => *ty,
208217
_ => pointee_ty,
209218
};
219+
// Check if we excluded this pointee type from the check.
210220
if self.excluded_pointees.contains(&element_ty) {
211221
trace!("Skipping pointer for type: {:?}", pointee_ty);
212222
return;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ run-fail
2+
//@ ignore-i686-pc-windows-msvc: #112480
3+
//@ compile-flags: -C debug-assertions
4+
//@ error-pattern: misaligned pointer dereference: address must be a multiple of 0x4 but is
5+
6+
struct Misalignment {
7+
a: u32,
8+
}
9+
10+
fn main() {
11+
let mut items: [Misalignment; 2] = [Misalignment { a: 0 }, Misalignment { a: 1 }];
12+
unsafe {
13+
let ptr: *const Misalignment = items.as_ptr().byte_add(1);
14+
let _ptr: &u32 = unsafe { &(*ptr).a };
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ run-fail
2+
//@ ignore-i686-pc-windows-msvc: #112480
3+
//@ compile-flags: -C debug-assertions
4+
//@ error-pattern: misaligned pointer dereference: address must be a multiple of 0x4 but is
5+
6+
fn main() {
7+
let x = [0u32; 2];
8+
let ptr = x.as_ptr();
9+
unsafe {
10+
let _ptr = &(*(ptr.byte_add(1)));
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ run-fail
2+
//@ ignore-i686-pc-windows-msvc: #112480
3+
//@ compile-flags: -C debug-assertions
4+
//@ error-pattern: misaligned pointer dereference: address must be a multiple of 0x4 but is
5+
6+
fn main() {
7+
let mut x = [0u32; 2];
8+
let ptr = x.as_mut_ptr();
9+
unsafe {
10+
let _ptr = &mut (*(ptr.byte_add(1)));
11+
}
12+
}

0 commit comments

Comments
 (0)