Skip to content

Commit 6ac598a

Browse files
authored
Rollup merge of #129828 - RalfJung:miri-data-race, r=saethlin
miri: treat non-memory local variables properly for data race detection Fixes rust-lang/miri#3242 Miri has an optimization where some local variables are not represented in memory until something forces them to be stored in memory (most notably, creating a pointer/reference to the local will do that). However, for a subsystem triggering on memory accesses -- such as the data race detector -- this means that the memory access seems to happen only when the local is moved to memory, instead of at the time that it actually happens. This can lead to UB reports in programs that do not actually have UB. This PR fixes that by adding machine hooks for reads and writes to such efficiently represented local variables. The data race system tracks those very similar to how it would track reads and writes to addressable memory, and when a local is moved to memory, the clocks get overwritten with the information stored for the local.
2 parents df3cf91 + 339f68b commit 6ac598a

16 files changed

+534
-123
lines changed

Diff for: compiler/rustc_const_eval/src/interpret/machine.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -540,10 +540,29 @@ pub trait Machine<'tcx>: Sized {
540540
Ok(ReturnAction::Normal)
541541
}
542542

543+
/// Called immediately after an "immediate" local variable is read
544+
/// (i.e., this is called for reads that do not end up accessing addressable memory).
545+
#[inline(always)]
546+
fn after_local_read(_ecx: &InterpCx<'tcx, Self>, _local: mir::Local) -> InterpResult<'tcx> {
547+
Ok(())
548+
}
549+
550+
/// Called immediately after an "immediate" local variable is assigned a new value
551+
/// (i.e., this is called for writes that do not end up in memory).
552+
/// `storage_live` indicates whether this is the initial write upon `StorageLive`.
553+
#[inline(always)]
554+
fn after_local_write(
555+
_ecx: &mut InterpCx<'tcx, Self>,
556+
_local: mir::Local,
557+
_storage_live: bool,
558+
) -> InterpResult<'tcx> {
559+
Ok(())
560+
}
561+
543562
/// Called immediately after actual memory was allocated for a local
544563
/// but before the local's stack frame is updated to point to that memory.
545564
#[inline(always)]
546-
fn after_local_allocated(
565+
fn after_local_moved_to_memory(
547566
_ecx: &mut InterpCx<'tcx, Self>,
548567
_local: mir::Local,
549568
_mplace: &MPlaceTy<'tcx, Self::Provenance>,

Diff for: compiler/rustc_const_eval/src/interpret/memory.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
10461046
);
10471047
res
10481048
}
1049+
1050+
pub(super) fn validation_in_progress(&self) -> bool {
1051+
self.memory.validation_in_progress
1052+
}
10491053
}
10501054

10511055
#[doc(hidden)]

Diff for: compiler/rustc_const_eval/src/interpret/operand.rs

+1
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
697697
if matches!(op, Operand::Immediate(_)) {
698698
assert!(!layout.is_unsized());
699699
}
700+
M::after_local_read(self, local)?;
700701
Ok(OpTy { op, layout })
701702
}
702703

Diff for: compiler/rustc_const_eval/src/interpret/place.rs

+22-9
Original file line numberDiff line numberDiff line change
@@ -500,15 +500,13 @@ where
500500
&self,
501501
local: mir::Local,
502502
) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
503-
// Other parts of the system rely on `Place::Local` never being unsized.
504-
// So we eagerly check here if this local has an MPlace, and if yes we use it.
505503
let frame = self.frame();
506504
let layout = self.layout_of_local(frame, local, None)?;
507505
let place = if layout.is_sized() {
508506
// We can just always use the `Local` for sized values.
509507
Place::Local { local, offset: None, locals_addr: frame.locals_addr() }
510508
} else {
511-
// Unsized `Local` isn't okay (we cannot store the metadata).
509+
// Other parts of the system rely on `Place::Local` never being unsized.
512510
match frame.locals[local].access()? {
513511
Operand::Immediate(_) => bug!(),
514512
Operand::Indirect(mplace) => Place::Ptr(*mplace),
@@ -562,7 +560,10 @@ where
562560
place: &PlaceTy<'tcx, M::Provenance>,
563561
) -> InterpResult<
564562
'tcx,
565-
Either<MPlaceTy<'tcx, M::Provenance>, (&mut Immediate<M::Provenance>, TyAndLayout<'tcx>)>,
563+
Either<
564+
MPlaceTy<'tcx, M::Provenance>,
565+
(&mut Immediate<M::Provenance>, TyAndLayout<'tcx>, mir::Local),
566+
>,
566567
> {
567568
Ok(match place.to_place().as_mplace_or_local() {
568569
Left(mplace) => Left(mplace),
@@ -581,7 +582,7 @@ where
581582
}
582583
Operand::Immediate(local_val) => {
583584
// The local still has the optimized representation.
584-
Right((local_val, layout))
585+
Right((local_val, layout, local))
585586
}
586587
}
587588
}
@@ -643,9 +644,13 @@ where
643644
assert!(dest.layout().is_sized(), "Cannot write unsized immediate data");
644645

645646
match self.as_mplace_or_mutable_local(&dest.to_place())? {
646-
Right((local_val, local_layout)) => {
647+
Right((local_val, local_layout, local)) => {
647648
// Local can be updated in-place.
648649
*local_val = src;
650+
// Call the machine hook (the data race detector needs to know about this write).
651+
if !self.validation_in_progress() {
652+
M::after_local_write(self, local, /*storage_live*/ false)?;
653+
}
649654
// Double-check that the value we are storing and the local fit to each other.
650655
if cfg!(debug_assertions) {
651656
src.assert_matches_abi(local_layout.abi, self);
@@ -714,8 +719,12 @@ where
714719
dest: &impl Writeable<'tcx, M::Provenance>,
715720
) -> InterpResult<'tcx> {
716721
match self.as_mplace_or_mutable_local(&dest.to_place())? {
717-
Right((local_val, _local_layout)) => {
722+
Right((local_val, _local_layout, local)) => {
718723
*local_val = Immediate::Uninit;
724+
// Call the machine hook (the data race detector needs to know about this write).
725+
if !self.validation_in_progress() {
726+
M::after_local_write(self, local, /*storage_live*/ false)?;
727+
}
719728
}
720729
Left(mplace) => {
721730
let Some(mut alloc) = self.get_place_alloc_mut(&mplace)? else {
@@ -734,8 +743,12 @@ where
734743
dest: &impl Writeable<'tcx, M::Provenance>,
735744
) -> InterpResult<'tcx> {
736745
match self.as_mplace_or_mutable_local(&dest.to_place())? {
737-
Right((local_val, _local_layout)) => {
746+
Right((local_val, _local_layout, local)) => {
738747
local_val.clear_provenance()?;
748+
// Call the machine hook (the data race detector needs to know about this write).
749+
if !self.validation_in_progress() {
750+
M::after_local_write(self, local, /*storage_live*/ false)?;
751+
}
739752
}
740753
Left(mplace) => {
741754
let Some(mut alloc) = self.get_place_alloc_mut(&mplace)? else {
@@ -941,7 +954,7 @@ where
941954
mplace.mplace,
942955
)?;
943956
}
944-
M::after_local_allocated(self, local, &mplace)?;
957+
M::after_local_moved_to_memory(self, local, &mplace)?;
945958
// Now we can call `access_mut` again, asserting it goes well, and actually
946959
// overwrite things. This points to the entire allocation, not just the part
947960
// the place refers to, i.e. we do this before we apply `offset`.

Diff for: compiler/rustc_const_eval/src/interpret/stack.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
534534
let dest_place = self.allocate_dyn(layout, MemoryKind::Stack, meta)?;
535535
Operand::Indirect(*dest_place.mplace())
536536
} else {
537-
assert!(!meta.has_meta()); // we're dropping the metadata
538537
// Just make this an efficient immediate.
538+
assert!(!meta.has_meta()); // we're dropping the metadata
539+
// Make sure the machine knows this "write" is happening. (This is important so that
540+
// races involving local variable allocation can be detected by Miri.)
541+
M::after_local_write(self, local, /*storage_live*/ true)?;
539542
// Note that not calling `layout_of` here does have one real consequence:
540543
// if the type is too big, we'll only notice this when the local is actually initialized,
541544
// which is a bit too late -- we should ideally notice this already here, when the memory

0 commit comments

Comments
 (0)