interpret: make read-pointer-as-bytes *always* work in Miri
and show some extra information when it happens in CTFE
This commit is contained in:
parent
e63a625711
commit
2e172473da
15 changed files with 155 additions and 147 deletions
|
@ -186,7 +186,10 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>(
|
||||||
let size = Size::from_bytes(
|
let size = Size::from_bytes(
|
||||||
4 * ret_lane_count, /* size_of([u32; ret_lane_count]) */
|
4 * ret_lane_count, /* size_of([u32; ret_lane_count]) */
|
||||||
);
|
);
|
||||||
alloc.inner().get_bytes(fx, alloc_range(offset, size)).unwrap()
|
alloc
|
||||||
|
.inner()
|
||||||
|
.get_bytes_strip_provenance(fx, alloc_range(offset, size))
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
_ => unreachable!("{:?}", idx_const),
|
_ => unreachable!("{:?}", idx_const),
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ use rustc_span::{Span, Symbol};
|
||||||
use super::InterpCx;
|
use super::InterpCx;
|
||||||
use crate::interpret::{
|
use crate::interpret::{
|
||||||
struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
|
struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
|
||||||
|
UnsupportedOpInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The CTFE machine has some custom error kinds.
|
/// The CTFE machine has some custom error kinds.
|
||||||
|
@ -153,6 +154,18 @@ impl<'tcx> ConstEvalErr<'tcx> {
|
||||||
if let Some(span_msg) = span_msg {
|
if let Some(span_msg) = span_msg {
|
||||||
err.span_label(self.span, span_msg);
|
err.span_label(self.span, span_msg);
|
||||||
}
|
}
|
||||||
|
// Add some more context for select error types.
|
||||||
|
match self.error {
|
||||||
|
InterpError::Unsupported(
|
||||||
|
UnsupportedOpInfo::ReadPointerAsBytes
|
||||||
|
| UnsupportedOpInfo::PartialPointerOverwrite(_)
|
||||||
|
| UnsupportedOpInfo::PartialPointerCopy(_),
|
||||||
|
) => {
|
||||||
|
err.help("this code performed an operation that depends on the underlying bytes representing a pointer");
|
||||||
|
err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
// Add spans for the stacktrace. Don't print a single-line backtrace though.
|
// Add spans for the stacktrace. Don't print a single-line backtrace though.
|
||||||
if self.stacktrace.len() > 1 {
|
if self.stacktrace.len() > 1 {
|
||||||
// Helper closure to print duplicated lines.
|
// Helper closure to print duplicated lines.
|
||||||
|
|
|
@ -2,8 +2,8 @@ use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr};
|
||||||
use crate::interpret::eval_nullary_intrinsic;
|
use crate::interpret::eval_nullary_intrinsic;
|
||||||
use crate::interpret::{
|
use crate::interpret::{
|
||||||
intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
|
intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
|
||||||
Immediate, InternKind, InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking,
|
Immediate, InternKind, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy,
|
||||||
StackPopCleanup,
|
RefTracking, StackPopCleanup,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rustc_hir::def::DefKind;
|
use rustc_hir::def::DefKind;
|
||||||
|
@ -385,7 +385,9 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
|
||||||
ecx.tcx,
|
ecx.tcx,
|
||||||
"it is undefined behavior to use this value",
|
"it is undefined behavior to use this value",
|
||||||
|diag| {
|
|diag| {
|
||||||
|
if matches!(err.error, InterpError::UndefinedBehavior(_)) {
|
||||||
diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
|
diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
|
||||||
|
}
|
||||||
diag.note(&format!(
|
diag.note(&format!(
|
||||||
"the raw bytes of the constant ({}",
|
"the raw bytes of the constant ({}",
|
||||||
display_allocation(
|
display_allocation(
|
||||||
|
|
|
@ -687,10 +687,23 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
let layout = self.layout_of(lhs.layout.ty.builtin_deref(true).unwrap().ty)?;
|
let layout = self.layout_of(lhs.layout.ty.builtin_deref(true).unwrap().ty)?;
|
||||||
assert!(!layout.is_unsized());
|
assert!(!layout.is_unsized());
|
||||||
|
|
||||||
let lhs = self.read_pointer(lhs)?;
|
let get_bytes = |this: &InterpCx<'mir, 'tcx, M>,
|
||||||
let rhs = self.read_pointer(rhs)?;
|
op: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,
|
||||||
let lhs_bytes = self.read_bytes_ptr(lhs, layout.size)?;
|
size|
|
||||||
let rhs_bytes = self.read_bytes_ptr(rhs, layout.size)?;
|
-> InterpResult<'tcx, &[u8]> {
|
||||||
|
let ptr = this.read_pointer(op)?;
|
||||||
|
let Some(alloc_ref) = self.get_ptr_alloc(ptr, size, Align::ONE)? else {
|
||||||
|
// zero-sized access
|
||||||
|
return Ok(&[]);
|
||||||
|
};
|
||||||
|
if alloc_ref.has_provenance() {
|
||||||
|
throw_ub_format!("`raw_eq` on bytes with provenance");
|
||||||
|
}
|
||||||
|
alloc_ref.get_bytes_strip_provenance()
|
||||||
|
};
|
||||||
|
|
||||||
|
let lhs_bytes = get_bytes(self, lhs, layout.size)?;
|
||||||
|
let rhs_bytes = get_bytes(self, rhs, layout.size)?;
|
||||||
Ok(Scalar::from_bool(lhs_bytes == rhs_bytes))
|
Ok(Scalar::from_bool(lhs_bytes == rhs_bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -953,10 +953,10 @@ impl<'tcx, 'a, Prov: Provenance, Extra> AllocRef<'a, 'tcx, Prov, Extra> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `range` is relative to this allocation reference, not the base of the allocation.
|
/// `range` is relative to this allocation reference, not the base of the allocation.
|
||||||
pub fn check_bytes(&self, range: AllocRange) -> InterpResult<'tcx> {
|
pub fn get_bytes_strip_provenance<'b>(&'b self) -> InterpResult<'tcx, &'a [u8]> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.alloc
|
.alloc
|
||||||
.check_bytes(&self.tcx, self.range.subrange(range))
|
.get_bytes_strip_provenance(&self.tcx, self.range)
|
||||||
.map_err(|e| e.to_interp_error(self.alloc_id))?)
|
.map_err(|e| e.to_interp_error(self.alloc_id))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -967,10 +967,11 @@ impl<'tcx, 'a, Prov: Provenance, Extra> AllocRef<'a, 'tcx, Prov, Extra> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
/// Reads the given number of bytes from memory. Returns them as a slice.
|
/// Reads the given number of bytes from memory, and strips their provenance if possible.
|
||||||
|
/// Returns them as a slice.
|
||||||
///
|
///
|
||||||
/// Performs appropriate bounds checks.
|
/// Performs appropriate bounds checks.
|
||||||
pub fn read_bytes_ptr(
|
pub fn read_bytes_ptr_strip_provenance(
|
||||||
&self,
|
&self,
|
||||||
ptr: Pointer<Option<M::Provenance>>,
|
ptr: Pointer<Option<M::Provenance>>,
|
||||||
size: Size,
|
size: Size,
|
||||||
|
@ -983,7 +984,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
// (We are staying inside the bounds here so all is good.)
|
// (We are staying inside the bounds here so all is good.)
|
||||||
Ok(alloc_ref
|
Ok(alloc_ref
|
||||||
.alloc
|
.alloc
|
||||||
.get_bytes(&alloc_ref.tcx, alloc_ref.range)
|
.get_bytes_strip_provenance(&alloc_ref.tcx, alloc_ref.range)
|
||||||
.map_err(|e| e.to_interp_error(alloc_ref.alloc_id))?)
|
.map_err(|e| e.to_interp_error(alloc_ref.alloc_id))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,12 +1079,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// This checks provenance edges on the src, which needs to happen before
|
// Checks provenance edges on the src, which needs to happen before
|
||||||
// `prepare_provenance_copy`.
|
// `prepare_provenance_copy`.
|
||||||
let src_bytes = src_alloc
|
if src_alloc.range_has_provenance(&tcx, alloc_range(src_range.start, Size::ZERO)) {
|
||||||
.get_bytes_with_uninit_and_ptr(&tcx, src_range)
|
throw_unsup!(PartialPointerCopy(Pointer::new(src_alloc_id, src_range.start)));
|
||||||
.map_err(|e| e.to_interp_error(src_alloc_id))?
|
}
|
||||||
.as_ptr(); // raw ptr, so we can also get a ptr to the destination allocation
|
if src_alloc.range_has_provenance(&tcx, alloc_range(src_range.end(), Size::ZERO)) {
|
||||||
|
throw_unsup!(PartialPointerCopy(Pointer::new(src_alloc_id, src_range.end())));
|
||||||
|
}
|
||||||
|
let src_bytes = src_alloc.get_bytes_unchecked(src_range).as_ptr(); // raw ptr, so we can also get a ptr to the destination allocation
|
||||||
// first copy the provenance to a temporary buffer, because
|
// first copy the provenance to a temporary buffer, because
|
||||||
// `get_bytes_mut` will clear the provenance, which is correct,
|
// `get_bytes_mut` will clear the provenance, which is correct,
|
||||||
// since we don't want to keep any provenance at the target.
|
// since we don't want to keep any provenance at the target.
|
||||||
|
|
|
@ -415,7 +415,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
/// Turn the wide MPlace into a string (must already be dereferenced!)
|
/// Turn the wide MPlace into a string (must already be dereferenced!)
|
||||||
pub fn read_str(&self, mplace: &MPlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx, &str> {
|
pub fn read_str(&self, mplace: &MPlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx, &str> {
|
||||||
let len = mplace.len(self)?;
|
let len = mplace.len(self)?;
|
||||||
let bytes = self.read_bytes_ptr(mplace.ptr, Size::from_bytes(len))?;
|
let bytes = self.read_bytes_ptr_strip_provenance(mplace.ptr, Size::from_bytes(len))?;
|
||||||
let str = std::str::from_utf8(bytes).map_err(|err| err_ub!(InvalidStr(err)))?;
|
let str = std::str::from_utf8(bytes).map_err(|err| err_ub!(InvalidStr(err)))?;
|
||||||
Ok(str)
|
Ok(str)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
//! into a place.
|
//! into a place.
|
||||||
//! All high-level functions to write to memory work on places as destinations.
|
//! All high-level functions to write to memory work on places as destinations.
|
||||||
|
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
use rustc_ast::Mutability;
|
use rustc_ast::Mutability;
|
||||||
use rustc_middle::mir;
|
use rustc_middle::mir;
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
|
@ -290,7 +288,7 @@ impl<'tcx, Prov: Provenance> PlaceTy<'tcx, Prov> {
|
||||||
// FIXME: Working around https://github.com/rust-lang/rust/issues/54385
|
// FIXME: Working around https://github.com/rust-lang/rust/issues/54385
|
||||||
impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
|
impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
|
||||||
where
|
where
|
||||||
Prov: Provenance + Eq + Hash + 'static,
|
Prov: Provenance + 'static,
|
||||||
M: Machine<'mir, 'tcx, Provenance = Prov>,
|
M: Machine<'mir, 'tcx, Provenance = Prov>,
|
||||||
{
|
{
|
||||||
/// Take a value, which represents a (thin or wide) reference, and make it a place.
|
/// Take a value, which represents a (thin or wide) reference, and make it a place.
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
//! but we still need to do bounds checking and adjust the layout. To not duplicate that with MPlaceTy, we actually
|
//! but we still need to do bounds checking and adjust the layout. To not duplicate that with MPlaceTy, we actually
|
||||||
//! implement the logic on OpTy, and MPlaceTy calls that.
|
//! implement the logic on OpTy, and MPlaceTy calls that.
|
||||||
|
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
use rustc_middle::mir;
|
use rustc_middle::mir;
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
use rustc_middle::ty::layout::LayoutOf;
|
use rustc_middle::ty::layout::LayoutOf;
|
||||||
|
@ -22,7 +20,7 @@ use super::{
|
||||||
// FIXME: Working around https://github.com/rust-lang/rust/issues/54385
|
// FIXME: Working around https://github.com/rust-lang/rust/issues/54385
|
||||||
impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
|
impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
|
||||||
where
|
where
|
||||||
Prov: Provenance + Eq + Hash + 'static,
|
Prov: Provenance + 'static,
|
||||||
M: Machine<'mir, 'tcx, Provenance = Prov>,
|
M: Machine<'mir, 'tcx, Provenance = Prov>,
|
||||||
{
|
{
|
||||||
//# Field access
|
//# Field access
|
||||||
|
|
|
@ -20,8 +20,8 @@ use rustc_target::abi::{Abi, Scalar as ScalarAbi, Size, VariantIdx, Variants, Wr
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
alloc_range, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy,
|
CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine,
|
||||||
Machine, MemPlaceMeta, OpTy, Scalar, ValueVisitor,
|
MemPlaceMeta, OpTy, Scalar, ValueVisitor,
|
||||||
};
|
};
|
||||||
|
|
||||||
macro_rules! throw_validation_failure {
|
macro_rules! throw_validation_failure {
|
||||||
|
@ -312,7 +312,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
||||||
Ok(try_validation!(
|
Ok(try_validation!(
|
||||||
self.ecx.read_immediate(op),
|
self.ecx.read_immediate(op),
|
||||||
self.path,
|
self.path,
|
||||||
err_unsup!(ReadPointerAsBytes) => { "(potentially part of) a pointer" } expected { "{expected}" },
|
|
||||||
err_ub!(InvalidUninitBytes(None)) => { "uninitialized memory" } expected { "{expected}" }
|
err_ub!(InvalidUninitBytes(None)) => { "uninitialized memory" } expected { "{expected}" }
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -345,11 +344,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
||||||
// FIXME: check if the type/trait match what ty::Dynamic says?
|
// FIXME: check if the type/trait match what ty::Dynamic says?
|
||||||
}
|
}
|
||||||
ty::Slice(..) | ty::Str => {
|
ty::Slice(..) | ty::Str => {
|
||||||
let _len = try_validation!(
|
let _len = meta.unwrap_meta().to_machine_usize(self.ecx)?;
|
||||||
meta.unwrap_meta().to_machine_usize(self.ecx),
|
|
||||||
self.path,
|
|
||||||
err_unsup!(ReadPointerAsBytes) => { "non-integer slice length in wide pointer" },
|
|
||||||
);
|
|
||||||
// We do not check that `len * elem_size <= isize::MAX`:
|
// We do not check that `len * elem_size <= isize::MAX`:
|
||||||
// that is only required for references, and there it falls out of the
|
// that is only required for references, and there it falls out of the
|
||||||
// "dereferenceable" check performed by Stacked Borrows.
|
// "dereferenceable" check performed by Stacked Borrows.
|
||||||
|
@ -669,8 +664,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
||||||
{ "{:x}", val } expected { "a valid enum tag" },
|
{ "{:x}", val } expected { "a valid enum tag" },
|
||||||
err_ub!(InvalidUninitBytes(None)) =>
|
err_ub!(InvalidUninitBytes(None)) =>
|
||||||
{ "uninitialized bytes" } expected { "a valid enum tag" },
|
{ "uninitialized bytes" } expected { "a valid enum tag" },
|
||||||
err_unsup!(ReadPointerAsBytes) =>
|
|
||||||
{ "a pointer" } expected { "a valid enum tag" },
|
|
||||||
)
|
)
|
||||||
.1)
|
.1)
|
||||||
})
|
})
|
||||||
|
@ -810,10 +803,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
||||||
let mplace = op.assert_mem_place(); // strings are unsized and hence never immediate
|
let mplace = op.assert_mem_place(); // strings are unsized and hence never immediate
|
||||||
let len = mplace.len(self.ecx)?;
|
let len = mplace.len(self.ecx)?;
|
||||||
try_validation!(
|
try_validation!(
|
||||||
self.ecx.read_bytes_ptr(mplace.ptr, Size::from_bytes(len)),
|
self.ecx.read_bytes_ptr_strip_provenance(mplace.ptr, Size::from_bytes(len)),
|
||||||
self.path,
|
self.path,
|
||||||
err_ub!(InvalidUninitBytes(..)) => { "uninitialized data in `str`" },
|
err_ub!(InvalidUninitBytes(..)) => { "uninitialized data in `str`" },
|
||||||
err_unsup!(ReadPointerAsBytes) => { "a pointer in `str`" },
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ty::Array(tys, ..) | ty::Slice(tys)
|
ty::Array(tys, ..) | ty::Slice(tys)
|
||||||
|
@ -861,9 +853,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
||||||
// We also accept uninit, for consistency with the slow path.
|
// We also accept uninit, for consistency with the slow path.
|
||||||
let alloc = self.ecx.get_ptr_alloc(mplace.ptr, size, mplace.align)?.expect("we already excluded size 0");
|
let alloc = self.ecx.get_ptr_alloc(mplace.ptr, size, mplace.align)?.expect("we already excluded size 0");
|
||||||
|
|
||||||
match alloc.check_bytes(alloc_range(Size::ZERO, size)) {
|
match alloc.get_bytes_strip_provenance() {
|
||||||
// In the happy case, we needn't check anything else.
|
// In the happy case, we needn't check anything else.
|
||||||
Ok(()) => {}
|
Ok(_) => {}
|
||||||
// Some error happened, try to provide a more detailed description.
|
// Some error happened, try to provide a more detailed description.
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// For some errors we might be able to provide extra information.
|
// For some errors we might be able to provide extra information.
|
||||||
|
@ -881,9 +873,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
||||||
|
|
||||||
throw_validation_failure!(self.path, { "uninitialized bytes" })
|
throw_validation_failure!(self.path, { "uninitialized bytes" })
|
||||||
}
|
}
|
||||||
err_unsup!(ReadPointerAsBytes) => {
|
|
||||||
throw_validation_failure!(self.path, { "a pointer" } expected { "plain (non-pointer) bytes" })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Propagate upwards (that will also check for unexpected errors).
|
// Propagate upwards (that will also check for unexpected errors).
|
||||||
_ => return Err(err),
|
_ => return Err(err),
|
||||||
|
@ -924,14 +913,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
Ok(()) => Ok(()),
|
Ok(()) => Ok(()),
|
||||||
// Pass through validation failures.
|
// Pass through validation failures.
|
||||||
Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err),
|
Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err),
|
||||||
// Also pass through InvalidProgram, those just indicate that we could not
|
// Complain about any other kind of UB error -- those are bad because we'd like to
|
||||||
// validate and each caller will know best what to do with them.
|
// report them in a way that shows *where* in the value the issue lies.
|
||||||
Err(err) if matches!(err.kind(), InterpError::InvalidProgram(_)) => Err(err),
|
Err(err) if matches!(err.kind(), InterpError::UndefinedBehavior(_)) => {
|
||||||
// Avoid other errors as those do not show *where* in the value the issue lies.
|
|
||||||
Err(err) => {
|
|
||||||
err.print_backtrace();
|
err.print_backtrace();
|
||||||
bug!("Unexpected error during validation: {}", err);
|
bug!("Unexpected Undefined Behavior error during validation: {}", err);
|
||||||
}
|
}
|
||||||
|
// Pass through everything else.
|
||||||
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,8 @@ pub enum AllocError {
|
||||||
ReadPointerAsBytes,
|
ReadPointerAsBytes,
|
||||||
/// Partially overwriting a pointer.
|
/// Partially overwriting a pointer.
|
||||||
PartialPointerOverwrite(Size),
|
PartialPointerOverwrite(Size),
|
||||||
|
/// Partially copying a pointer.
|
||||||
|
PartialPointerCopy(Size),
|
||||||
/// Using uninitialized data where it is not allowed.
|
/// Using uninitialized data where it is not allowed.
|
||||||
InvalidUninitBytes(Option<UninitBytesAccess>),
|
InvalidUninitBytes(Option<UninitBytesAccess>),
|
||||||
}
|
}
|
||||||
|
@ -152,6 +154,9 @@ impl AllocError {
|
||||||
PartialPointerOverwrite(offset) => InterpError::Unsupported(
|
PartialPointerOverwrite(offset) => InterpError::Unsupported(
|
||||||
UnsupportedOpInfo::PartialPointerOverwrite(Pointer::new(alloc_id, offset)),
|
UnsupportedOpInfo::PartialPointerOverwrite(Pointer::new(alloc_id, offset)),
|
||||||
),
|
),
|
||||||
|
PartialPointerCopy(offset) => InterpError::Unsupported(
|
||||||
|
UnsupportedOpInfo::PartialPointerCopy(Pointer::new(alloc_id, offset)),
|
||||||
|
),
|
||||||
InvalidUninitBytes(info) => InterpError::UndefinedBehavior(
|
InvalidUninitBytes(info) => InterpError::UndefinedBehavior(
|
||||||
UndefinedBehaviorInfo::InvalidUninitBytes(info.map(|b| (alloc_id, b))),
|
UndefinedBehaviorInfo::InvalidUninitBytes(info.map(|b| (alloc_id, b))),
|
||||||
),
|
),
|
||||||
|
@ -322,62 +327,35 @@ impl<Prov, Extra> Allocation<Prov, Extra> {
|
||||||
/// Byte accessors.
|
/// Byte accessors.
|
||||||
impl<Prov: Provenance, Extra> Allocation<Prov, Extra> {
|
impl<Prov: Provenance, Extra> Allocation<Prov, Extra> {
|
||||||
/// This is the entirely abstraction-violating way to just grab the raw bytes without
|
/// This is the entirely abstraction-violating way to just grab the raw bytes without
|
||||||
/// caring about provenance. It just deduplicates some code between `read_scalar`
|
/// caring about provenance or initialization.
|
||||||
/// and `get_bytes_internal`.
|
|
||||||
fn get_bytes_even_more_internal(&self, range: AllocRange) -> &[u8] {
|
|
||||||
&self.bytes[range.start.bytes_usize()..range.end().bytes_usize()]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The last argument controls whether we error out when there are uninitialized or pointer
|
|
||||||
/// bytes. However, we *always* error when there is provenance overlapping the edges of the
|
|
||||||
/// range.
|
|
||||||
///
|
|
||||||
/// You should never call this, call `get_bytes` or `get_bytes_with_uninit_and_ptr` instead,
|
|
||||||
///
|
///
|
||||||
/// This function also guarantees that the resulting pointer will remain stable
|
/// This function also guarantees that the resulting pointer will remain stable
|
||||||
/// even when new allocations are pushed to the `HashMap`. `mem_copy_repeatedly` relies
|
/// even when new allocations are pushed to the `HashMap`. `mem_copy_repeatedly` relies
|
||||||
/// on that.
|
/// on that.
|
||||||
///
|
#[inline]
|
||||||
/// It is the caller's responsibility to check bounds and alignment beforehand.
|
pub fn get_bytes_unchecked(&self, range: AllocRange) -> &[u8] {
|
||||||
fn get_bytes_internal(
|
&self.bytes[range.start.bytes_usize()..range.end().bytes_usize()]
|
||||||
&self,
|
|
||||||
cx: &impl HasDataLayout,
|
|
||||||
range: AllocRange,
|
|
||||||
check_init_and_ptr: bool,
|
|
||||||
) -> AllocResult<&[u8]> {
|
|
||||||
if check_init_and_ptr {
|
|
||||||
self.check_init(range)?;
|
|
||||||
self.check_provenance(cx, range)?;
|
|
||||||
} else {
|
|
||||||
// We still don't want provenance on the *edges*.
|
|
||||||
self.check_provenance_edges(cx, range)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self.get_bytes_even_more_internal(range))
|
/// Checks that these bytes are initialized, and then strip provenance (if possible) and return
|
||||||
}
|
/// them.
|
||||||
|
|
||||||
/// Checks that these bytes are initialized and not pointer bytes, and then return them
|
|
||||||
/// as a slice.
|
|
||||||
///
|
///
|
||||||
/// It is the caller's responsibility to check bounds and alignment beforehand.
|
/// It is the caller's responsibility to check bounds and alignment beforehand.
|
||||||
/// Most likely, you want to use the `PlaceTy` and `OperandTy`-based methods
|
/// Most likely, you want to use the `PlaceTy` and `OperandTy`-based methods
|
||||||
/// on `InterpCx` instead.
|
/// on `InterpCx` instead.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_bytes(&self, cx: &impl HasDataLayout, range: AllocRange) -> AllocResult<&[u8]> {
|
pub fn get_bytes_strip_provenance(
|
||||||
self.get_bytes_internal(cx, range, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// It is the caller's responsibility to handle uninitialized and pointer bytes.
|
|
||||||
/// However, this still checks that there is no provenance on the *edges*.
|
|
||||||
///
|
|
||||||
/// It is the caller's responsibility to check bounds and alignment beforehand.
|
|
||||||
#[inline]
|
|
||||||
pub fn get_bytes_with_uninit_and_ptr(
|
|
||||||
&self,
|
&self,
|
||||||
cx: &impl HasDataLayout,
|
cx: &impl HasDataLayout,
|
||||||
range: AllocRange,
|
range: AllocRange,
|
||||||
) -> AllocResult<&[u8]> {
|
) -> AllocResult<&[u8]> {
|
||||||
self.get_bytes_internal(cx, range, false)
|
self.check_init(range)?;
|
||||||
|
if !Prov::OFFSET_IS_ADDR {
|
||||||
|
if self.range_has_provenance(cx, range) {
|
||||||
|
return Err(AllocError::ReadPointerAsBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(self.get_bytes_unchecked(range))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Just calling this already marks everything as defined and removes provenance,
|
/// Just calling this already marks everything as defined and removes provenance,
|
||||||
|
@ -415,13 +393,6 @@ impl<Prov: Provenance, Extra> Allocation<Prov, Extra> {
|
||||||
|
|
||||||
/// Reading and writing.
|
/// Reading and writing.
|
||||||
impl<Prov: Provenance, Extra> Allocation<Prov, Extra> {
|
impl<Prov: Provenance, Extra> Allocation<Prov, Extra> {
|
||||||
/// Validates that this memory range is initiailized and contains no provenance.
|
|
||||||
pub fn check_bytes(&self, cx: &impl HasDataLayout, range: AllocRange) -> AllocResult {
|
|
||||||
// This implicitly does all the checking we are asking for.
|
|
||||||
self.get_bytes(cx, range)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a *non-ZST* scalar.
|
/// Reads a *non-ZST* scalar.
|
||||||
///
|
///
|
||||||
/// If `read_provenance` is `true`, this will also read provenance; otherwise (if the machine
|
/// If `read_provenance` is `true`, this will also read provenance; otherwise (if the machine
|
||||||
|
@ -438,43 +409,53 @@ impl<Prov: Provenance, Extra> Allocation<Prov, Extra> {
|
||||||
range: AllocRange,
|
range: AllocRange,
|
||||||
read_provenance: bool,
|
read_provenance: bool,
|
||||||
) -> AllocResult<Scalar<Prov>> {
|
) -> AllocResult<Scalar<Prov>> {
|
||||||
if read_provenance {
|
|
||||||
assert_eq!(range.size, cx.data_layout().pointer_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First and foremost, if anything is uninit, bail.
|
// First and foremost, if anything is uninit, bail.
|
||||||
if self.is_init(range).is_err() {
|
if self.is_init(range).is_err() {
|
||||||
return Err(AllocError::InvalidUninitBytes(None));
|
return Err(AllocError::InvalidUninitBytes(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are doing a pointer read, and there is provenance exactly where we
|
// Get the integer part of the result. We HAVE TO check provenance before returning this!
|
||||||
// are reading, then we can put data and provenance back together and return that.
|
let bytes = self.get_bytes_unchecked(range);
|
||||||
if read_provenance && let Some(&prov) = self.provenance.get(&range.start) {
|
|
||||||
// We already checked init and provenance, so we can use this function.
|
|
||||||
let bytes = self.get_bytes_even_more_internal(range);
|
|
||||||
let bits = read_target_uint(cx.data_layout().endian, bytes).unwrap();
|
let bits = read_target_uint(cx.data_layout().endian, bytes).unwrap();
|
||||||
|
|
||||||
|
if read_provenance {
|
||||||
|
assert_eq!(range.size, cx.data_layout().pointer_size);
|
||||||
|
|
||||||
|
// When reading data with provenance, the easy case is finding provenance exactly where we
|
||||||
|
// are reading, then we can put data and provenance back together and return that.
|
||||||
|
if let Some(&prov) = self.provenance.get(&range.start) {
|
||||||
|
// Now we can return the bits, with their appropriate provenance.
|
||||||
let ptr = Pointer::new(prov, Size::from_bytes(bits));
|
let ptr = Pointer::new(prov, Size::from_bytes(bits));
|
||||||
return Ok(Scalar::from_pointer(ptr, cx));
|
return Ok(Scalar::from_pointer(ptr, cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are *not* reading a pointer, and we can just ignore provenance,
|
// If we can work on pointers byte-wise, join the byte-wise provenances.
|
||||||
// then do exactly that.
|
if Prov::OFFSET_IS_ADDR {
|
||||||
if !read_provenance && Prov::OFFSET_IS_ADDR {
|
let mut prov = self.offset_get_provenance(cx, range.start);
|
||||||
|
for offset in 1..range.size.bytes() {
|
||||||
|
let this_prov =
|
||||||
|
self.offset_get_provenance(cx, range.start + Size::from_bytes(offset));
|
||||||
|
prov = Prov::join(prov, this_prov);
|
||||||
|
}
|
||||||
|
// Now use this provenance.
|
||||||
|
let ptr = Pointer::new(prov, Size::from_bytes(bits));
|
||||||
|
return Ok(Scalar::from_maybe_pointer(ptr, cx));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We are *not* reading a pointer.
|
||||||
|
// If we can just ignore provenance, do exactly that.
|
||||||
|
if Prov::OFFSET_IS_ADDR {
|
||||||
// We just strip provenance.
|
// We just strip provenance.
|
||||||
let bytes = self.get_bytes_even_more_internal(range);
|
|
||||||
let bits = read_target_uint(cx.data_layout().endian, bytes).unwrap();
|
|
||||||
return Ok(Scalar::from_uint(bits, range.size));
|
return Ok(Scalar::from_uint(bits, range.size));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// It's complicated. Better make sure there is no provenance anywhere.
|
// Fallback path for when we cannot treat provenance bytewise or ignore it.
|
||||||
// FIXME: If !OFFSET_IS_ADDR, this is the best we can do. But if OFFSET_IS_ADDR, then
|
assert!(!Prov::OFFSET_IS_ADDR);
|
||||||
// `read_pointer` is true and we ideally would distinguish the following two cases:
|
if self.range_has_provenance(cx, range) {
|
||||||
// - The entire `range` is covered by the same provenance, stored in two separate entries of
|
return Err(AllocError::ReadPointerAsBytes);
|
||||||
// the provenance map. Then we should return a pointer with that provenance.
|
}
|
||||||
// - The range has inhomogeneous provenance. Then we should return just the
|
// There is no provenance, we can just return the bits.
|
||||||
// underlying bits.
|
|
||||||
let bytes = self.get_bytes(cx, range)?;
|
|
||||||
let bits = read_target_uint(cx.data_layout().endian, bytes).unwrap();
|
|
||||||
Ok(Scalar::from_uint(bits, range.size))
|
Ok(Scalar::from_uint(bits, range.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,6 +515,13 @@ impl<Prov: Copy, Extra> Allocation<Prov, Extra> {
|
||||||
self.provenance.range(Size::from_bytes(start)..range.end())
|
self.provenance.range(Size::from_bytes(start)..range.end())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the provenance of a single byte.
|
||||||
|
fn offset_get_provenance(&self, cx: &impl HasDataLayout, offset: Size) -> Option<Prov> {
|
||||||
|
let prov = self.range_get_provenance(cx, alloc_range(offset, Size::from_bytes(1)));
|
||||||
|
assert!(prov.len() <= 1);
|
||||||
|
prov.first().map(|(_offset, prov)| *prov)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether this allocation has progrnance overlapping with the given range.
|
/// Returns whether this allocation has progrnance overlapping with the given range.
|
||||||
///
|
///
|
||||||
/// Note: this function exists to allow `range_get_provenance` to be private, in order to somewhat
|
/// Note: this function exists to allow `range_get_provenance` to be private, in order to somewhat
|
||||||
|
@ -543,12 +531,6 @@ impl<Prov: Copy, Extra> Allocation<Prov, Extra> {
|
||||||
!self.range_get_provenance(cx, range).is_empty()
|
!self.range_get_provenance(cx, range).is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks that there is no provenance overlapping with the given range.
|
|
||||||
#[inline(always)]
|
|
||||||
fn check_provenance(&self, cx: &impl HasDataLayout, range: AllocRange) -> AllocResult {
|
|
||||||
if self.range_has_provenance(cx, range) { Err(AllocError::ReadPointerAsBytes) } else { Ok(()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes all provenance inside the given range.
|
/// Removes all provenance inside the given range.
|
||||||
/// If there is provenance overlapping with the edges, it
|
/// If there is provenance overlapping with the edges, it
|
||||||
/// are removed as well *and* the bytes they cover are marked as
|
/// are removed as well *and* the bytes they cover are marked as
|
||||||
|
@ -606,14 +588,6 @@ impl<Prov: Copy, Extra> Allocation<Prov, Extra> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors if there is provenance overlapping with the edges of the given memory range.
|
|
||||||
#[inline]
|
|
||||||
fn check_provenance_edges(&self, cx: &impl HasDataLayout, range: AllocRange) -> AllocResult {
|
|
||||||
self.check_provenance(cx, alloc_range(range.start, Size::ZERO))?;
|
|
||||||
self.check_provenance(cx, alloc_range(range.end(), Size::ZERO))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores the provenance information of pointers stored in memory.
|
/// Stores the provenance information of pointers stored in memory.
|
||||||
|
|
|
@ -401,14 +401,18 @@ impl fmt::Display for UndefinedBehaviorInfo {
|
||||||
pub enum UnsupportedOpInfo {
|
pub enum UnsupportedOpInfo {
|
||||||
/// Free-form case. Only for errors that are never caught!
|
/// Free-form case. Only for errors that are never caught!
|
||||||
Unsupported(String),
|
Unsupported(String),
|
||||||
/// Encountered a pointer where we needed raw bytes.
|
|
||||||
ReadPointerAsBytes,
|
|
||||||
/// Overwriting parts of a pointer; the resulting state cannot be represented in our
|
/// Overwriting parts of a pointer; the resulting state cannot be represented in our
|
||||||
/// `Allocation` data structure. See <https://github.com/rust-lang/miri/issues/2181>.
|
/// `Allocation` data structure. See <https://github.com/rust-lang/miri/issues/2181>.
|
||||||
PartialPointerOverwrite(Pointer<AllocId>),
|
PartialPointerOverwrite(Pointer<AllocId>),
|
||||||
|
/// Attempting to `copy` parts of a pointer to somewhere else; the resulting state cannot be
|
||||||
|
/// represented in our `Allocation` data structure. See
|
||||||
|
/// <https://github.com/rust-lang/miri/issues/2181>.
|
||||||
|
PartialPointerCopy(Pointer<AllocId>),
|
||||||
//
|
//
|
||||||
// The variants below are only reachable from CTFE/const prop, miri will never emit them.
|
// The variants below are only reachable from CTFE/const prop, miri will never emit them.
|
||||||
//
|
//
|
||||||
|
/// Encountered a pointer where we needed raw bytes.
|
||||||
|
ReadPointerAsBytes,
|
||||||
/// Accessing thread local statics
|
/// Accessing thread local statics
|
||||||
ThreadLocalStatic(DefId),
|
ThreadLocalStatic(DefId),
|
||||||
/// Accessing an unsupported extern static.
|
/// Accessing an unsupported extern static.
|
||||||
|
@ -420,10 +424,13 @@ impl fmt::Display for UnsupportedOpInfo {
|
||||||
use UnsupportedOpInfo::*;
|
use UnsupportedOpInfo::*;
|
||||||
match self {
|
match self {
|
||||||
Unsupported(ref msg) => write!(f, "{msg}"),
|
Unsupported(ref msg) => write!(f, "{msg}"),
|
||||||
ReadPointerAsBytes => write!(f, "unable to turn pointer into raw bytes"),
|
|
||||||
PartialPointerOverwrite(ptr) => {
|
PartialPointerOverwrite(ptr) => {
|
||||||
write!(f, "unable to overwrite parts of a pointer in memory at {ptr:?}")
|
write!(f, "unable to overwrite parts of a pointer in memory at {ptr:?}")
|
||||||
}
|
}
|
||||||
|
PartialPointerCopy(ptr) => {
|
||||||
|
write!(f, "unable to copy parts of a pointer from memory at {ptr:?}")
|
||||||
|
}
|
||||||
|
ReadPointerAsBytes => write!(f, "unable to turn pointer into raw bytes"),
|
||||||
ThreadLocalStatic(did) => write!(f, "cannot access thread local static ({did:?})"),
|
ThreadLocalStatic(did) => write!(f, "cannot access thread local static ({did:?})"),
|
||||||
ReadExternStatic(did) => write!(f, "cannot read from extern static ({did:?})"),
|
ReadExternStatic(did) => write!(f, "cannot read from extern static ({did:?})"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,9 @@ pub trait Provenance: Copy + fmt::Debug {
|
||||||
/// Otherwise this function is best-effort (but must agree with `Machine::ptr_get_alloc`).
|
/// Otherwise this function is best-effort (but must agree with `Machine::ptr_get_alloc`).
|
||||||
/// (Identifying the offset in that allocation, however, is harder -- use `Memory::ptr_get_alloc` for that.)
|
/// (Identifying the offset in that allocation, however, is harder -- use `Memory::ptr_get_alloc` for that.)
|
||||||
fn get_alloc_id(self) -> Option<AllocId>;
|
fn get_alloc_id(self) -> Option<AllocId>;
|
||||||
|
|
||||||
|
/// Defines the 'join' of provenance: what happens when doing a pointer load and different bytes have different provenance.
|
||||||
|
fn join(left: Option<Self>, right: Option<Self>) -> Option<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Provenance for AllocId {
|
impl Provenance for AllocId {
|
||||||
|
@ -152,6 +155,10 @@ impl Provenance for AllocId {
|
||||||
fn get_alloc_id(self) -> Option<AllocId> {
|
fn get_alloc_id(self) -> Option<AllocId> {
|
||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn join(_left: Option<Self>, _right: Option<Self>) -> Option<Self> {
|
||||||
|
panic!("merging provenance is not supported when `OFFSET_IS_ADDR` is false")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a pointer in the Miri engine.
|
/// Represents a pointer in the Miri engine.
|
||||||
|
|
|
@ -507,7 +507,7 @@ pub fn get_slice_bytes<'tcx>(cx: &impl HasDataLayout, val: ConstValue<'tcx>) ->
|
||||||
if let ConstValue::Slice { data, start, end } = val {
|
if let ConstValue::Slice { data, start, end } = val {
|
||||||
let len = end - start;
|
let len = end - start;
|
||||||
data.inner()
|
data.inner()
|
||||||
.get_bytes(
|
.get_bytes_strip_provenance(
|
||||||
cx,
|
cx,
|
||||||
AllocRange { start: Size::from_bytes(start), size: Size::from_bytes(len) },
|
AllocRange { start: Size::from_bytes(start), size: Size::from_bytes(len) },
|
||||||
)
|
)
|
||||||
|
|
|
@ -2719,7 +2719,7 @@ fn pretty_print_const_value<'tcx>(
|
||||||
let n = n.kind().try_to_bits(tcx.data_layout.pointer_size).unwrap();
|
let n = n.kind().try_to_bits(tcx.data_layout.pointer_size).unwrap();
|
||||||
// cast is ok because we already checked for pointer size (32 or 64 bit) above
|
// cast is ok because we already checked for pointer size (32 or 64 bit) above
|
||||||
let range = AllocRange { start: offset, size: Size::from_bytes(n) };
|
let range = AllocRange { start: offset, size: Size::from_bytes(n) };
|
||||||
let byte_str = alloc.inner().get_bytes(&tcx, range).unwrap();
|
let byte_str = alloc.inner().get_bytes_strip_provenance(&tcx, range).unwrap();
|
||||||
fmt.write_str("*")?;
|
fmt.write_str("*")?;
|
||||||
pretty_print_byte_str(fmt, byte_str)?;
|
pretty_print_byte_str(fmt, byte_str)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -1275,7 +1275,7 @@ pub trait PrettyPrinter<'tcx>:
|
||||||
let range =
|
let range =
|
||||||
AllocRange { start: offset, size: Size::from_bytes(len) };
|
AllocRange { start: offset, size: Size::from_bytes(len) };
|
||||||
if let Ok(byte_str) =
|
if let Ok(byte_str) =
|
||||||
alloc.inner().get_bytes(&self.tcx(), range)
|
alloc.inner().get_bytes_strip_provenance(&self.tcx(), range)
|
||||||
{
|
{
|
||||||
p!(pretty_print_byte_str(byte_str))
|
p!(pretty_print_byte_str(byte_str))
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue