Skip to content

Commit e00fa96

Browse files
committed
Auto merge of #872 - RalfJung:retag-shallow, r=oli-obk
Make Retag shallow A shallow retag does not traverse into fields of compound typed to search for references to retag. It only retags "top-level"/"bare" references (and boxes). This helps with rust-lang/unsafe-code-guidelines#125 because it also means that we do not add protectors for references passed in fields of a struct (or other compound types). Until we know what the rules should be for protectors, I prefer to be less aggressive about what we are rejecting. This also matches our work-in-progress Coq formalization.
2 parents 5bbf673 + 30fb027 commit e00fa96

8 files changed

+93
-87
lines changed

src/stacked_borrows.rs

+4-44
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc::hir::{MutMutable, MutImmutable};
1212
use rustc::mir::RetagKind;
1313

1414
use crate::{
15-
InterpResult, InterpError, MiriEvalContext, HelpersEvalContextExt, Evaluator, MutValueVisitor,
15+
InterpResult, InterpError, HelpersEvalContextExt,
1616
MemoryKind, MiriMemoryKind, RangeMap, AllocId, Pointer, Immediate, ImmTy, PlaceTy, MPlaceTy,
1717
};
1818

@@ -632,54 +632,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
632632
}
633633
}
634634

635-
// We need a visitor to visit all references. However, that requires
636-
// a `MemPlace`, so we have a fast path for reference types that
637-
// avoids allocating.
635+
// We only reborrow "bare" references/boxes.
636+
// Not traversing into fields helps with <https://github.com/rust-lang/unsafe-code-guidelines/issues/125>,
637+
// but might also cost us optimization and analyses. We will have to experiment more with this.
638638
if let Some((mutbl, protector)) = qualify(place.layout.ty, kind) {
639639
// Fast path.
640640
let val = this.read_immediate(this.place_to_op(place)?)?;
641641
let val = this.retag_reference(val, mutbl, protector)?;
642642
this.write_immediate(val, place)?;
643-
return Ok(());
644-
}
645-
let place = this.force_allocation(place)?;
646-
647-
let mut visitor = RetagVisitor { ecx: this, kind };
648-
visitor.visit_value(place)?;
649-
650-
// The actual visitor.
651-
struct RetagVisitor<'ecx, 'mir, 'tcx> {
652-
ecx: &'ecx mut MiriEvalContext<'mir, 'tcx>,
653-
kind: RetagKind,
654-
}
655-
impl<'ecx, 'mir, 'tcx>
656-
MutValueVisitor<'mir, 'tcx, Evaluator<'tcx>>
657-
for
658-
RetagVisitor<'ecx, 'mir, 'tcx>
659-
{
660-
type V = MPlaceTy<'tcx, Tag>;
661-
662-
#[inline(always)]
663-
fn ecx(&mut self) -> &mut MiriEvalContext<'mir, 'tcx> {
664-
&mut self.ecx
665-
}
666-
667-
// Primitives of reference type, that is the one thing we are interested in.
668-
fn visit_primitive(&mut self, place: MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx>
669-
{
670-
// Cannot use `builtin_deref` because that reports *immutable* for `Box`,
671-
// making it useless.
672-
if let Some((mutbl, protector)) = qualify(place.layout.ty, self.kind) {
673-
let val = self.ecx.read_immediate(place.into())?;
674-
let val = self.ecx.retag_reference(
675-
val,
676-
mutbl,
677-
protector
678-
)?;
679-
self.ecx.write_immediate(val, place.into())?;
680-
}
681-
Ok(())
682-
}
683643
}
684644

685645
Ok(())

tests/compile-fail/stacked_borrows/buggy_split_at_mut.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ mod safe {
99
assert!(mid <= len);
1010

1111
(from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid"
12-
//~^ ERROR borrow stack
1312
from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
1413
}
1514
}
@@ -18,6 +17,7 @@ mod safe {
1817
fn main() {
1918
let mut array = [1,2,3,4];
2019
let (a, b) = safe::split_at_mut(&mut array, 0);
20+
//~^ ERROR borrow stack
2121
a[1] = 5;
2222
b[1] = 6;
2323
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
// Make sure that we cannot return a `&mut` that got already invalidated, not even in an `Option`.
2+
// Due to shallow reborrowing, the error only surfaces when we look into the `Option`.
23
fn foo(x: &mut (i32, i32)) -> Option<&mut i32> {
34
let xraw = x as *mut (i32, i32);
4-
let ret = Some(unsafe { &mut (*xraw).1 });
5+
let ret = unsafe { &mut (*xraw).1 }; // let-bind to avoid 2phase
6+
let ret = Some(ret);
57
let _val = unsafe { *xraw }; // invalidate xref
6-
ret //~ ERROR borrow stack
8+
ret
79
}
810

911
fn main() {
10-
foo(&mut (1, 2));
12+
match foo(&mut (1, 2)) {
13+
Some(_x) => {}, //~ ERROR borrow stack
14+
None => {},
15+
}
1116
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Make sure that we cannot return a `&mut` that got already invalidated, not even in a tuple.
2+
// Due to shallow reborrowing, the error only surfaces when we look into the tuple.
23
fn foo(x: &mut (i32, i32)) -> (&mut i32,) {
34
let xraw = x as *mut (i32, i32);
45
let ret = (unsafe { &mut (*xraw).1 },);
56
let _val = unsafe { *xraw }; // invalidate xref
6-
ret //~ ERROR borrow stack
7+
ret
78
}
89

910
fn main() {
10-
foo(&mut (1, 2));
11+
foo(&mut (1, 2)).0; //~ ERROR: borrow stack
1112
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
// Make sure that we cannot return a `&` that got already invalidated, not even in an `Option`.
2+
// Due to shallow reborrowing, the error only surfaces when we look into the `Option`.
23
fn foo(x: &mut (i32, i32)) -> Option<&i32> {
34
let xraw = x as *mut (i32, i32);
45
let ret = Some(unsafe { &(*xraw).1 });
56
unsafe { *xraw = (42, 23) }; // unfreeze
6-
ret //~ ERROR borrow stack
7+
ret
78
}
89

910
fn main() {
10-
foo(&mut (1, 2));
11+
match foo(&mut (1, 2)) {
12+
Some(_x) => {}, //~ ERROR borrow stack
13+
None => {},
14+
}
1115
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Make sure that we cannot return a `&` that got already invalidated, not even in a tuple.
2+
// Due to shallow reborrowing, the error only surfaces when we look into the tuple.
23
fn foo(x: &mut (i32, i32)) -> (&i32,) {
34
let xraw = x as *mut (i32, i32);
45
let ret = (unsafe { &(*xraw).1 },);
56
unsafe { *xraw = (42, 23) }; // unfreeze
6-
ret //~ ERROR borrow stack
7+
ret
78
}
89

910
fn main() {
10-
foo(&mut (1, 2));
11+
foo(&mut (1, 2)).0; //~ ERROR borrow stack
1112
}

tests/run-pass/refcell.rs

-33
This file was deleted.
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::cell::{RefCell, Ref, RefMut};
2+
3+
fn main() {
4+
basic();
5+
ref_protector();
6+
ref_mut_protector();
7+
}
8+
9+
fn basic() {
10+
let c = RefCell::new(42);
11+
{
12+
let s1 = c.borrow();
13+
let _x: i32 = *s1;
14+
let s2 = c.borrow();
15+
let _x: i32 = *s1;
16+
let _y: i32 = *s2;
17+
let _x: i32 = *s1;
18+
let _y: i32 = *s2;
19+
}
20+
{
21+
let mut m = c.borrow_mut();
22+
let _z: i32 = *m;
23+
{
24+
let s: &i32 = &*m;
25+
let _x = *s;
26+
}
27+
*m = 23;
28+
let _z: i32 = *m;
29+
}
30+
{
31+
let s1 = c.borrow();
32+
let _x: i32 = *s1;
33+
let s2 = c.borrow();
34+
let _x: i32 = *s1;
35+
let _y: i32 = *s2;
36+
let _x: i32 = *s1;
37+
let _y: i32 = *s2;
38+
}
39+
}
40+
41+
// Adding a Stacked Borrows protector for `Ref` would break this
42+
fn ref_protector() {
43+
fn break_it(rc: &RefCell<i32>, r: Ref<'_, i32>) {
44+
// `r` has a shared reference, it is passed in as argument and hence
45+
// a protector is added that marks this memory as read-only for the entire
46+
// duration of this function.
47+
drop(r);
48+
// *oops* here we can mutate that memory.
49+
*rc.borrow_mut() = 2;
50+
}
51+
52+
let rc = RefCell::new(0);
53+
break_it(&rc, rc.borrow())
54+
}
55+
56+
fn ref_mut_protector() {
57+
fn break_it(rc: &RefCell<i32>, r: RefMut<'_, i32>) {
58+
// `r` has a shared reference, it is passed in as argument and hence
59+
// a protector is added that marks this memory as inaccessible for the entire
60+
// duration of this function
61+
drop(r);
62+
// *oops* here we can mutate that memory.
63+
*rc.borrow_mut() = 2;
64+
}
65+
66+
let rc = RefCell::new(0);
67+
break_it(&rc, rc.borrow_mut())
68+
}

0 commit comments

Comments
 (0)