Skip to content

Commit 2d610f7

Browse files
committed
Report retags as distinct from real memory accesses for data races
1 parent 6d9549f commit 2d610f7

14 files changed

+202
-89
lines changed

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

+15-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::borrow_tracker::{
1818
stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder},
1919
GlobalStateInner, ProtectorKind,
2020
};
21+
use crate::concurrency::data_race::{NaReadType, NaWriteType};
2122
use crate::*;
2223

2324
use diagnostics::{RetagCause, RetagInfo};
@@ -751,7 +752,13 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
751752
assert_eq!(access, AccessKind::Write);
752753
// Make sure the data race model also knows about this.
753754
if let Some(data_race) = alloc_extra.data_race.as_mut() {
754-
data_race.write(alloc_id, range, machine)?;
755+
data_race.write(
756+
alloc_id,
757+
range,
758+
NaWriteType::Retag,
759+
Some(place.layout.ty),
760+
machine,
761+
)?;
755762
}
756763
}
757764
}
@@ -794,7 +801,13 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
794801
assert_eq!(access, AccessKind::Read);
795802
// Make sure the data race model also knows about this.
796803
if let Some(data_race) = alloc_extra.data_race.as_ref() {
797-
data_race.read(alloc_id, range, &this.machine)?;
804+
data_race.read(
805+
alloc_id,
806+
range,
807+
NaReadType::Retag,
808+
Some(place.layout.ty),
809+
&this.machine,
810+
)?;
798811
}
799812
}
800813
Ok(())

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ use rustc_middle::{
99
use rustc_span::def_id::DefId;
1010
use rustc_target::abi::{Abi, Size};
1111

12-
use crate::borrow_tracker::{GlobalState, GlobalStateInner, ProtectorKind};
1312
use crate::*;
13+
use crate::{
14+
borrow_tracker::{GlobalState, GlobalStateInner, ProtectorKind},
15+
concurrency::data_race::NaReadType,
16+
};
1417

1518
pub mod diagnostics;
1619
mod perms;
@@ -312,7 +315,13 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
312315
// Also inform the data race model (but only if any bytes are actually affected).
313316
if range.size.bytes() > 0 {
314317
if let Some(data_race) = alloc_extra.data_race.as_ref() {
315-
data_race.read(alloc_id, range, &this.machine)?;
318+
data_race.read(
319+
alloc_id,
320+
range,
321+
NaReadType::Retag,
322+
Some(place.layout.ty),
323+
&this.machine,
324+
)?;
316325
}
317326
}
318327

src/tools/miri/src/concurrency/data_race.rs

+79-62
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use std::{
4949
use rustc_ast::Mutability;
5050
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
5151
use rustc_index::{Idx, IndexVec};
52-
use rustc_middle::mir;
52+
use rustc_middle::{mir, ty::Ty};
5353
use rustc_span::Span;
5454
use rustc_target::abi::{Align, HasDataLayout, Size};
5555

@@ -200,18 +200,38 @@ enum AtomicAccessType {
200200
Rmw,
201201
}
202202

203-
/// Type of write operation: allocating memory
204-
/// non-atomic writes and deallocating memory
205-
/// are all treated as writes for the purpose
206-
/// of the data-race detector.
203+
/// Type of a non-atomic read operation.
207204
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
208-
enum NaWriteType {
205+
pub enum NaReadType {
206+
/// Standard unsynchronized write.
207+
Read,
208+
209+
// An implicit read generated by a retag.
210+
Retag,
211+
}
212+
213+
impl NaReadType {
214+
fn description(self) -> &'static str {
215+
match self {
216+
NaReadType::Read => "non-atomic read",
217+
NaReadType::Retag => "retag read",
218+
}
219+
}
220+
}
221+
222+
/// Type of a non-atomic write operation: allocating memory, non-atomic writes, and
223+
/// deallocating memory are all treated as writes for the purpose of the data-race detector.
224+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
225+
pub enum NaWriteType {
209226
/// Allocate memory.
210227
Allocate,
211228

212229
/// Standard unsynchronized write.
213230
Write,
214231

232+
// An implicit write generated by a retag.
233+
Retag,
234+
215235
/// Deallocate memory.
216236
/// Note that when memory is deallocated first, later non-atomic accesses
217237
/// will be reported as use-after-free, not as data races.
@@ -224,44 +244,64 @@ impl NaWriteType {
224244
match self {
225245
NaWriteType::Allocate => "creating a new allocation",
226246
NaWriteType::Write => "non-atomic write",
247+
NaWriteType::Retag => "retag write",
227248
NaWriteType::Deallocate => "deallocation",
228249
}
229250
}
230251
}
231252

232253
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
233254
enum AccessType {
234-
NaRead,
255+
NaRead(NaReadType),
235256
NaWrite(NaWriteType),
236257
AtomicLoad,
237258
AtomicStore,
238259
AtomicRmw,
239260
}
240261

241262
impl AccessType {
242-
fn description(self) -> &'static str {
243-
match self {
244-
AccessType::NaRead => "non-atomic read",
263+
fn description(self, ty: Option<Ty<'_>>, size: Option<Size>) -> String {
264+
let mut msg = String::new();
265+
266+
if let Some(size) = size {
267+
msg.push_str(&format!("{}-byte {}", size.bytes(), msg))
268+
}
269+
270+
msg.push_str(match self {
271+
AccessType::NaRead(w) => w.description(),
245272
AccessType::NaWrite(w) => w.description(),
246273
AccessType::AtomicLoad => "atomic load",
247274
AccessType::AtomicStore => "atomic store",
248275
AccessType::AtomicRmw => "atomic read-modify-write",
276+
});
277+
278+
if let Some(ty) = ty {
279+
msg.push_str(&format!(" of type `{}`", ty));
249280
}
281+
282+
msg
250283
}
251284

252285
fn is_atomic(self) -> bool {
253286
match self {
254287
AccessType::AtomicLoad | AccessType::AtomicStore | AccessType::AtomicRmw => true,
255-
AccessType::NaRead | AccessType::NaWrite(_) => false,
288+
AccessType::NaRead(_) | AccessType::NaWrite(_) => false,
256289
}
257290
}
258291

259292
fn is_read(self) -> bool {
260293
match self {
261-
AccessType::AtomicLoad | AccessType::NaRead => true,
294+
AccessType::AtomicLoad | AccessType::NaRead(_) => true,
262295
AccessType::NaWrite(_) | AccessType::AtomicStore | AccessType::AtomicRmw => false,
263296
}
264297
}
298+
299+
fn is_retag(self) -> bool {
300+
matches!(
301+
self,
302+
AccessType::NaRead(NaReadType::Retag) | AccessType::NaWrite(NaWriteType::Retag)
303+
)
304+
}
265305
}
266306

267307
/// Memory Cell vector clock metadata
@@ -502,12 +542,14 @@ impl MemoryCellClocks {
502542
&mut self,
503543
thread_clocks: &mut ThreadClockSet,
504544
index: VectorIdx,
545+
read_type: NaReadType,
505546
current_span: Span,
506547
) -> Result<(), DataRace> {
507548
trace!("Unsynchronized read with vectors: {:#?} :: {:#?}", self, thread_clocks);
508549
if !current_span.is_dummy() {
509550
thread_clocks.clock[index].span = current_span;
510551
}
552+
thread_clocks.clock[index].set_read_type(read_type);
511553
if self.write_was_before(&thread_clocks.clock) {
512554
let race_free = if let Some(atomic) = self.atomic() {
513555
// We must be ordered-after all atomic accesses, reads and writes.
@@ -875,7 +917,8 @@ impl VClockAlloc {
875917
/// This finds the two racing threads and the type
876918
/// of data-race that occurred. This will also
877919
/// return info about the memory location the data-race
878-
/// occurred in.
920+
/// occurred in. The `ty` parameter is used for diagnostics, letting
921+
/// the user know which type was involved in the access.
879922
#[cold]
880923
#[inline(never)]
881924
fn report_data_race<'tcx>(
@@ -885,6 +928,7 @@ impl VClockAlloc {
885928
access: AccessType,
886929
access_size: Size,
887930
ptr_dbg: Pointer<AllocId>,
931+
ty: Option<Ty<'_>>,
888932
) -> InterpResult<'tcx> {
889933
let (current_index, current_clocks) = global.current_thread_state(thread_mgr);
890934
let mut other_size = None; // if `Some`, this was a size-mismatch race
@@ -908,7 +952,7 @@ impl VClockAlloc {
908952
write_clock = mem_clocks.write();
909953
(AccessType::NaWrite(mem_clocks.write_type), mem_clocks.write.0, &write_clock)
910954
} else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &current_clocks.clock) {
911-
(AccessType::NaRead, idx, &mem_clocks.read)
955+
(AccessType::NaRead(mem_clocks.read[idx].read_type()), idx, &mem_clocks.read)
912956
// Finally, mixed-size races.
913957
} else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != access_size {
914958
// This is only a race if we are not synchronized with all atomic accesses, so find
@@ -950,37 +994,33 @@ impl VClockAlloc {
950994
Err(err_machine_stop!(TerminationInfo::DataRace {
951995
involves_non_atomic,
952996
extra,
997+
retag_explain: access.is_retag() || other_access.is_retag(),
953998
ptr: ptr_dbg,
954999
op1: RacingOp {
955-
action: if let Some(other_size) = other_size {
956-
format!("{}-byte {}", other_size.bytes(), other_access.description())
957-
} else {
958-
other_access.description().to_owned()
959-
},
1000+
action: other_access.description(None, other_size),
9601001
thread_info: other_thread_info,
9611002
span: other_clock.as_slice()[other_thread.index()].span_data(),
9621003
},
9631004
op2: RacingOp {
964-
action: if other_size.is_some() {
965-
format!("{}-byte {}", access_size.bytes(), access.description())
966-
} else {
967-
access.description().to_owned()
968-
},
1005+
action: access.description(ty, other_size.map(|_| access_size)),
9691006
thread_info: current_thread_info,
9701007
span: current_clocks.clock.as_slice()[current_index.index()].span_data(),
9711008
},
9721009
}))?
9731010
}
9741011

975-
/// Detect data-races for an unsynchronized read operation, will not perform
1012+
/// Detect data-races for an unsynchronized read operation. It will not perform
9761013
/// data-race detection if `race_detecting()` is false, either due to no threads
9771014
/// being created or if it is temporarily disabled during a racy read or write
9781015
/// operation for which data-race detection is handled separately, for example
979-
/// atomic read operations.
1016+
/// atomic read operations. The `ty` parameter is used for diagnostics, letting
1017+
/// the user know which type was read.
9801018
pub fn read<'tcx>(
9811019
&self,
9821020
alloc_id: AllocId,
9831021
access_range: AllocRange,
1022+
read_type: NaReadType,
1023+
ty: Option<Ty<'_>>,
9841024
machine: &MiriMachine<'_, '_>,
9851025
) -> InterpResult<'tcx> {
9861026
let current_span = machine.current_span();
@@ -992,17 +1032,18 @@ impl VClockAlloc {
9921032
alloc_ranges.iter_mut(access_range.start, access_range.size)
9931033
{
9941034
if let Err(DataRace) =
995-
mem_clocks.read_race_detect(&mut thread_clocks, index, current_span)
1035+
mem_clocks.read_race_detect(&mut thread_clocks, index, read_type, current_span)
9961036
{
9971037
drop(thread_clocks);
9981038
// Report data-race.
9991039
return Self::report_data_race(
10001040
global,
10011041
&machine.threads,
10021042
mem_clocks,
1003-
AccessType::NaRead,
1043+
AccessType::NaRead(read_type),
10041044
access_range.size,
10051045
Pointer::new(alloc_id, Size::from_bytes(mem_clocks_range.start)),
1046+
ty,
10061047
);
10071048
}
10081049
}
@@ -1012,12 +1053,17 @@ impl VClockAlloc {
10121053
}
10131054
}
10141055

1015-
// Shared code for detecting data-races on unique access to a section of memory
1016-
fn unique_access<'tcx>(
1056+
/// Detect data-races for an unsynchronized write operation. It will not perform
1057+
/// data-race detection if `race_detecting()` is false, either due to no threads
1058+
/// being created or if it is temporarily disabled during a racy read or write
1059+
/// operation. The `ty` parameter is used for diagnostics, letting
1060+
/// the user know which type was written.
1061+
pub fn write<'tcx>(
10171062
&mut self,
10181063
alloc_id: AllocId,
10191064
access_range: AllocRange,
10201065
write_type: NaWriteType,
1066+
ty: Option<Ty<'_>>,
10211067
machine: &mut MiriMachine<'_, '_>,
10221068
) -> InterpResult<'tcx> {
10231069
let current_span = machine.current_span();
@@ -1042,6 +1088,7 @@ impl VClockAlloc {
10421088
AccessType::NaWrite(write_type),
10431089
access_range.size,
10441090
Pointer::new(alloc_id, Size::from_bytes(mem_clocks_range.start)),
1091+
ty,
10451092
);
10461093
}
10471094
}
@@ -1050,37 +1097,6 @@ impl VClockAlloc {
10501097
Ok(())
10511098
}
10521099
}
1053-
1054-
/// Detect data-races for an unsynchronized write operation, will not perform
1055-
/// data-race threads if `race_detecting()` is false, either due to no threads
1056-
/// being created or if it is temporarily disabled during a racy read or write
1057-
/// operation
1058-
pub fn write<'tcx>(
1059-
&mut self,
1060-
alloc_id: AllocId,
1061-
range: AllocRange,
1062-
machine: &mut MiriMachine<'_, '_>,
1063-
) -> InterpResult<'tcx> {
1064-
self.unique_access(alloc_id, range, NaWriteType::Write, machine)
1065-
}
1066-
1067-
/// Detect data-races for an unsynchronized deallocate operation, will not perform
1068-
/// data-race threads if `race_detecting()` is false, either due to no threads
1069-
/// being created or if it is temporarily disabled during a racy read or write
1070-
/// operation
1071-
pub fn deallocate<'tcx>(
1072-
&mut self,
1073-
alloc_id: AllocId,
1074-
size: Size,
1075-
machine: &mut MiriMachine<'_, '_>,
1076-
) -> InterpResult<'tcx> {
1077-
self.unique_access(
1078-
alloc_id,
1079-
alloc_range(Size::ZERO, size),
1080-
NaWriteType::Deallocate,
1081-
machine,
1082-
)
1083-
}
10841100
}
10851101

10861102
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {}
@@ -1279,7 +1295,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
12791295
let alloc_meta = this.get_alloc_extra(alloc_id)?.data_race.as_ref().unwrap();
12801296
trace!(
12811297
"Atomic op({}) with ordering {:?} on {:?} (size={})",
1282-
access.description(),
1298+
access.description(None, None),
12831299
&atomic,
12841300
place.ptr(),
12851301
size.bytes()
@@ -1307,6 +1323,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
13071323
alloc_id,
13081324
Size::from_bytes(mem_clocks_range.start),
13091325
),
1326+
None,
13101327
)
13111328
.map(|_| true);
13121329
}

0 commit comments

Comments
 (0)