Auto merge of #59627 - LooMaclin:issue_57128_improve_miri_error_reporting_in_check_in_alloc, r=RalfJung
Improve miri error reporting in check_in_alloc Fixes https://github.com/rust-lang/rust/issues/57128 r? @RalfJung @oli-obk
This commit is contained in:
commit
1a56ec4dae
7 changed files with 54 additions and 30 deletions
|
@ -7,7 +7,7 @@ use super::{
|
||||||
|
|
||||||
use crate::ty::layout::{Size, Align};
|
use crate::ty::layout::{Size, Align};
|
||||||
use syntax::ast::Mutability;
|
use syntax::ast::Mutability;
|
||||||
use std::iter;
|
use std::{iter, fmt::{self, Display}};
|
||||||
use crate::mir;
|
use crate::mir;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use rustc_data_structures::sorted_map::SortedMap;
|
use rustc_data_structures::sorted_map::SortedMap;
|
||||||
|
@ -22,6 +22,28 @@ pub enum InboundsCheck {
|
||||||
MaybeDead,
|
MaybeDead,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by `check_in_alloc` to indicate context of check
|
||||||
|
#[derive(Debug, Copy, Clone, RustcEncodable, RustcDecodable, HashStable)]
|
||||||
|
pub enum CheckInAllocMsg {
|
||||||
|
MemoryAccessTest,
|
||||||
|
NullPointerTest,
|
||||||
|
PointerArithmeticTest,
|
||||||
|
InboundsTest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CheckInAllocMsg {
|
||||||
|
/// When this is printed as an error the context looks like this
|
||||||
|
/// "{test name} failed: pointer must be in-bounds at offset..."
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", match *self {
|
||||||
|
CheckInAllocMsg::MemoryAccessTest => "Memory access",
|
||||||
|
CheckInAllocMsg::NullPointerTest => "Null pointer test",
|
||||||
|
CheckInAllocMsg::PointerArithmeticTest => "Pointer arithmetic",
|
||||||
|
CheckInAllocMsg::InboundsTest => "Inbounds test",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
|
||||||
pub struct Allocation<Tag=(),Extra=()> {
|
pub struct Allocation<Tag=(),Extra=()> {
|
||||||
/// The actual bytes of the allocation.
|
/// The actual bytes of the allocation.
|
||||||
|
@ -131,9 +153,10 @@ impl<'tcx, Tag, Extra> Allocation<Tag, Extra> {
|
||||||
fn check_bounds_ptr(
|
fn check_bounds_ptr(
|
||||||
&self,
|
&self,
|
||||||
ptr: Pointer<Tag>,
|
ptr: Pointer<Tag>,
|
||||||
|
msg: CheckInAllocMsg,
|
||||||
) -> EvalResult<'tcx> {
|
) -> EvalResult<'tcx> {
|
||||||
let allocation_size = self.bytes.len() as u64;
|
let allocation_size = self.bytes.len() as u64;
|
||||||
ptr.check_in_alloc(Size::from_bytes(allocation_size), InboundsCheck::Live)
|
ptr.check_in_alloc(Size::from_bytes(allocation_size), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the memory range beginning at `ptr` and of size `Size` is "in-bounds".
|
/// Checks if the memory range beginning at `ptr` and of size `Size` is "in-bounds".
|
||||||
|
@ -143,9 +166,10 @@ impl<'tcx, Tag, Extra> Allocation<Tag, Extra> {
|
||||||
cx: &impl HasDataLayout,
|
cx: &impl HasDataLayout,
|
||||||
ptr: Pointer<Tag>,
|
ptr: Pointer<Tag>,
|
||||||
size: Size,
|
size: Size,
|
||||||
|
msg: CheckInAllocMsg,
|
||||||
) -> EvalResult<'tcx> {
|
) -> EvalResult<'tcx> {
|
||||||
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
|
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
|
||||||
self.check_bounds_ptr(ptr.offset(size, cx)?)
|
self.check_bounds_ptr(ptr.offset(size, cx)?, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,9 +188,10 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
|
||||||
ptr: Pointer<Tag>,
|
ptr: Pointer<Tag>,
|
||||||
size: Size,
|
size: Size,
|
||||||
check_defined_and_ptr: bool,
|
check_defined_and_ptr: bool,
|
||||||
|
msg: CheckInAllocMsg,
|
||||||
) -> EvalResult<'tcx, &[u8]>
|
) -> EvalResult<'tcx, &[u8]>
|
||||||
{
|
{
|
||||||
self.check_bounds(cx, ptr, size)?;
|
self.check_bounds(cx, ptr, size, msg)?;
|
||||||
|
|
||||||
if check_defined_and_ptr {
|
if check_defined_and_ptr {
|
||||||
self.check_defined(ptr, size)?;
|
self.check_defined(ptr, size)?;
|
||||||
|
@ -192,7 +217,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
|
||||||
size: Size,
|
size: Size,
|
||||||
) -> EvalResult<'tcx, &[u8]>
|
) -> EvalResult<'tcx, &[u8]>
|
||||||
{
|
{
|
||||||
self.get_bytes_internal(cx, ptr, size, true)
|
self.get_bytes_internal(cx, ptr, size, true, CheckInAllocMsg::MemoryAccessTest)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// It is the caller's responsibility to handle undefined and pointer bytes.
|
/// It is the caller's responsibility to handle undefined and pointer bytes.
|
||||||
|
@ -205,7 +230,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
|
||||||
size: Size,
|
size: Size,
|
||||||
) -> EvalResult<'tcx, &[u8]>
|
) -> EvalResult<'tcx, &[u8]>
|
||||||
{
|
{
|
||||||
self.get_bytes_internal(cx, ptr, size, false)
|
self.get_bytes_internal(cx, ptr, size, false, CheckInAllocMsg::MemoryAccessTest)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Just calling this already marks everything as defined and removes relocations,
|
/// Just calling this already marks everything as defined and removes relocations,
|
||||||
|
@ -218,7 +243,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
|
||||||
) -> EvalResult<'tcx, &mut [u8]>
|
) -> EvalResult<'tcx, &mut [u8]>
|
||||||
{
|
{
|
||||||
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
|
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
|
||||||
self.check_bounds(cx, ptr, size)?;
|
self.check_bounds(cx, ptr, size, CheckInAllocMsg::MemoryAccessTest)?;
|
||||||
|
|
||||||
self.mark_definedness(ptr, size, true)?;
|
self.mark_definedness(ptr, size, true)?;
|
||||||
self.clear_relocations(cx, ptr, size)?;
|
self.clear_relocations(cx, ptr, size)?;
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::ty::layout::{Size, Align, LayoutError};
|
||||||
use rustc_target::spec::abi::Abi;
|
use rustc_target::spec::abi::Abi;
|
||||||
use rustc_macros::HashStable;
|
use rustc_macros::HashStable;
|
||||||
|
|
||||||
use super::{RawConst, Pointer, InboundsCheck, ScalarMaybeUndef};
|
use super::{RawConst, Pointer, CheckInAllocMsg, ScalarMaybeUndef};
|
||||||
|
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ pub enum InterpError<'tcx, O> {
|
||||||
InvalidDiscriminant(ScalarMaybeUndef),
|
InvalidDiscriminant(ScalarMaybeUndef),
|
||||||
PointerOutOfBounds {
|
PointerOutOfBounds {
|
||||||
ptr: Pointer,
|
ptr: Pointer,
|
||||||
check: InboundsCheck,
|
msg: CheckInAllocMsg,
|
||||||
allocation_size: Size,
|
allocation_size: Size,
|
||||||
},
|
},
|
||||||
InvalidNullPointerUsage,
|
InvalidNullPointerUsage,
|
||||||
|
@ -466,14 +466,10 @@ impl<'tcx, O: fmt::Debug> fmt::Debug for InterpError<'tcx, O> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
use self::InterpError::*;
|
use self::InterpError::*;
|
||||||
match *self {
|
match *self {
|
||||||
PointerOutOfBounds { ptr, check, allocation_size } => {
|
PointerOutOfBounds { ptr, msg, allocation_size } => {
|
||||||
write!(f, "Pointer must be in-bounds{} at offset {}, but is outside bounds of \
|
write!(f, "{} failed: pointer must be in-bounds at offset {}, \
|
||||||
allocation {} which has size {}",
|
but is outside bounds of allocation {} which has size {}",
|
||||||
match check {
|
msg, ptr.offset.bytes(), ptr.alloc_id, allocation_size.bytes())
|
||||||
InboundsCheck::Live => " and live",
|
|
||||||
InboundsCheck::MaybeDead => "",
|
|
||||||
},
|
|
||||||
ptr.offset.bytes(), ptr.alloc_id, allocation_size.bytes())
|
|
||||||
},
|
},
|
||||||
ValidationFailure(ref err) => {
|
ValidationFailure(ref err) => {
|
||||||
write!(f, "type validation failed: {}", err)
|
write!(f, "type validation failed: {}", err)
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub use self::value::{Scalar, ScalarMaybeUndef, RawConst, ConstValue};
|
||||||
|
|
||||||
pub use self::allocation::{
|
pub use self::allocation::{
|
||||||
InboundsCheck, Allocation, AllocationExtra,
|
InboundsCheck, Allocation, AllocationExtra,
|
||||||
Relocations, UndefMask,
|
Relocations, UndefMask, CheckInAllocMsg,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::pointer::{Pointer, PointerArithmetic};
|
pub use self::pointer::{Pointer, PointerArithmetic};
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::ty::layout::{self, HasDataLayout, Size};
|
||||||
use rustc_macros::HashStable;
|
use rustc_macros::HashStable;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AllocId, EvalResult, InboundsCheck,
|
AllocId, EvalResult, CheckInAllocMsg
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -177,12 +177,12 @@ impl<'tcx, Tag> Pointer<Tag> {
|
||||||
pub fn check_in_alloc(
|
pub fn check_in_alloc(
|
||||||
self,
|
self,
|
||||||
allocation_size: Size,
|
allocation_size: Size,
|
||||||
check: InboundsCheck,
|
msg: CheckInAllocMsg,
|
||||||
) -> EvalResult<'tcx, ()> {
|
) -> EvalResult<'tcx, ()> {
|
||||||
if self.offset > allocation_size {
|
if self.offset > allocation_size {
|
||||||
err!(PointerOutOfBounds {
|
err!(PointerOutOfBounds {
|
||||||
ptr: self.erase_tag(),
|
ptr: self.erase_tag(),
|
||||||
check,
|
msg,
|
||||||
allocation_size,
|
allocation_size,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,7 +20,7 @@ use syntax::ast::Mutability;
|
||||||
use super::{
|
use super::{
|
||||||
Pointer, AllocId, Allocation, GlobalId, AllocationExtra,
|
Pointer, AllocId, Allocation, GlobalId, AllocationExtra,
|
||||||
EvalResult, Scalar, InterpError, AllocKind, PointerArithmetic,
|
EvalResult, Scalar, InterpError, AllocKind, PointerArithmetic,
|
||||||
Machine, AllocMap, MayLeak, ErrorHandled, InboundsCheck,
|
Machine, AllocMap, MayLeak, ErrorHandled, CheckInAllocMsg, InboundsCheck,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
|
||||||
|
@ -252,7 +252,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
|
||||||
Scalar::Ptr(ptr) => {
|
Scalar::Ptr(ptr) => {
|
||||||
// check this is not NULL -- which we can ensure only if this is in-bounds
|
// check this is not NULL -- which we can ensure only if this is in-bounds
|
||||||
// of some (potentially dead) allocation.
|
// of some (potentially dead) allocation.
|
||||||
let align = self.check_bounds_ptr(ptr, InboundsCheck::MaybeDead)?;
|
let align = self.check_bounds_ptr(ptr, InboundsCheck::MaybeDead,
|
||||||
|
CheckInAllocMsg::NullPointerTest)?;
|
||||||
(ptr.offset.bytes(), align)
|
(ptr.offset.bytes(), align)
|
||||||
}
|
}
|
||||||
Scalar::Bits { bits, size } => {
|
Scalar::Bits { bits, size } => {
|
||||||
|
@ -293,9 +294,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
|
||||||
&self,
|
&self,
|
||||||
ptr: Pointer<M::PointerTag>,
|
ptr: Pointer<M::PointerTag>,
|
||||||
liveness: InboundsCheck,
|
liveness: InboundsCheck,
|
||||||
|
msg: CheckInAllocMsg,
|
||||||
) -> EvalResult<'tcx, Align> {
|
) -> EvalResult<'tcx, Align> {
|
||||||
let (allocation_size, align) = self.get_size_and_align(ptr.alloc_id, liveness)?;
|
let (allocation_size, align) = self.get_size_and_align(ptr.alloc_id, liveness)?;
|
||||||
ptr.check_in_alloc(allocation_size, liveness)?;
|
ptr.check_in_alloc(allocation_size, msg)?;
|
||||||
Ok(align)
|
Ok(align)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,7 +421,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
|
||||||
|
|
||||||
/// Obtain the size and alignment of an allocation, even if that allocation has been deallocated
|
/// Obtain the size and alignment of an allocation, even if that allocation has been deallocated
|
||||||
///
|
///
|
||||||
/// If `liveness` is `InboundsCheck::Dead`, this function always returns `Ok`
|
/// If `liveness` is `InboundsCheck::MaybeDead`, this function always returns `Ok`
|
||||||
pub fn get_size_and_align(
|
pub fn get_size_and_align(
|
||||||
&self,
|
&self,
|
||||||
id: AllocId,
|
id: AllocId,
|
||||||
|
|
|
@ -7,9 +7,9 @@ use rustc::{mir, ty};
|
||||||
use rustc::ty::layout::{self, Size, LayoutOf, TyLayout, HasDataLayout, IntegerExt, VariantIdx};
|
use rustc::ty::layout::{self, Size, LayoutOf, TyLayout, HasDataLayout, IntegerExt, VariantIdx};
|
||||||
|
|
||||||
use rustc::mir::interpret::{
|
use rustc::mir::interpret::{
|
||||||
GlobalId, AllocId, InboundsCheck,
|
GlobalId, AllocId, CheckInAllocMsg,
|
||||||
ConstValue, Pointer, Scalar,
|
ConstValue, Pointer, Scalar,
|
||||||
EvalResult, InterpError,
|
EvalResult, InterpError, InboundsCheck,
|
||||||
sign_extend, truncate,
|
sign_extend, truncate,
|
||||||
};
|
};
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -645,7 +645,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> InterpretCx<'a, 'mir, 'tcx, M>
|
||||||
ScalarMaybeUndef::Scalar(Scalar::Ptr(ptr)) => {
|
ScalarMaybeUndef::Scalar(Scalar::Ptr(ptr)) => {
|
||||||
// The niche must be just 0 (which an inbounds pointer value never is)
|
// The niche must be just 0 (which an inbounds pointer value never is)
|
||||||
let ptr_valid = niche_start == 0 && variants_start == variants_end &&
|
let ptr_valid = niche_start == 0 && variants_start == variants_end &&
|
||||||
self.memory.check_bounds_ptr(ptr, InboundsCheck::MaybeDead).is_ok();
|
self.memory.check_bounds_ptr(ptr, InboundsCheck::MaybeDead,
|
||||||
|
CheckInAllocMsg::NullPointerTest).is_ok();
|
||||||
if !ptr_valid {
|
if !ptr_valid {
|
||||||
return err!(InvalidDiscriminant(raw_discr.erase_tag()));
|
return err!(InvalidDiscriminant(raw_discr.erase_tag()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use rustc::ty::layout::{self, Size, Align, TyLayout, LayoutOf, VariantIdx};
|
||||||
use rustc::ty;
|
use rustc::ty;
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc::mir::interpret::{
|
use rustc::mir::interpret::{
|
||||||
Scalar, AllocKind, EvalResult, InterpError,
|
Scalar, AllocKind, EvalResult, InterpError, CheckInAllocMsg,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -417,7 +417,7 @@ impl<'rt, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>>
|
||||||
try_validation!(
|
try_validation!(
|
||||||
self.ecx.memory
|
self.ecx.memory
|
||||||
.get(ptr.alloc_id)?
|
.get(ptr.alloc_id)?
|
||||||
.check_bounds(self.ecx, ptr, size),
|
.check_bounds(self.ecx, ptr, size, CheckInAllocMsg::InboundsTest),
|
||||||
"dangling (not entirely in bounds) reference", self.path);
|
"dangling (not entirely in bounds) reference", self.path);
|
||||||
}
|
}
|
||||||
// Check if we have encountered this pointer+layout combination
|
// Check if we have encountered this pointer+layout combination
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue