Skip to content

Commit 22364f8

Browse files
committed
This pattern using lazy protected Reserved IM prevents spurious writes
1 parent f50b0b8 commit 22364f8

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Illustrating a problematic interaction between Reserved, interior mutability,
2+
// and protectors, that makes spurious writes fail in the previous model of Tree Borrows.
3+
// As for all similar tests, we disable preemption so that the error message is deterministic.
4+
//@compile-flags: -Zmiri-tree-borrows -Zmiri-preemption-rate=0
5+
6+
use std::cell::Cell;
7+
use std::sync::{Arc, Barrier};
8+
use std::thread;
9+
10+
// Here is the problematic interleaving:
11+
// - thread 1: retag and activate `x` (protected)
12+
// - thread 2: create but do not retag (lazy) `y` as Reserved with interior mutability
13+
// - thread 1: spurious write through `x` would go here
14+
// - thread 2: function exit (noop due to lazyness)
15+
// - thread 1: function exit (no permanent effect on `y` because it is now Reserved IM unprotected)
16+
// - thread 2: write through `y`
17+
// In the source code nothing happens to `y`
18+
19+
// `Send`able raw pointer wrapper.
20+
#[derive(Copy, Clone)]
21+
struct SendPtr(*mut u8);
22+
unsafe impl Send for SendPtr {}
23+
24+
type IdxBarrier = (usize, Arc<Barrier>);
25+
26+
// Barriers to enforce the interleaving.
27+
// This macro expects `synchronized!(thread, msg)` where `thread` is a `IdxBarrier`,
28+
// and `msg` is the message to be displayed when the thread reaches this point in the execution.
29+
macro_rules! synchronized {
30+
($thread:expr, $msg:expr) => {{
31+
let (thread_id, barrier) = &$thread;
32+
eprintln!("Thread {} executing: {}", thread_id, $msg);
33+
barrier.wait();
34+
}};
35+
}
36+
37+
fn main() {
38+
eprintln!("Without spurious write");
39+
example(false);
40+
41+
eprintln!("\nIf this text is visible then the model forbids spurious writes.\n");
42+
43+
eprintln!("With spurious write");
44+
example(true);
45+
46+
eprintln!("\nIf this text is visible then the model fails to detect a noalias violation.\n");
47+
}
48+
49+
fn example(spurious: bool) {
50+
// For this example it is important that we have at least two bytes
51+
// because lazyness is involved.
52+
let mut data = [0u8; 2];
53+
let ptr = SendPtr(std::ptr::addr_of_mut!(data[0]));
54+
let barrier = Arc::new(Barrier::new(2));
55+
let bx = Arc::clone(&barrier);
56+
let by = Arc::clone(&barrier);
57+
58+
// Retag and activate `x`, wait until the other thread creates a lazy permission.
59+
// Then do a spurious write. Finally exit the function after the other thread.
60+
let thread_1 = thread::spawn(move || {
61+
let b = (1, bx);
62+
synchronized!(b, "start");
63+
let ptr = ptr;
64+
synchronized!(b, "retag x (&mut, protect)");
65+
fn inner(x: &mut u8, b: IdxBarrier, spurious: bool) {
66+
*x = 42; // activate immediately
67+
synchronized!(b, "[lazy] retag y (&mut, protect, IM)");
68+
// A spurious write should be valid here because `x` is
69+
// `Active` and protected.
70+
if spurious {
71+
synchronized!(b, "spurious write x (executed)");
72+
*x = 64;
73+
} else {
74+
synchronized!(b, "spurious write x (skipped)");
75+
}
76+
synchronized!(b, "ret y");
77+
synchronized!(b, "ret x");
78+
}
79+
inner(unsafe { &mut *ptr.0 }, b.clone(), spurious);
80+
synchronized!(b, "write y");
81+
synchronized!(b, "end");
82+
});
83+
84+
// Create a lazy Reserved with interior mutability.
85+
// Wait for the other thread's spurious write then observe the side effects
86+
// of that write.
87+
let thread_2 = thread::spawn(move || {
88+
let b = (2, by);
89+
synchronized!(b, "start");
90+
let ptr = ptr;
91+
synchronized!(b, "retag x (&mut, protect)");
92+
synchronized!(b, "[lazy] retag y (&mut, protect, IM)");
93+
fn inner(y: &mut Cell<u8>, b: IdxBarrier) -> *mut u8 {
94+
synchronized!(b, "spurious write x");
95+
synchronized!(b, "ret y");
96+
y as *mut Cell<u8> as *mut u8
97+
}
98+
// Currently `ptr` points to `data[0]`. We retag it for `data[1]`
99+
// then use it for `data[0]` where its initialization has been deferred.
100+
let y = inner(unsafe { &mut *(ptr.0 as *mut Cell<u8>).wrapping_add(1) }, b.clone());
101+
synchronized!(b, "ret x");
102+
synchronized!(b, "write y");
103+
unsafe { *y.wrapping_sub(1) = 13 }
104+
synchronized!(b, "end");
105+
});
106+
107+
thread_1.join().unwrap();
108+
thread_2.join().unwrap();
109+
}

0 commit comments

Comments
 (0)