about summary refs log tree commit diff
path: root/src/tools/miri/tests/pass/tree_borrows/spurious_read.rs
blob: 840832c633cf564bdd4769e1634b643094d6e8b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// We ensure a deterministic execution.
// Note that we are *also* using barriers: the barriers enforce the
// specific interleaving of operations that we want, but only the preemption
// rate guarantees that the error message is also deterministic.
//@compile-flags: -Zmiri-deterministic-concurrency
//@compile-flags: -Zmiri-tree-borrows

use std::sync::{Arc, Barrier};
use std::thread;

// A way to send raw pointers across threads.
// Note that when using this in closures will require explicit copying
// `let ptr = ptr;` to force the borrow checker to copy the `Send` wrapper
// instead of just copying the inner `!Send` field.
#[derive(Copy, Clone)]
struct SendPtr(*mut u8);
unsafe impl Send for SendPtr {}

fn main() {
    retagx_retagy_spuriousx_retx_rety_writey();
}

// We're going to enforce a specific interleaving of two
// threads, we use this macro in an effort to make it feasible
// to check in the output that the execution is properly synchronized.
//
// Provide `synchronized!(thread, msg)` where thread is
// a `(thread_id: usize, barrier: Arc<Barrier>)`, and `msg` the message
// to be displayed when the thread reaches this point in the execution.
macro_rules! synchronized {
    ($thread:expr, $msg:expr) => {{
        let (thread_id, barrier) = &$thread;
        eprintln!("Thread {} executing: {}", thread_id, $msg);
        barrier.wait();
    }};
}

// Interleaving:
//   retag x (protect)
//   retag y (protect)
//   spurious read x (target only, which we are executing)
//   ret x
//   ret y
//   write y
//
// This is an interleaving that will never have UB in the source
// (`x` is never accessed for the entire time that `y` is protected).
// For the spurious read to be allowed, we need to check that there is
// no UB in the target (i.e., *with* the spurious read).
//
// The interleaving differs from the one in `tests/fail/tree_borrows/spurious_read.rs` only
// in that it has the `write y` while `y` is no longer protected.
// When the write occurs after protection ends, both source and target are fine
// (checked by this test); when the write occurs during protection, both source
// and target are UB (checked by the `fail` test).
fn retagx_retagy_spuriousx_retx_rety_writey() {
    let mut data = 0u8;
    let ptr = SendPtr(std::ptr::addr_of_mut!(data));
    let barrier = Arc::new(Barrier::new(2));
    let bx = Arc::clone(&barrier);
    let by = Arc::clone(&barrier);

    // This thread only needs to
    // - retag `x` protected
    // - do a read through `x`
    // - remove `x`'s protector
    // Most of the complexity here is synchronization.
    let thread_x = thread::spawn(move || {
        let b = (1, bx);
        synchronized!(b, "start");
        let ptr = ptr;
        synchronized!(b, "retag x (&mut, protect)");
        fn as_mut(x: &mut u8, b: (usize, Arc<Barrier>)) -> *mut u8 {
            synchronized!(b, "retag y (&mut, protect)");
            synchronized!(b, "spurious read x");
            let _v = *x;
            synchronized!(b, "ret x");
            let x = x as *mut u8;
            x
        }
        let _x = as_mut(unsafe { &mut *ptr.0 }, b.clone());
        synchronized!(b, "ret y");
        synchronized!(b, "write y");
        synchronized!(b, "end");
    });

    // This thread's job is to
    // - retag `y` protected
    // - (wait a bit that the other thread performs its spurious read)
    // - remove `y`'s protector
    // - attempt a write through `y`.
    let thread_y = thread::spawn(move || {
        let b = (2, by);
        synchronized!(b, "start");
        let ptr = ptr;
        synchronized!(b, "retag x (&mut, protect)");
        synchronized!(b, "retag y (&mut, protect)");
        fn as_mut(y: &mut u8, b: (usize, Arc<Barrier>)) -> *mut u8 {
            synchronized!(b, "spurious read x");
            synchronized!(b, "ret x");
            let y = y as *mut u8;
            y
        }
        let y = as_mut(unsafe { &mut *ptr.0 }, b.clone());
        synchronized!(b, "ret y");
        synchronized!(b, "write y");
        unsafe {
            *y = 2;
        }
        synchronized!(b, "end");
    });

    thread_x.join().unwrap();
    thread_y.join().unwrap();
}