Skip to content

Commit 4c9b6f4

Browse files
committed
justify return-position noalias: make Box clear stack on some retags
1 parent b38a6d3 commit 4c9b6f4

18 files changed

+129
-54
lines changed

compiler/rustc_middle/src/mir/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,7 @@ impl Debug for Statement<'_> {
14441444
"Retag({}{:?})",
14451445
match kind {
14461446
RetagKind::FnEntry => "[fn entry] ",
1447+
RetagKind::FnReturn => "[fn return] ",
14471448
RetagKind::TwoPhase => "[2phase] ",
14481449
RetagKind::Raw => "[raw] ",
14491450
RetagKind::Default => "",

compiler/rustc_middle/src/mir/syntax.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ pub enum StatementKind<'tcx> {
323323
/// For code that is not specific to stacked borrows, you should consider retags to read and
324324
/// modify the place in an opaque way.
325325
///
326-
/// Only `RetagKind::Default` and `RetagKind::FnEntry` are permitted.
326+
/// Only `RetagKind::Default`, `RetagKind::FnEntry`, `RetagKind::FnReturn` are permitted.
327327
Retag(RetagKind, Box<Place<'tcx>>),
328328

329329
/// Encodes a user's type ascription. These need to be preserved
@@ -412,6 +412,8 @@ impl std::fmt::Display for NonDivergingIntrinsic<'_> {
412412
pub enum RetagKind {
413413
/// The initial retag of arguments when entering a function.
414414
FnEntry,
415+
/// The retag of a value that was just returned from another function.
416+
FnReturn,
415417
/// Retag preparing for a two-phase borrow.
416418
TwoPhase,
417419
/// Retagging raw pointers.

compiler/rustc_mir_transform/src/add_retag.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl<'tcx> MirPass<'tcx> for AddRetag {
112112
0,
113113
Statement {
114114
source_info,
115-
kind: StatementKind::Retag(RetagKind::Default, Box::new(dest_place)),
115+
kind: StatementKind::Retag(RetagKind::FnReturn, Box::new(dest_place)),
116116
},
117117
);
118118
}

src/test/mir-opt/retag.main.SimplifyCfg-elaborate-drops.after.mir

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fn main() -> () {
7676
}
7777

7878
bb1: {
79-
Retag(_3); // scope 1 at $DIR/retag.rs:+3:17: +3:36
79+
Retag([fn return] _3); // scope 1 at $DIR/retag.rs:+3:17: +3:36
8080
StorageDead(_6); // scope 1 at $DIR/retag.rs:+3:35: +3:36
8181
StorageDead(_4); // scope 1 at $DIR/retag.rs:+3:35: +3:36
8282
StorageDead(_7); // scope 1 at $DIR/retag.rs:+3:36: +3:37
@@ -122,7 +122,7 @@ fn main() -> () {
122122
}
123123

124124
bb3: {
125-
Retag(_15); // scope 6 at $DIR/retag.rs:+15:14: +15:19
125+
Retag([fn return] _15); // scope 6 at $DIR/retag.rs:+15:14: +15:19
126126
StorageDead(_17); // scope 6 at $DIR/retag.rs:+15:18: +15:19
127127
StorageDead(_16); // scope 6 at $DIR/retag.rs:+15:18: +15:19
128128
StorageDead(_18); // scope 6 at $DIR/retag.rs:+15:19: +15:20
@@ -148,7 +148,7 @@ fn main() -> () {
148148
}
149149

150150
bb4: {
151-
Retag(_19); // scope 7 at $DIR/retag.rs:+18:5: +18:24
151+
Retag([fn return] _19); // scope 7 at $DIR/retag.rs:+18:5: +18:24
152152
StorageDead(_22); // scope 7 at $DIR/retag.rs:+18:23: +18:24
153153
StorageDead(_20); // scope 7 at $DIR/retag.rs:+18:23: +18:24
154154
StorageDead(_23); // scope 7 at $DIR/retag.rs:+18:24: +18:25

src/tools/miri/src/borrow_tracker/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub struct GlobalStateInner {
8787
/// Table storing the "base" tag for each allocation.
8888
/// The base tag is the one used for the initial pointer.
8989
/// We need this in a separate table to handle cyclic statics.
90-
pub base_ptr_tags: FxHashMap<AllocId, BorTag>,
90+
base_ptr_tags: FxHashMap<AllocId, BorTag>,
9191
/// Next unused call ID (for protectors).
9292
pub next_call_id: CallId,
9393
/// All currently protected tags.

src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ struct RetagOp {
193193
#[derive(Debug, Clone, Copy, PartialEq)]
194194
pub enum RetagCause {
195195
Normal,
196-
FnReturn,
196+
FnReturnPlace,
197197
FnEntry,
198198
TwoPhase,
199199
}
@@ -496,7 +496,7 @@ impl RetagCause {
496496
match self {
497497
RetagCause::Normal => "retag",
498498
RetagCause::FnEntry => "FnEntry retag",
499-
RetagCause::FnReturn => "FnReturn retag",
499+
RetagCause::FnReturnPlace => "return-place retag",
500500
RetagCause::TwoPhase => "two-phase retag",
501501
}
502502
.to_string()

src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub struct Stacks {
4848
enum NewPermission {
4949
Uniform {
5050
perm: Permission,
51+
/// If true, indicates that the stack should be cleared of all other tags.
52+
clear: bool,
5153
access: Option<AccessKind>,
5254
protector: Option<ProtectorKind>,
5355
},
@@ -77,19 +79,22 @@ impl NewPermission {
7779
assert!(protector.is_none()); // RetagKind can't be both FnEntry and TwoPhase.
7880
NewPermission::Uniform {
7981
perm: Permission::SharedReadWrite,
82+
clear: false,
8083
access: None,
8184
protector: None,
8285
}
8386
} else if pointee.is_unpin(*cx.tcx, cx.param_env()) {
8487
// A regular full mutable reference.
8588
NewPermission::Uniform {
8689
perm: Permission::Unique,
90+
clear: false,
8791
access: Some(AccessKind::Write),
8892
protector,
8993
}
9094
} else {
9195
NewPermission::Uniform {
9296
perm: Permission::SharedReadWrite,
97+
clear: false,
9398
// FIXME: We emit `dereferenceable` for `!Unpin` mutable references, so we
9499
// should do fake accesses here. But then we run into
95100
// <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>, so for now
@@ -104,6 +109,7 @@ impl NewPermission {
104109
// Mutable raw pointer. No access, not protected.
105110
NewPermission::Uniform {
106111
perm: Permission::SharedReadWrite,
112+
clear: false,
107113
access: None,
108114
protector: None,
109115
}
@@ -371,11 +377,13 @@ impl<'tcx> Stack {
371377

372378
/// Derive a new pointer from one with the given tag.
373379
///
380+
/// `clear` indicates whether everything else should be removed from the stack.
374381
/// `access` indicates which kind of memory access this retag itself should correspond to.
375382
fn grant(
376383
&mut self,
377384
derived_from: ProvenanceExtra,
378385
new: Item,
386+
clear: bool,
379387
access: Option<AccessKind>,
380388
global: &GlobalStateInner,
381389
dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
@@ -392,6 +400,15 @@ impl<'tcx> Stack {
392400
// This ensures F2b for `Unique`, by removing offending `SharedReadOnly`.
393401
self.access(access, derived_from, global, dcx, exposed_tags)?;
394402

403+
if clear {
404+
// Remove all items, also checking their protectors.
405+
for idx in (0..self.len()).rev() {
406+
let item = self.get(idx).unwrap();
407+
Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Conflict)?;
408+
}
409+
self.clear();
410+
}
411+
395412
// We insert "as far up as possible": We know only compatible items are remaining
396413
// on top of `derived_from`, and we want the new item at the top so that we
397414
// get the strongest possible guarantees.
@@ -400,6 +417,7 @@ impl<'tcx> Stack {
400417
} else {
401418
// The tricky case: creating a new SRW permission without actually being an access.
402419
assert!(new.perm() == Permission::SharedReadWrite);
420+
assert!(!clear);
403421

404422
// First we figure out which item grants our parent (`derived_from`) this kind of access.
405423
// We use that to determine where to put the new item.
@@ -723,7 +741,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
723741

724742
// Update the stacks, according to the new permission information we are given.
725743
match new_perm {
726-
NewPermission::Uniform { perm, access, protector } => {
744+
NewPermission::Uniform { perm, clear, access, protector } => {
727745
assert!(perm != Permission::SharedReadOnly);
728746
// Here we can avoid `borrow()` calls because we have mutable references.
729747
// Note that this asserts that the allocation is mutable -- but since we are creating a
@@ -741,7 +759,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
741759
alloc_range(base_offset, size),
742760
);
743761
stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| {
744-
stack.grant(orig_tag, item, access, &global, dcx, exposed_tags)
762+
stack.grant(orig_tag, item, clear, access, &global, dcx, exposed_tags)
745763
})?;
746764
drop(global);
747765
if let Some(access) = access {
@@ -784,7 +802,8 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
784802
alloc_range(base_offset, size),
785803
);
786804
stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| {
787-
stack.grant(orig_tag, item, access, &global, dcx, exposed_tags)
805+
let clear = false;
806+
stack.grant(orig_tag, item, clear, access, &global, dcx, exposed_tags)
788807
})?;
789808
drop(global);
790809
if let Some(access) = access {
@@ -862,7 +881,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
862881
let new_perm = NewPermission::from_ref_ty(val.layout.ty, kind, this);
863882
let retag_cause = match kind {
864883
RetagKind::TwoPhase { .. } => RetagCause::TwoPhase,
865-
RetagKind::FnEntry => unreachable!(),
884+
RetagKind::FnEntry | RetagKind::FnReturn => unreachable!(),
866885
RetagKind::Raw | RetagKind::Default => RetagCause::Normal,
867886
};
868887
this.sb_retag_reference(val, new_perm, retag_cause)
@@ -878,7 +897,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
878897
let retag_cause = match kind {
879898
RetagKind::Raw | RetagKind::TwoPhase { .. } => unreachable!(), // these can only happen in `retag_ptr_value`
880899
RetagKind::FnEntry => RetagCause::FnEntry,
881-
RetagKind::Default => RetagCause::Normal,
900+
RetagKind::Default | RetagKind::FnReturn => RetagCause::Normal,
882901
};
883902
let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, retag_fields };
884903
return visitor.visit_value(place);
@@ -915,12 +934,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
915934
}
916935

917936
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
918-
// Boxes get a weak protectors, since they may be deallocated.
937+
// We cannot protect boxes since they might get invalidated if passed to an `id`
938+
// function (due to the return-position `noalias`). However, doing a `clear` on
939+
// `FnEntry` also has the effect that using any other pointer to access this memory
940+
// is *immediate* UB.
941+
// FIXME: should we `clear` on *all* `Box` retags? Currently (2022-12-27) this leads
942+
// to UB somewhere in thread-local dtor handling.
919943
let new_perm = NewPermission::Uniform {
920944
perm: Permission::Unique,
945+
clear: matches!(self.kind, RetagKind::FnEntry | RetagKind::FnReturn),
921946
access: Some(AccessKind::Write),
922-
protector: (self.kind == RetagKind::FnEntry)
923-
.then_some(ProtectorKind::WeakProtector),
947+
protector: None,
924948
};
925949
self.retag_ptr_inplace(place, new_perm, self.retag_cause)
926950
}
@@ -995,10 +1019,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
9951019
// Reborrow it. With protection! That is part of the point.
9961020
let new_perm = NewPermission::Uniform {
9971021
perm: Permission::Unique,
1022+
clear: false,
9981023
access: Some(AccessKind::Write),
9991024
protector: Some(ProtectorKind::StrongProtector),
10001025
};
1001-
let val = this.sb_retag_reference(&val, new_perm, RetagCause::FnReturn)?;
1026+
let val = this.sb_retag_reference(&val, new_perm, RetagCause::FnReturnPlace)?;
10021027
// And use reborrowed pointer for return place.
10031028
let return_place = this.ref_to_mplace(&val)?;
10041029
this.frame_mut().return_place = return_place.into();

src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ impl<'tcx> Stack {
252252
// and this check actually ensures we do not access an invalid cache.
253253
// When a stack is created and when items are removed from the top of the borrow stack, we
254254
// need some valid value to populate the cache. In both cases, we try to use the bottom
255-
// item. But when the stack is cleared in `set_unknown_bottom` there is nothing we could
256-
// place in the cache that is correct. But due to the way we populate the cache in
255+
// item. But when the stack is cleared in `clear` or `set_unknown_bottom` there is nothing
256+
// we could place in the cache that is correct. But due to the way we populate the cache in
257257
// `StackCache::add`, we know that when the borrow stack has grown larger than the cache,
258258
// every slot in the cache is valid.
259259
if self.borrows.len() <= CACHE_LEN {
@@ -368,6 +368,19 @@ impl<'tcx> Stack {
368368
}
369369
}
370370

371+
// Empty the stack.
372+
pub fn clear(&mut self) {
373+
// We clear the borrow stack but the lookup cache doesn't support clearing per se. Instead,
374+
// there is a check explained in `find_granting_cache` which protects against accessing the
375+
// cache when it has been cleared and not yet refilled.
376+
self.borrows.clear();
377+
self.unknown_bottom = None;
378+
#[cfg(feature = "stack-cache")]
379+
{
380+
self.unique_range = 0..0;
381+
}
382+
}
383+
371384
/// Find all `Unique` elements in this borrow stack above `granting_idx`, pass a copy of them
372385
/// to the `visitor`, then set their `Permission` to `Disabled`.
373386
pub fn disable_uniques_starting_at(

src/tools/miri/tests/fail/stacked_borrows/box_noalias_violation.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ unsafe fn test(mut x: Box<i32>, y: *const i32) -> i32 {
22
// We will call this in a way that x and y alias.
33
*x = 5;
44
std::mem::forget(x);
5-
*y //~ERROR: weakly protected
5+
*y //~ERROR: does not exist in the borrow stack
66
}
77

88
fn main() {
99
unsafe {
1010
let mut v = 42;
1111
let ptr = &mut v as *mut i32;
12-
test(Box::from_raw(ptr), ptr);
12+
let b = Box::from_raw(ptr);
13+
let ptr = &*b as *const i32;
14+
test(b, ptr);
1315
}
1416
}

src/tools/miri/tests/fail/stacked_borrows/box_noalias_violation.stderr

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1-
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is weakly protected because it is an argument of call ID
1+
error: Undefined Behavior: attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
22
--> $DIR/box_noalias_violation.rs:LL:CC
33
|
44
LL | *y
5-
| ^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is weakly protected because it is an argument of call ID
5+
| ^^
6+
| |
7+
| attempting a read access using <TAG> at ALLOC[0x0], but that tag does not exist in the borrow stack for this location
8+
| this error occurs as part of an access at ALLOC[0x0..0x4]
69
|
710
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
811
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
9-
help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
12+
help: <TAG> was created by a SharedReadOnly retag at offsets [0x0..0x4]
1013
--> $DIR/box_noalias_violation.rs:LL:CC
1114
|
12-
LL | let ptr = &mut v as *mut i32;
13-
| ^^^^^^
14-
help: <TAG> is this argument
15+
LL | let ptr = &*b as *const i32;
16+
| ^^^
17+
help: <TAG> was later invalidated at offsets [0x0..0x4] by a Unique retag
1518
--> $DIR/box_noalias_violation.rs:LL:CC
1619
|
17-
LL | unsafe fn test(mut x: Box<i32>, y: *const i32) -> i32 {
18-
| ^^^^^
20+
LL | test(b, ptr);
21+
| ^
1922
= note: BACKTRACE (of the first span):
2023
= note: inside `test` at $DIR/box_noalias_violation.rs:LL:CC
2124
note: inside `main`
2225
--> $DIR/box_noalias_violation.rs:LL:CC
2326
|
24-
LL | test(Box::from_raw(ptr), ptr);
25-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27+
LL | test(b, ptr);
28+
| ^^^^^^^^^^^^
2629

2730
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
2831

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//@error-pattern: /deallocating while item \[Unique for .*\] is strongly protected/
2+
use std::alloc::{dealloc, Layout};
23

34
fn inner(x: &mut i32, f: fn(&mut i32)) {
45
// `f` may mutate, but it may not deallocate!
@@ -7,7 +8,7 @@ fn inner(x: &mut i32, f: fn(&mut i32)) {
78

89
fn main() {
910
inner(Box::leak(Box::new(0)), |x| {
10-
let raw = x as *mut _;
11-
drop(unsafe { Box::from_raw(raw) });
11+
let raw = x as *mut i32 as *mut u8;
12+
drop(unsafe { dealloc(raw, Layout::new::<i32>()) });
1213
});
1314
}

src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@ LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
88
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
99
= note: BACKTRACE:
1010
= note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
11-
= note: inside `<std::alloc::Global as std::alloc::Allocator>::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC
12-
= note: inside `alloc::alloc::box_free::<i32, std::alloc::Global>` at RUSTLIB/alloc/src/alloc.rs:LL:CC
13-
= note: inside `std::ptr::drop_in_place::<std::boxed::Box<i32>> - shim(Some(std::boxed::Box<i32>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC
14-
= note: inside `std::mem::drop::<std::boxed::Box<i32>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC
1511
note: inside closure
1612
--> $DIR/deallocate_against_protector1.rs:LL:CC
1713
|
18-
LL | drop(unsafe { Box::from_raw(raw) });
19-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
LL | drop(unsafe { dealloc(raw, Layout::new::<i32>()) });
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2016
= note: inside `<[closure@$DIR/deallocate_against_protector1.rs:LL:CC] as std::ops::FnOnce<(&mut i32,)>>::call_once - shim` at RUSTLIB/core/src/ops/function.rs:LL:CC
2117
note: inside `inner`
2218
--> $DIR/deallocate_against_protector1.rs:LL:CC
@@ -27,8 +23,8 @@ note: inside `main`
2723
--> $DIR/deallocate_against_protector1.rs:LL:CC
2824
|
2925
LL | / inner(Box::leak(Box::new(0)), |x| {
30-
LL | | let raw = x as *mut _;
31-
LL | | drop(unsafe { Box::from_raw(raw) });
26+
LL | | let raw = x as *mut i32 as *mut u8;
27+
LL | | drop(unsafe { dealloc(raw, Layout::new::<i32>()) });
3228
LL | | });
3329
| |______^
3430

src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector2.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//@error-pattern: /deallocating while item \[SharedReadWrite for .*\] is strongly protected/
2+
use std::alloc::{dealloc, Layout};
23
use std::marker::PhantomPinned;
34

45
pub struct NotUnpin(i32, PhantomPinned);
@@ -10,7 +11,7 @@ fn inner(x: &mut NotUnpin, f: fn(&mut NotUnpin)) {
1011

1112
fn main() {
1213
inner(Box::leak(Box::new(NotUnpin(0, PhantomPinned))), |x| {
13-
let raw = x as *mut _;
14-
drop(unsafe { Box::from_raw(raw) });
14+
let raw = x as *mut NotUnpin as *mut u8;
15+
drop(unsafe { dealloc(raw, Layout::new::<NotUnpin>()) });
1516
});
1617
}

0 commit comments

Comments
 (0)