Miri: add a flag to do recursive validity checking
This commit is contained in:
parent
2cec7a85ed
commit
21c02517c3
14 changed files with 186 additions and 107 deletions
|
@ -396,7 +396,7 @@ fn const_validate_mplace<'tcx>(
|
||||||
let alloc_id = mplace.ptr().provenance.unwrap().alloc_id();
|
let alloc_id = mplace.ptr().provenance.unwrap().alloc_id();
|
||||||
let mut ref_tracking = RefTracking::new(mplace.clone());
|
let mut ref_tracking = RefTracking::new(mplace.clone());
|
||||||
let mut inner = false;
|
let mut inner = false;
|
||||||
while let Some((mplace, path)) = ref_tracking.todo.pop() {
|
while let Some((mplace, path)) = ref_tracking.next() {
|
||||||
let mode = match ecx.tcx.static_mutability(cid.instance.def_id()) {
|
let mode = match ecx.tcx.static_mutability(cid.instance.def_id()) {
|
||||||
_ if cid.promoted.is_some() => CtfeValidationMode::Promoted,
|
_ if cid.promoted.is_some() => CtfeValidationMode::Promoted,
|
||||||
Some(mutbl) => CtfeValidationMode::Static { mutbl }, // a `static`
|
Some(mutbl) => CtfeValidationMode::Static { mutbl }, // a `static`
|
||||||
|
|
|
@ -165,6 +165,13 @@ pub trait Machine<'tcx>: Sized {
|
||||||
|
|
||||||
/// Whether to enforce the validity invariant for a specific layout.
|
/// Whether to enforce the validity invariant for a specific layout.
|
||||||
fn enforce_validity(ecx: &InterpCx<'tcx, Self>, layout: TyAndLayout<'tcx>) -> bool;
|
fn enforce_validity(ecx: &InterpCx<'tcx, Self>, layout: TyAndLayout<'tcx>) -> bool;
|
||||||
|
/// Whether to enforce the validity invariant *recursively*.
|
||||||
|
fn enforce_validity_recursively(
|
||||||
|
_ecx: &InterpCx<'tcx, Self>,
|
||||||
|
_layout: TyAndLayout<'tcx>,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether function calls should be [ABI](CallAbi)-checked.
|
/// Whether function calls should be [ABI](CallAbi)-checked.
|
||||||
fn enforce_abi(_ecx: &InterpCx<'tcx, Self>) -> bool {
|
fn enforce_abi(_ecx: &InterpCx<'tcx, Self>) -> bool {
|
||||||
|
|
|
@ -1006,8 +1006,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the close in "validation" mode, which means the machine's memory read hooks will be
|
/// 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.
|
/// 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(super) fn run_for_validation<R>(&self, f: impl FnOnce() -> R) -> R {
|
pub(super) fn run_for_validation<R>(&self, f: impl FnOnce() -> 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)`.
|
||||||
|
|
|
@ -572,7 +572,10 @@ where
|
||||||
|
|
||||||
if M::enforce_validity(self, dest.layout()) {
|
if M::enforce_validity(self, dest.layout()) {
|
||||||
// Data got changed, better make sure it matches the type!
|
// Data got changed, better make sure it matches the type!
|
||||||
self.validate_operand(&dest.to_op(self)?)?;
|
self.validate_operand(
|
||||||
|
&dest.to_op(self)?,
|
||||||
|
M::enforce_validity_recursively(self, dest.layout()),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -811,7 +814,10 @@ where
|
||||||
// Generally for transmutation, data must be valid both at the old and new type.
|
// Generally for transmutation, data must be valid both at the old and new type.
|
||||||
// But if the types are the same, the 2nd validation below suffices.
|
// But if the types are the same, the 2nd validation below suffices.
|
||||||
if src.layout().ty != dest.layout().ty && M::enforce_validity(self, src.layout()) {
|
if src.layout().ty != dest.layout().ty && M::enforce_validity(self, src.layout()) {
|
||||||
self.validate_operand(&src.to_op(self)?)?;
|
self.validate_operand(
|
||||||
|
&src.to_op(self)?,
|
||||||
|
M::enforce_validity_recursively(self, src.layout()),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the actual copy.
|
// Do the actual copy.
|
||||||
|
@ -819,7 +825,10 @@ where
|
||||||
|
|
||||||
if validate_dest && M::enforce_validity(self, dest.layout()) {
|
if validate_dest && M::enforce_validity(self, dest.layout()) {
|
||||||
// Data got changed, better make sure it matches the type!
|
// Data got changed, better make sure it matches the type!
|
||||||
self.validate_operand(&dest.to_op(self)?)?;
|
self.validate_operand(
|
||||||
|
&dest.to_op(self)?,
|
||||||
|
M::enforce_validity_recursively(self, dest.layout()),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -155,8 +155,8 @@ impl CtfeValidationMode {
|
||||||
|
|
||||||
/// State for tracking recursive validation of references
|
/// State for tracking recursive validation of references
|
||||||
pub struct RefTracking<T, PATH = ()> {
|
pub struct RefTracking<T, PATH = ()> {
|
||||||
pub seen: FxHashSet<T>,
|
seen: FxHashSet<T>,
|
||||||
pub todo: Vec<(T, PATH)>,
|
todo: Vec<(T, PATH)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH> {
|
impl<T: Clone + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH> {
|
||||||
|
@ -169,8 +169,11 @@ impl<T: Clone + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH>
|
||||||
ref_tracking_for_consts.seen.insert(op);
|
ref_tracking_for_consts.seen.insert(op);
|
||||||
ref_tracking_for_consts
|
ref_tracking_for_consts
|
||||||
}
|
}
|
||||||
|
pub fn next(&mut self) -> Option<(T, PATH)> {
|
||||||
|
self.todo.pop()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn track(&mut self, op: T, path: impl FnOnce() -> PATH) {
|
fn track(&mut self, op: T, path: impl FnOnce() -> PATH) {
|
||||||
if self.seen.insert(op.clone()) {
|
if self.seen.insert(op.clone()) {
|
||||||
trace!("Recursing below ptr {:#?}", op);
|
trace!("Recursing below ptr {:#?}", op);
|
||||||
let path = path();
|
let path = path();
|
||||||
|
@ -435,88 +438,96 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
|
||||||
if self.ecx.scalar_may_be_null(Scalar::from_maybe_pointer(place.ptr(), self.ecx))? {
|
if self.ecx.scalar_may_be_null(Scalar::from_maybe_pointer(place.ptr(), self.ecx))? {
|
||||||
throw_validation_failure!(self.path, NullPtr { ptr_kind })
|
throw_validation_failure!(self.path, NullPtr { ptr_kind })
|
||||||
}
|
}
|
||||||
// Do not allow pointers to uninhabited types.
|
// Do not allow references to uninhabited types.
|
||||||
if place.layout.abi.is_uninhabited() {
|
if place.layout.abi.is_uninhabited() {
|
||||||
let ty = place.layout.ty;
|
let ty = place.layout.ty;
|
||||||
throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
|
throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
|
||||||
}
|
}
|
||||||
// Recursive checking
|
// Recursive checking
|
||||||
if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
|
if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
|
||||||
// Determine whether this pointer expects to be pointing to something mutable.
|
|
||||||
let ptr_expected_mutbl = match ptr_kind {
|
|
||||||
PointerKind::Box => Mutability::Mut,
|
|
||||||
PointerKind::Ref(mutbl) => {
|
|
||||||
// We do not take into account interior mutability here since we cannot know if
|
|
||||||
// there really is an `UnsafeCell` inside `Option<UnsafeCell>` -- so we check
|
|
||||||
// that in the recursive descent behind this reference (controlled by
|
|
||||||
// `allow_immutable_unsafe_cell`).
|
|
||||||
mutbl
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Proceed recursively even for ZST, no reason to skip them!
|
// Proceed recursively even for ZST, no reason to skip them!
|
||||||
// `!` is a ZST and we want to validate it.
|
// `!` is a ZST and we want to validate it.
|
||||||
if let Ok((alloc_id, _offset, _prov)) = self.ecx.ptr_try_get_alloc_id(place.ptr(), 0) {
|
if let Some(ctfe_mode) = self.ctfe_mode {
|
||||||
let mut skip_recursive_check = false;
|
let mut skip_recursive_check = false;
|
||||||
if let Some(GlobalAlloc::Static(did)) = self.ecx.tcx.try_get_global_alloc(alloc_id)
|
// CTFE imposes restrictions on what references can point to.
|
||||||
|
if let Ok((alloc_id, _offset, _prov)) =
|
||||||
|
self.ecx.ptr_try_get_alloc_id(place.ptr(), 0)
|
||||||
{
|
{
|
||||||
let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else { bug!() };
|
if let Some(GlobalAlloc::Static(did)) =
|
||||||
// Special handling for pointers to statics (irrespective of their type).
|
self.ecx.tcx.try_get_global_alloc(alloc_id)
|
||||||
assert!(!self.ecx.tcx.is_thread_local_static(did));
|
{
|
||||||
assert!(self.ecx.tcx.is_static(did));
|
let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else {
|
||||||
// Mode-specific checks
|
bug!()
|
||||||
match self.ctfe_mode {
|
};
|
||||||
Some(
|
// Special handling for pointers to statics (irrespective of their type).
|
||||||
CtfeValidationMode::Static { .. } | CtfeValidationMode::Promoted { .. },
|
assert!(!self.ecx.tcx.is_thread_local_static(did));
|
||||||
) => {
|
assert!(self.ecx.tcx.is_static(did));
|
||||||
// We skip recursively checking other statics. These statics must be sound by
|
// Mode-specific checks
|
||||||
// themselves, and the only way to get broken statics here is by using
|
match ctfe_mode {
|
||||||
// unsafe code.
|
CtfeValidationMode::Static { .. }
|
||||||
// The reasons we don't check other statics is twofold. For one, in all
|
| CtfeValidationMode::Promoted { .. } => {
|
||||||
// sound cases, the static was already validated on its own, and second, we
|
// We skip recursively checking other statics. These statics must be sound by
|
||||||
// trigger cycle errors if we try to compute the value of the other static
|
// themselves, and the only way to get broken statics here is by using
|
||||||
// and that static refers back to us (potentially through a promoted).
|
// unsafe code.
|
||||||
// This could miss some UB, but that's fine.
|
// The reasons we don't check other statics is twofold. For one, in all
|
||||||
// We still walk nested allocations, as they are fundamentally part of this validation run.
|
// sound cases, the static was already validated on its own, and second, we
|
||||||
// This means we will also recurse into nested statics of *other*
|
// trigger cycle errors if we try to compute the value of the other static
|
||||||
// statics, even though we do not recurse into other statics directly.
|
// and that static refers back to us (potentially through a promoted).
|
||||||
// That's somewhat inconsistent but harmless.
|
// This could miss some UB, but that's fine.
|
||||||
skip_recursive_check = !nested;
|
// We still walk nested allocations, as they are fundamentally part of this validation run.
|
||||||
}
|
// This means we will also recurse into nested statics of *other*
|
||||||
Some(CtfeValidationMode::Const { .. }) => {
|
// statics, even though we do not recurse into other statics directly.
|
||||||
// We can't recursively validate `extern static`, so we better reject them.
|
// That's somewhat inconsistent but harmless.
|
||||||
if self.ecx.tcx.is_foreign_item(did) {
|
skip_recursive_check = !nested;
|
||||||
throw_validation_failure!(self.path, ConstRefToExtern);
|
}
|
||||||
|
CtfeValidationMode::Const { .. } => {
|
||||||
|
// We can't recursively validate `extern static`, so we better reject them.
|
||||||
|
if self.ecx.tcx.is_foreign_item(did) {
|
||||||
|
throw_validation_failure!(self.path, ConstRefToExtern);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Dangling and Mutability check.
|
// Dangling and Mutability check.
|
||||||
let (size, _align, alloc_kind) = self.ecx.get_alloc_info(alloc_id);
|
let (size, _align, alloc_kind) = self.ecx.get_alloc_info(alloc_id);
|
||||||
if alloc_kind == AllocKind::Dead {
|
if alloc_kind == AllocKind::Dead {
|
||||||
// This can happen for zero-sized references. We can't have *any* references to non-existing
|
// This can happen for zero-sized references. We can't have *any* references to
|
||||||
// allocations though, interning rejects them all as the rest of rustc isn't happy with them...
|
// non-existing allocations in const-eval though, interning rejects them all as
|
||||||
// so we throw an error, even though this isn't really UB.
|
// the rest of rustc isn't happy with them... so we throw an error, even though
|
||||||
// A potential future alternative would be to resurrect this as a zero-sized allocation
|
// this isn't really UB.
|
||||||
// (which codegen will then compile to an aligned dummy pointer anyway).
|
// A potential future alternative would be to resurrect this as a zero-sized allocation
|
||||||
throw_validation_failure!(self.path, DanglingPtrUseAfterFree { ptr_kind });
|
// (which codegen will then compile to an aligned dummy pointer anyway).
|
||||||
}
|
throw_validation_failure!(self.path, DanglingPtrUseAfterFree { ptr_kind });
|
||||||
// If this allocation has size zero, there is no actual mutability here.
|
|
||||||
if size != Size::ZERO {
|
|
||||||
let alloc_actual_mutbl = mutability(self.ecx, alloc_id);
|
|
||||||
// Mutable pointer to immutable memory is no good.
|
|
||||||
if ptr_expected_mutbl == Mutability::Mut
|
|
||||||
&& alloc_actual_mutbl == Mutability::Not
|
|
||||||
{
|
|
||||||
throw_validation_failure!(self.path, MutableRefToImmutable);
|
|
||||||
}
|
}
|
||||||
// In a const, everything must be completely immutable.
|
// If this allocation has size zero, there is no actual mutability here.
|
||||||
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) {
|
if size != Size::ZERO {
|
||||||
|
// Determine whether this pointer expects to be pointing to something mutable.
|
||||||
|
let ptr_expected_mutbl = match ptr_kind {
|
||||||
|
PointerKind::Box => Mutability::Mut,
|
||||||
|
PointerKind::Ref(mutbl) => {
|
||||||
|
// We do not take into account interior mutability here since we cannot know if
|
||||||
|
// there really is an `UnsafeCell` inside `Option<UnsafeCell>` -- so we check
|
||||||
|
// that in the recursive descent behind this reference (controlled by
|
||||||
|
// `allow_immutable_unsafe_cell`).
|
||||||
|
mutbl
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Determine what it actually points to.
|
||||||
|
let alloc_actual_mutbl = mutability(self.ecx, alloc_id);
|
||||||
|
// Mutable pointer to immutable memory is no good.
|
||||||
if ptr_expected_mutbl == Mutability::Mut
|
if ptr_expected_mutbl == Mutability::Mut
|
||||||
|| alloc_actual_mutbl == Mutability::Mut
|
&& alloc_actual_mutbl == Mutability::Not
|
||||||
{
|
{
|
||||||
throw_validation_failure!(self.path, ConstRefToMutable);
|
throw_validation_failure!(self.path, MutableRefToImmutable);
|
||||||
|
}
|
||||||
|
// In a const, everything must be completely immutable.
|
||||||
|
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) {
|
||||||
|
if ptr_expected_mutbl == Mutability::Mut
|
||||||
|
|| alloc_actual_mutbl == Mutability::Mut
|
||||||
|
{
|
||||||
|
throw_validation_failure!(self.path, ConstRefToMutable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -524,6 +535,15 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
|
||||||
if skip_recursive_check {
|
if skip_recursive_check {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// This is not CTFE, so it's Miri with recursive checking.
|
||||||
|
// FIXME: we do *not* check behind boxes, since creating a new box first creates it uninitialized
|
||||||
|
// and then puts the value in there, so briefly we have a box with uninit contents.
|
||||||
|
// FIXME: should we also skip `UnsafeCell` behind shared references? Currently that is not
|
||||||
|
// needed since validation reads bypass Stacked Borrows and data race checks.
|
||||||
|
if matches!(ptr_kind, PointerKind::Box) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let path = &self.path;
|
let path = &self.path;
|
||||||
ref_tracking.track(place, || {
|
ref_tracking.track(place, || {
|
||||||
|
@ -1072,11 +1092,23 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
/// `op` is assumed to cover valid memory if it is an indirect operand.
|
/// `op` is assumed to cover valid memory if it is an indirect operand.
|
||||||
/// It will error if the bits at the destination do not match the ones described by the layout.
|
/// It will error if the bits at the destination do not match the ones described by the layout.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn validate_operand(&self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
|
pub fn validate_operand(
|
||||||
|
&self,
|
||||||
|
op: &OpTy<'tcx, M::Provenance>,
|
||||||
|
recursive: bool,
|
||||||
|
) -> InterpResult<'tcx> {
|
||||||
// Note that we *could* actually be in CTFE here with `-Zextra-const-ub-checks`, but it's
|
// Note that we *could* actually be in CTFE here with `-Zextra-const-ub-checks`, but it's
|
||||||
// still correct to not use `ctfe_mode`: that mode is for validation of the final constant
|
// still correct to not use `ctfe_mode`: that mode is for validation of the final constant
|
||||||
// value, it rules out things like `UnsafeCell` in awkward places. It also can make checking
|
// value, it rules out things like `UnsafeCell` in awkward places.
|
||||||
// recurse through references which, for now, we don't want here, either.
|
if !recursive {
|
||||||
self.validate_operand_internal(op, vec![], None, None)
|
return self.validate_operand_internal(op, vec![], None, None);
|
||||||
|
}
|
||||||
|
// Do a recursive check.
|
||||||
|
let mut ref_tracking = RefTracking::empty();
|
||||||
|
self.validate_operand_internal(op, vec![], Some(&mut ref_tracking), None)?;
|
||||||
|
while let Some((mplace, path)) = ref_tracking.todo.pop() {
|
||||||
|
self.validate_operand_internal(&mplace.into(), path, Some(&mut ref_tracking), None)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ fn might_permit_raw_init_strict<'tcx>(
|
||||||
// This does *not* actually check that references are dereferenceable, but since all types that
|
// This does *not* actually check that references are dereferenceable, but since all types that
|
||||||
// require dereferenceability also require non-null, we don't actually get any false negatives
|
// require dereferenceability also require non-null, we don't actually get any false negatives
|
||||||
// due to this.
|
// due to this.
|
||||||
Ok(cx.validate_operand(&ot).is_ok())
|
Ok(cx.validate_operand(&ot, /*recursive*/ false).is_ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for
|
/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for
|
||||||
|
|
|
@ -398,6 +398,8 @@ to Miri failing to detect cases of undefined behavior in a program.
|
||||||
application instead of raising an error within the context of Miri (and halting
|
application instead of raising an error within the context of Miri (and halting
|
||||||
execution). Note that code might not expect these operations to ever panic, so
|
execution). Note that code might not expect these operations to ever panic, so
|
||||||
this flag can lead to strange (mis)behavior.
|
this flag can lead to strange (mis)behavior.
|
||||||
|
* `-Zmiri-recursive-validation` is a *highly experimental* flag that makes validity checking
|
||||||
|
recurse below references.
|
||||||
* `-Zmiri-retag-fields[=<all|none|scalar>]` controls when Stacked Borrows retagging recurses into
|
* `-Zmiri-retag-fields[=<all|none|scalar>]` controls when Stacked Borrows retagging recurses into
|
||||||
fields. `all` means it always recurses (the default, and equivalent to `-Zmiri-retag-fields`
|
fields. `all` means it always recurses (the default, and equivalent to `-Zmiri-retag-fields`
|
||||||
without an explicit value), `none` means it never recurses, `scalar` means it only recurses for
|
without an explicit value), `none` means it never recurses, `scalar` means it only recurses for
|
||||||
|
|
|
@ -44,7 +44,7 @@ use rustc_session::config::{CrateType, ErrorOutputType, OptLevel};
|
||||||
use rustc_session::search_paths::PathKind;
|
use rustc_session::search_paths::PathKind;
|
||||||
use rustc_session::{CtfeBacktrace, EarlyDiagCtxt};
|
use rustc_session::{CtfeBacktrace, EarlyDiagCtxt};
|
||||||
|
|
||||||
use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields};
|
use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields, ValidationMode};
|
||||||
|
|
||||||
struct MiriCompilerCalls {
|
struct MiriCompilerCalls {
|
||||||
miri_config: miri::MiriConfig,
|
miri_config: miri::MiriConfig,
|
||||||
|
@ -421,7 +421,9 @@ fn main() {
|
||||||
} else if arg == "--" {
|
} else if arg == "--" {
|
||||||
after_dashdash = true;
|
after_dashdash = true;
|
||||||
} else if arg == "-Zmiri-disable-validation" {
|
} else if arg == "-Zmiri-disable-validation" {
|
||||||
miri_config.validate = false;
|
miri_config.validation = ValidationMode::No;
|
||||||
|
} else if arg == "-Zmiri-recursive-validation" {
|
||||||
|
miri_config.validation = ValidationMode::Deep;
|
||||||
} else if arg == "-Zmiri-disable-stacked-borrows" {
|
} else if arg == "-Zmiri-disable-stacked-borrows" {
|
||||||
miri_config.borrow_tracker = None;
|
miri_config.borrow_tracker = None;
|
||||||
} else if arg == "-Zmiri-tree-borrows" {
|
} else if arg == "-Zmiri-tree-borrows" {
|
||||||
|
|
|
@ -68,7 +68,7 @@ pub enum IsolatedOp {
|
||||||
Allow,
|
Allow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum BacktraceStyle {
|
pub enum BacktraceStyle {
|
||||||
/// Prints a terser backtrace which ideally only contains relevant information.
|
/// Prints a terser backtrace which ideally only contains relevant information.
|
||||||
Short,
|
Short,
|
||||||
|
@ -78,6 +78,16 @@ pub enum BacktraceStyle {
|
||||||
Off,
|
Off,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ValidationMode {
|
||||||
|
/// Do not perform any kind of validation.
|
||||||
|
No,
|
||||||
|
/// Validate the interior of the value, but not things behind references.
|
||||||
|
Shallow,
|
||||||
|
/// Fully recursively validate references.
|
||||||
|
Deep,
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration needed to spawn a Miri instance.
|
/// Configuration needed to spawn a Miri instance.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MiriConfig {
|
pub struct MiriConfig {
|
||||||
|
@ -85,7 +95,7 @@ pub struct MiriConfig {
|
||||||
/// (This is still subject to isolation as well as `forwarded_env_vars`.)
|
/// (This is still subject to isolation as well as `forwarded_env_vars`.)
|
||||||
pub env: Vec<(OsString, OsString)>,
|
pub env: Vec<(OsString, OsString)>,
|
||||||
/// Determine if validity checking is enabled.
|
/// Determine if validity checking is enabled.
|
||||||
pub validate: bool,
|
pub validation: ValidationMode,
|
||||||
/// Determines if Stacked Borrows or Tree Borrows is enabled.
|
/// Determines if Stacked Borrows or Tree Borrows is enabled.
|
||||||
pub borrow_tracker: Option<BorrowTrackerMethod>,
|
pub borrow_tracker: Option<BorrowTrackerMethod>,
|
||||||
/// Whether `core::ptr::Unique` receives special treatment.
|
/// Whether `core::ptr::Unique` receives special treatment.
|
||||||
|
@ -162,7 +172,7 @@ impl Default for MiriConfig {
|
||||||
fn default() -> MiriConfig {
|
fn default() -> MiriConfig {
|
||||||
MiriConfig {
|
MiriConfig {
|
||||||
env: vec![],
|
env: vec![],
|
||||||
validate: true,
|
validation: ValidationMode::Shallow,
|
||||||
borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows),
|
borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows),
|
||||||
unique_is_unique: false,
|
unique_is_unique: false,
|
||||||
check_alignment: AlignmentCheck::Int,
|
check_alignment: AlignmentCheck::Int,
|
||||||
|
|
|
@ -153,7 +153,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||||
// Would not be considered UB, or the other way around (`is_val_statically_known(0)`).
|
// Would not be considered UB, or the other way around (`is_val_statically_known(0)`).
|
||||||
"is_val_statically_known" => {
|
"is_val_statically_known" => {
|
||||||
let [arg] = check_arg_count(args)?;
|
let [arg] = check_arg_count(args)?;
|
||||||
this.validate_operand(arg)?;
|
this.validate_operand(arg, /*recursive*/ false)?;
|
||||||
let branch: bool = this.machine.rng.get_mut().gen();
|
let branch: bool = this.machine.rng.get_mut().gen();
|
||||||
this.write_scalar(Scalar::from_bool(branch), dest)?;
|
this.write_scalar(Scalar::from_bool(branch), dest)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,7 @@ pub use crate::diagnostics::{
|
||||||
};
|
};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
create_ecx, eval_entry, AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, RejectOpWith,
|
create_ecx, eval_entry, AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, RejectOpWith,
|
||||||
|
ValidationMode,
|
||||||
};
|
};
|
||||||
pub use crate::helpers::{AccessKind, EvalContextExt as _};
|
pub use crate::helpers::{AccessKind, EvalContextExt as _};
|
||||||
pub use crate::machine::{
|
pub use crate::machine::{
|
||||||
|
|
|
@ -187,7 +187,11 @@ impl fmt::Display for MiriMemoryKind {
|
||||||
pub type MemoryKind = interpret::MemoryKind<MiriMemoryKind>;
|
pub type MemoryKind = interpret::MemoryKind<MiriMemoryKind>;
|
||||||
|
|
||||||
/// Pointer provenance.
|
/// Pointer provenance.
|
||||||
#[derive(Clone, Copy)]
|
// This needs to be `Eq`+`Hash` because the `Machine` trait needs that because validity checking
|
||||||
|
// *might* be recursive and then it has to track which places have already been visited.
|
||||||
|
// These implementations are a bit questionable, and it means we may check the same place multiple
|
||||||
|
// times with different provenance, but that is in general not wrong.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum Provenance {
|
pub enum Provenance {
|
||||||
/// For pointers with concrete provenance. we exactly know which allocation they are attached to
|
/// For pointers with concrete provenance. we exactly know which allocation they are attached to
|
||||||
/// and what their borrow tag is.
|
/// and what their borrow tag is.
|
||||||
|
@ -215,24 +219,6 @@ pub enum Provenance {
|
||||||
Wildcard,
|
Wildcard,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This needs to be `Eq`+`Hash` because the `Machine` trait needs that because validity checking
|
|
||||||
// *might* be recursive and then it has to track which places have already been visited.
|
|
||||||
// However, comparing provenance is meaningless, since `Wildcard` might be any provenance -- and of
|
|
||||||
// course we don't actually do recursive checking.
|
|
||||||
// We could change `RefTracking` to strip provenance for its `seen` set but that type is generic so that is quite annoying.
|
|
||||||
// Instead owe add the required instances but make them panic.
|
|
||||||
impl PartialEq for Provenance {
|
|
||||||
fn eq(&self, _other: &Self) -> bool {
|
|
||||||
panic!("Provenance must not be compared")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for Provenance {}
|
|
||||||
impl std::hash::Hash for Provenance {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {
|
|
||||||
panic!("Provenance must not be hashed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The "extra" information a pointer has over a regular AllocId.
|
/// The "extra" information a pointer has over a regular AllocId.
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum ProvenanceExtra {
|
pub enum ProvenanceExtra {
|
||||||
|
@ -460,7 +446,7 @@ pub struct MiriMachine<'tcx> {
|
||||||
pub(crate) isolated_op: IsolatedOp,
|
pub(crate) isolated_op: IsolatedOp,
|
||||||
|
|
||||||
/// Whether to enforce the validity invariant.
|
/// Whether to enforce the validity invariant.
|
||||||
pub(crate) validate: bool,
|
pub(crate) validation: ValidationMode,
|
||||||
|
|
||||||
/// The table of file descriptors.
|
/// The table of file descriptors.
|
||||||
pub(crate) fds: shims::FdTable,
|
pub(crate) fds: shims::FdTable,
|
||||||
|
@ -659,7 +645,7 @@ impl<'tcx> MiriMachine<'tcx> {
|
||||||
cmd_line: None,
|
cmd_line: None,
|
||||||
tls: TlsData::default(),
|
tls: TlsData::default(),
|
||||||
isolated_op: config.isolated_op,
|
isolated_op: config.isolated_op,
|
||||||
validate: config.validate,
|
validation: config.validation,
|
||||||
fds: shims::FdTable::init(config.mute_stdout_stderr),
|
fds: shims::FdTable::init(config.mute_stdout_stderr),
|
||||||
dirs: Default::default(),
|
dirs: Default::default(),
|
||||||
layouts,
|
layouts,
|
||||||
|
@ -801,7 +787,7 @@ impl VisitProvenance for MiriMachine<'_> {
|
||||||
fds,
|
fds,
|
||||||
tcx: _,
|
tcx: _,
|
||||||
isolated_op: _,
|
isolated_op: _,
|
||||||
validate: _,
|
validation: _,
|
||||||
clock: _,
|
clock: _,
|
||||||
layouts: _,
|
layouts: _,
|
||||||
static_roots: _,
|
static_roots: _,
|
||||||
|
@ -943,7 +929,11 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn enforce_validity(ecx: &MiriInterpCx<'tcx>, _layout: TyAndLayout<'tcx>) -> bool {
|
fn enforce_validity(ecx: &MiriInterpCx<'tcx>, _layout: TyAndLayout<'tcx>) -> bool {
|
||||||
ecx.machine.validate
|
ecx.machine.validation != ValidationMode::No
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn enforce_validity_recursively(ecx: &InterpCx<'tcx, Self>, _layout: TyAndLayout<'tcx>) -> bool {
|
||||||
|
ecx.machine.validation == ValidationMode::Deep
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
//@compile-flags: -Zmiri-recursive-validation
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let x = 3u8;
|
||||||
|
let xref = &x;
|
||||||
|
let xref_wrong_type: &bool = unsafe { std::mem::transmute(xref) }; //~ERROR: encountered 0x03, but expected a boolean
|
||||||
|
let _val = *xref_wrong_type;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
error: Undefined Behavior: constructing invalid value at .<deref>: encountered 0x03, but expected a boolean
|
||||||
|
--> $DIR/recursive-validity-ref-bool.rs:LL:CC
|
||||||
|
|
|
||||||
|
LL | let xref_wrong_type: &bool = unsafe { std::mem::transmute(xref) };
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<deref>: encountered 0x03, but expected a boolean
|
||||||
|
|
|
||||||
|
= 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:
|
||||||
|
= note: inside `main` at $DIR/recursive-validity-ref-bool.rs:LL:CC
|
||||||
|
|
||||||
|
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue