1
Fork 0

interpret: add a version of run_for_validation for &self

This commit is contained in:
Ralf Jung 2025-04-01 15:11:10 +02:00
parent ed201574c5
commit e638ba69f0
3 changed files with 34 additions and 13 deletions

View file

@ -8,8 +8,9 @@
use std::assert_matches::assert_matches; use std::assert_matches::assert_matches;
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
use std::cell::Cell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::{fmt, mem, ptr}; use std::{fmt, ptr};
use rustc_abi::{Align, HasDataLayout, Size}; use rustc_abi::{Align, HasDataLayout, Size};
use rustc_ast::Mutability; use rustc_ast::Mutability;
@ -131,7 +132,7 @@ pub struct Memory<'tcx, M: Machine<'tcx>> {
/// This stores whether we are currently doing reads purely for the purpose of validation. /// This stores whether we are currently doing reads purely for the purpose of validation.
/// Those reads do not trigger the machine's hooks for memory reads. /// Those reads do not trigger the machine's hooks for memory reads.
/// Needless to say, this must only be set with great care! /// Needless to say, this must only be set with great care!
validation_in_progress: bool, validation_in_progress: Cell<bool>,
} }
/// A reference to some allocation that was already bounds-checked for the given region /// A reference to some allocation that was already bounds-checked for the given region
@ -158,7 +159,7 @@ impl<'tcx, M: Machine<'tcx>> Memory<'tcx, M> {
alloc_map: M::MemoryMap::default(), alloc_map: M::MemoryMap::default(),
extra_fn_ptr_map: FxIndexMap::default(), extra_fn_ptr_map: FxIndexMap::default(),
dead_alloc_map: FxIndexMap::default(), dead_alloc_map: FxIndexMap::default(),
validation_in_progress: false, validation_in_progress: Cell::new(false),
} }
} }
@ -715,7 +716,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// We want to call the hook on *all* accesses that involve an AllocId, including zero-sized // We want to call the hook on *all* accesses that involve an AllocId, including zero-sized
// accesses. That means we cannot rely on the closure above or the `Some` branch below. We // accesses. That means we cannot rely on the closure above or the `Some` branch below. We
// do this after `check_and_deref_ptr` to ensure some basic sanity has already been checked. // do this after `check_and_deref_ptr` to ensure some basic sanity has already been checked.
if !self.memory.validation_in_progress { if !self.memory.validation_in_progress.get() {
if let Ok((alloc_id, ..)) = self.ptr_try_get_alloc_id(ptr, size_i64) { if let Ok((alloc_id, ..)) = self.ptr_try_get_alloc_id(ptr, size_i64) {
M::before_alloc_read(self, alloc_id)?; M::before_alloc_read(self, alloc_id)?;
} }
@ -723,7 +724,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
if let Some((alloc_id, offset, prov, alloc)) = ptr_and_alloc { if let Some((alloc_id, offset, prov, alloc)) = ptr_and_alloc {
let range = alloc_range(offset, size); let range = alloc_range(offset, size);
if !self.memory.validation_in_progress { if !self.memory.validation_in_progress.get() {
M::before_memory_read( M::before_memory_read(
self.tcx, self.tcx,
&self.machine, &self.machine,
@ -801,7 +802,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
) -> InterpResult<'tcx, Option<AllocRefMut<'a, 'tcx, M::Provenance, M::AllocExtra, M::Bytes>>> ) -> InterpResult<'tcx, Option<AllocRefMut<'a, 'tcx, M::Provenance, M::AllocExtra, M::Bytes>>>
{ {
let tcx = self.tcx; let tcx = self.tcx;
let validation_in_progress = self.memory.validation_in_progress; let validation_in_progress = self.memory.validation_in_progress.get();
let size_i64 = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes let size_i64 = i64::try_from(size.bytes()).unwrap(); // it would be an error to even ask for more than isize::MAX bytes
let ptr_and_alloc = Self::check_and_deref_ptr( let ptr_and_alloc = Self::check_and_deref_ptr(
@ -1087,23 +1088,43 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
/// ///
/// We do this so Miri's allocation access tracking does not show the validation /// We do this so Miri's allocation access tracking does not show the validation
/// reads as spurious accesses. /// reads as spurious accesses.
pub fn run_for_validation<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { pub fn run_for_validation_mut<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
// This deliberately uses `==` on `bool` to follow the pattern // This deliberately uses `==` on `bool` to follow the pattern
// `assert!(val.replace(new) == old)`. // `assert!(val.replace(new) == old)`.
assert!( assert!(
mem::replace(&mut self.memory.validation_in_progress, true) == false, self.memory.validation_in_progress.replace(true) == false,
"`validation_in_progress` was already set" "`validation_in_progress` was already set"
); );
let res = f(self); let res = f(self);
assert!( assert!(
mem::replace(&mut self.memory.validation_in_progress, false) == true, self.memory.validation_in_progress.replace(false) == true,
"`validation_in_progress` was unset by someone else"
);
res
}
/// Runs the closure in "validation" mode, which means the machine's memory read hooks will be
/// suppressed. Needless to say, this must only be set with great care! Cannot be nested.
///
/// We do this so Miri's allocation access tracking does not show the validation
/// reads as spurious accesses.
pub fn run_for_validation_ref<R>(&self, f: impl FnOnce(&Self) -> R) -> R {
// This deliberately uses `==` on `bool` to follow the pattern
// `assert!(val.replace(new) == old)`.
assert!(
self.memory.validation_in_progress.replace(true) == false,
"`validation_in_progress` was already set"
);
let res = f(self);
assert!(
self.memory.validation_in_progress.replace(false) == true,
"`validation_in_progress` was unset by someone else" "`validation_in_progress` was unset by someone else"
); );
res res
} }
pub(super) fn validation_in_progress(&self) -> bool { pub(super) fn validation_in_progress(&self) -> bool {
self.memory.validation_in_progress self.memory.validation_in_progress.get()
} }
} }
@ -1375,7 +1396,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
}; };
let src_alloc = self.get_alloc_raw(src_alloc_id)?; let src_alloc = self.get_alloc_raw(src_alloc_id)?;
let src_range = alloc_range(src_offset, size); let src_range = alloc_range(src_offset, size);
assert!(!self.memory.validation_in_progress, "we can't be copying during validation"); assert!(!self.memory.validation_in_progress.get(), "we can't be copying during validation");
// For the overlapping case, it is crucial that we trigger the read hook // For the overlapping case, it is crucial that we trigger the read hook
// before the write hook -- the aliasing model cares about the order. // before the write hook -- the aliasing model cares about the order.
M::before_memory_read( M::before_memory_read(

View file

@ -1322,7 +1322,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
trace!("validate_operand_internal: {:?}, {:?}", *val, val.layout.ty); trace!("validate_operand_internal: {:?}, {:?}", *val, val.layout.ty);
// Run the visitor. // Run the visitor.
self.run_for_validation(|ecx| { self.run_for_validation_mut(|ecx| {
let reset_padding = reset_provenance_and_padding && { let reset_padding = reset_provenance_and_padding && {
// Check if `val` is actually stored in memory. If not, padding is not even // Check if `val` is actually stored in memory. If not, padding is not even
// represented and we need not reset it. // represented and we need not reset it.

View file

@ -717,7 +717,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
// The program didn't actually do a read, so suppress the memory access hooks. // The program didn't actually do a read, so suppress the memory access hooks.
// This is also a very special exception where we just ignore an error -- if this read // This is also a very special exception where we just ignore an error -- if this read
// was UB e.g. because the memory is uninitialized, we don't want to know! // was UB e.g. because the memory is uninitialized, we don't want to know!
let old_val = this.run_for_validation(|this| this.read_scalar(dest)).discard_err(); let old_val = this.run_for_validation_mut(|this| this.read_scalar(dest)).discard_err();
this.allow_data_races_mut(move |this| this.write_scalar(val, dest))?; this.allow_data_races_mut(move |this| this.write_scalar(val, dest))?;
this.validate_atomic_store(dest, atomic)?; this.validate_atomic_store(dest, atomic)?;
this.buffered_atomic_write(val, dest, atomic, old_val) this.buffered_atomic_write(val, dest, atomic, old_val)