Skip to content

Commit

Permalink
Auto merge of #3137 - RalfJung:data-race, r=oli-obk
Browse files Browse the repository at this point in the history
Detect mixed-size and mixed-atomicity non-synchronized accesses

Fixes #2303
  • Loading branch information
bors committed Oct 24, 2023
2 parents cd3d70f + d7206eb commit f36a7d1
Show file tree
Hide file tree
Showing 18 changed files with 415 additions and 224 deletions.
278 changes: 163 additions & 115 deletions src/concurrency/data_race.rs

Large diffs are not rendered by default.

44 changes: 4 additions & 40 deletions src/concurrency/weak_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,6 @@ impl StoreBufferAlloc {
Self { store_buffers: RefCell::new(RangeObjectMap::new()) }
}

/// Checks if the range imperfectly overlaps with existing buffers
/// Used to determine if mixed-size atomic accesses
fn is_overlapping(&self, range: AllocRange) -> bool {
let buffers = self.store_buffers.borrow();
let access_type = buffers.access_type(range);
matches!(access_type, AccessType::ImperfectlyOverlapping(_))
}

/// When a non-atomic access happens on a location that has been atomically accessed
/// before without data race, we can determine that the non-atomic access fully happens
/// after all the prior atomic accesses so the location no longer needs to exhibit
Expand All @@ -190,6 +182,8 @@ impl StoreBufferAlloc {
buffers.remove_from_pos(pos);
}
AccessType::ImperfectlyOverlapping(pos_range) => {
// We rely on the data-race check making sure this is synchronized.
// Therefore we can forget about the old data here.
buffers.remove_pos_range(pos_range);
}
AccessType::Empty(_) => {
Expand All @@ -215,7 +209,7 @@ impl StoreBufferAlloc {
pos
}
AccessType::ImperfectlyOverlapping(pos_range) => {
// Once we reach here we would've already checked that this access is not racy
// Once we reach here we would've already checked that this access is not racy.
let mut buffers = self.store_buffers.borrow_mut();
buffers.remove_pos_range(pos_range.clone());
buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init));
Expand All @@ -240,6 +234,7 @@ impl StoreBufferAlloc {
pos
}
AccessType::ImperfectlyOverlapping(pos_range) => {
// Once we reach here we would've already checked that this access is not racy.
buffers.remove_pos_range(pos_range.clone());
buffers.insert_at_pos(pos_range.start, range, StoreBuffer::new(init));
pos_range.start
Expand Down Expand Up @@ -473,37 +468,6 @@ impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir,
pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
crate::MiriInterpCxExt<'mir, 'tcx>
{
// If weak memory emulation is enabled, check if this atomic op imperfectly overlaps with a previous
// atomic read or write. If it does, then we require it to be ordered (non-racy) with all previous atomic
// accesses on all the bytes in range
fn validate_overlapping_atomic(
&self,
place: &MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_ref();
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr())?;
if let crate::AllocExtra {
weak_memory: Some(alloc_buffers),
data_race: Some(alloc_clocks),
..
} = this.get_alloc_extra(alloc_id)?
{
let range = alloc_range(base_offset, place.layout.size);
if alloc_buffers.is_overlapping(range)
&& !alloc_clocks.race_free_with_atomic(
range,
this.machine.data_race.as_ref().unwrap(),
&this.machine.threads,
)
{
throw_unsup_format!(
"racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation"
);
}
}
Ok(())
}

fn buffered_atomic_rmw(
&mut self,
new_val: Scalar<Provenance>,
Expand Down
13 changes: 9 additions & 4 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ pub enum TerminationInfo {
span: SpanData,
},
DataRace {
involves_non_atomic: bool,
ptr: Pointer,
op1: RacingOp,
op2: RacingOp,
ptr: Pointer,
},
}

Expand Down Expand Up @@ -74,11 +75,15 @@ impl fmt::Display for TerminationInfo {
write!(f, "multiple definitions of symbol `{link_name}`"),
SymbolShimClashing { link_name, .. } =>
write!(f, "found `{link_name}` symbol definition that clashes with a built-in shim",),
DataRace { ptr, op1, op2 } =>
DataRace { involves_non_atomic, ptr, op1, op2 } =>
write!(
f,
"Data race detected between (1) {} on {} and (2) {} on {} at {ptr:?}. (2) just happened here",
op1.action, op1.thread_info, op2.action, op2.thread_info
"{} detected between (1) {} on {} and (2) {} on {} at {ptr:?}. (2) just happened here",
if *involves_non_atomic { "Data race" } else { "Race condition" },
op1.action,
op1.thread_info,
op2.action,
op2.thread_info
),
}
}
Expand Down
25 changes: 25 additions & 0 deletions tests/fail/data_race/mixed_size_read.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation

Check failure on line 1 in tests/fail/data_race/mixed_size_read.rs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

Unmatched diagnostics outside the testfile

Error: unsupported operation: can't call foreign function `_umtx_op` on OS `freebsd`
use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
use std::thread;

fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {
unsafe { std::mem::transmute(a) }
}

// We can't allow mixed-size accesses; they are not possible in C++ and even
// Intel says you shouldn't do it.
fn main() {
let a = AtomicU16::new(0);
let a16 = &a;
let a8 = convert(a16);

thread::scope(|s| {
s.spawn(|| {
a16.load(Ordering::SeqCst);
});
s.spawn(|| {
a8[0].load(Ordering::SeqCst);
//~^ ERROR: Race condition detected between (1) 2-byte Atomic Load on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Load on thread `<unnamed>`

Check failure on line 22 in tests/fail/data_race/mixed_size_read.rs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

Pattern not found

no message
});
});
}
20 changes: 20 additions & 0 deletions tests/fail/data_race/mixed_size_read.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: Undefined Behavior: Race condition detected between (1) 2-byte Atomic Load on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/mixed_size_read.rs:LL:CC

Check failure on line 2 in tests/fail/data_race/mixed_size_read.stderr

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

actual output differs from expected

this line was expected to be `error: unsupported operation: can't call foreign function `_umtx_op` on OS `freebsd``
|
LL | a8[0].load(Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 2-byte Atomic Load on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here

Check failure on line 5 in tests/fail/data_race/mixed_size_read.stderr

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

actual output differs from expected

this line was expected to be `LL | / libc::_umtx_op(`
|
help: and (1) occurred earlier here
--> $DIR/mixed_size_read.rs:LL:CC

Check failure on line 8 in tests/fail/data_race/mixed_size_read.stderr

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

actual output differs from expected

this line was expected to be ` = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support`
|
LL | a16.load(Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

Check failure on line 11 in tests/fail/data_race/mixed_size_read.stderr

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

actual output differs from expected

this line was expected to be `LL | / thread::scope(|s| {`
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span):
= note: inside closure at $DIR/mixed_size_read.rs:LL:CC

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

error: aborting due to previous error

25 changes: 25 additions & 0 deletions tests/fail/data_race/mixed_size_write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation
use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
use std::thread;

fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {
unsafe { std::mem::transmute(a) }
}

// We can't allow mixed-size accesses; they are not possible in C++ and even
// Intel says you shouldn't do it.
fn main() {
let a = AtomicU16::new(0);
let a16 = &a;
let a8 = convert(a16);

thread::scope(|s| {
s.spawn(|| {
a16.store(1, Ordering::SeqCst);
});
s.spawn(|| {
a8[0].store(1, Ordering::SeqCst);
//~^ ERROR: Race condition detected between (1) 2-byte Atomic Store on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Store on thread `<unnamed>`
});
});
}
20 changes: 20 additions & 0 deletions tests/fail/data_race/mixed_size_write.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: Undefined Behavior: Race condition detected between (1) 2-byte Atomic Store on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Store on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/mixed_size_write.rs:LL:CC

Check failure on line 2 in tests/fail/data_race/mixed_size_write.stderr

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

actual output differs from expected

this line was expected to be `error: unsupported operation: can't call foreign function `_umtx_op` on OS `freebsd``
|
LL | a8[0].store(1, Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 2-byte Atomic Store on thread `<unnamed>` and (2) 1-byte (different-size) Atomic Store on thread `<unnamed>` at ALLOC. (2) just happened here

Check failure on line 5 in tests/fail/data_race/mixed_size_write.stderr

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

actual output differs from expected

this line was expected to be `LL | / libc::_umtx_op(`
|
help: and (1) occurred earlier here
--> $DIR/mixed_size_write.rs:LL:CC

Check failure on line 8 in tests/fail/data_race/mixed_size_write.stderr

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

actual output differs from expected

this line was expected to be ` = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support`
|
LL | a16.store(1, Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Check failure on line 11 in tests/fail/data_race/mixed_size_write.stderr

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, x86_64-unknown-linux-gnu)

actual output differs from expected

this line was expected to be `LL | / thread::scope(|s| {`
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span):
= note: inside closure at $DIR/mixed_size_write.rs:LL:CC

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

error: aborting due to previous error

27 changes: 27 additions & 0 deletions tests/fail/data_race/read_read_race1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//@compile-flags: -Zmiri-preemption-rate=0.0
use std::sync::atomic::{AtomicU16, Ordering};
use std::thread;

// Make sure races between atomic and non-atomic reads are detected.
// This seems harmless but C++ does not allow them, so we can't allow them for now either.
// This test coverse the case where the non-atomic access come first.
fn main() {
let a = AtomicU16::new(0);

thread::scope(|s| {
s.spawn(|| {
let ptr = &a as *const AtomicU16 as *mut u16;
unsafe { ptr.read() };
});
s.spawn(|| {
thread::yield_now();

// We also put a non-atomic access here, but that should *not* be reported.
let ptr = &a as *const AtomicU16 as *mut u16;
unsafe { ptr.read() };
// Then do the atomic access.
a.load(Ordering::SeqCst);
//~^ ERROR: Data race detected between (1) Read on thread `<unnamed>` and (2) Atomic Load on thread `<unnamed>`
});
});
}
20 changes: 20 additions & 0 deletions tests/fail/data_race/read_read_race1.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: Undefined Behavior: Data race detected between (1) Read on thread `<unnamed>` and (2) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/read_read_race1.rs:LL:CC
|
LL | a.load(Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) Read on thread `<unnamed>` and (2) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
|
help: and (1) occurred earlier here
--> $DIR/read_read_race1.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span):
= note: inside closure at $DIR/read_read_race1.rs:LL:CC

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

error: aborting due to previous error

27 changes: 27 additions & 0 deletions tests/fail/data_race/read_read_race2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//@compile-flags: -Zmiri-preemption-rate=0.0
use std::sync::atomic::{AtomicU16, Ordering};
use std::thread;

// Make sure races between atomic and non-atomic reads are detected.
// This seems harmless but C++ does not allow them, so we can't allow them for now either.
// This test coverse the case where the atomic access come first.
fn main() {
let a = AtomicU16::new(0);

thread::scope(|s| {
s.spawn(|| {
// We also put a non-atomic access here, but that should *not* be reported.
let ptr = &a as *const AtomicU16 as *mut u16;
unsafe { ptr.read() };
// Then do the atomic access.
a.load(Ordering::SeqCst);
});
s.spawn(|| {
thread::yield_now();

let ptr = &a as *const AtomicU16 as *mut u16;
unsafe { ptr.read() };
//~^ ERROR: Data race detected between (1) Atomic Load on thread `<unnamed>` and (2) Read on thread `<unnamed>`
});
});
}
20 changes: 20 additions & 0 deletions tests/fail/data_race/read_read_race2.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: Undefined Behavior: Data race detected between (1) Atomic Load on thread `<unnamed>` and (2) Read on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/read_read_race2.rs:LL:CC
|
LL | unsafe { ptr.read() };
| ^^^^^^^^^^ Data race detected between (1) Atomic Load on thread `<unnamed>` and (2) Read on thread `<unnamed>` at ALLOC. (2) just happened here
|
help: and (1) occurred earlier here
--> $DIR/read_read_race2.rs:LL:CC
|
LL | a.load(Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span):
= note: inside closure at $DIR/read_read_race2.rs:LL:CC

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

error: aborting due to previous error

4 changes: 2 additions & 2 deletions tests/fail/weak_memory/racing_mixed_size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn split_u32_ptr(dword: *const u32) -> *const [u16; 2] {

// Wine's SRWLock implementation does this, which is definitely undefined in C++ memory model
// https://github.com/wine-mirror/wine/blob/303f8042f9db508adaca02ef21f8de4992cb9c03/dlls/ntdll/sync.c#L543-L566
// Though it probably works just fine on x86
// It probably works just fine on x86, but Intel does document this as "don't do it!"
pub fn main() {
let x = static_atomic_u32(0);
let j1 = spawn(move || {
Expand All @@ -31,7 +31,7 @@ pub fn main() {
let x_split = split_u32_ptr(x_ptr);
unsafe {
let hi = ptr::addr_of!((*x_split)[0]);
std::intrinsics::atomic_load_relaxed(hi); //~ ERROR: imperfectly overlapping
std::intrinsics::atomic_load_relaxed(hi); //~ ERROR: different-size
}
});

Expand Down
14 changes: 10 additions & 4 deletions tests/fail/weak_memory/racing_mixed_size.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
error: unsupported operation: racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation
error: Undefined Behavior: Race condition detected between (1) 4-byte Atomic Store on thread `<unnamed>` and (2) 2-byte (different-size) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/racing_mixed_size.rs:LL:CC
|
LL | std::intrinsics::atomic_load_relaxed(hi);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 4-byte Atomic Store on thread `<unnamed>` and (2) 2-byte (different-size) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
= note: BACKTRACE:
help: and (1) occurred earlier here
--> $DIR/racing_mixed_size.rs:LL:CC
|
LL | x.store(1, Relaxed);
| ^^^^^^^^^^^^^^^^^^^
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span):
= note: inside closure at $DIR/racing_mixed_size.rs:LL:CC

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
Expand Down
4 changes: 2 additions & 2 deletions tests/fail/weak_memory/racing_mixed_size_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn split_u32_ptr(dword: *const u32) -> *const [u16; 2] {

// Racing mixed size reads may cause two loads to read-from
// the same store but observe different values, which doesn't make
// sense under the formal model so we forbade this.
// sense under the formal model so we forbid this.
pub fn main() {
let x = static_atomic(0);

Expand All @@ -29,7 +29,7 @@ pub fn main() {
let x_split = split_u32_ptr(x_ptr);
unsafe {
let hi = x_split as *const u16 as *const AtomicU16;
(*hi).load(Relaxed); //~ ERROR: imperfectly overlapping
(*hi).load(Relaxed); //~ ERROR: different-size
}
});

Expand Down
14 changes: 10 additions & 4 deletions tests/fail/weak_memory/racing_mixed_size_read.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
error: unsupported operation: racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation
error: Undefined Behavior: Race condition detected between (1) 4-byte Atomic Load on thread `<unnamed>` and (2) 2-byte (different-size) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
--> $DIR/racing_mixed_size_read.rs:LL:CC
|
LL | (*hi).load(Relaxed);
| ^^^^^^^^^^^^^^^^^^^ racy imperfectly overlapping atomic access is not possible in the C++20 memory model, and not supported by Miri's weak memory emulation
| ^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 4-byte Atomic Load on thread `<unnamed>` and (2) 2-byte (different-size) Atomic Load on thread `<unnamed>` at ALLOC. (2) just happened here
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
= note: BACKTRACE:
help: and (1) occurred earlier here
--> $DIR/racing_mixed_size_read.rs:LL:CC
|
LL | x.load(Relaxed);
| ^^^^^^^^^^^^^^^
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span):
= note: inside closure at $DIR/racing_mixed_size_read.rs:LL:CC

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
Expand Down
Loading

0 comments on commit f36a7d1

Please sign in to comment.