Rollup merge of #101101 - RalfJung:read-pointer-as-bytes, r=oli-obk
interpret: make read-pointer-as-bytes a CTFE-only error with extra information Next step in the reaction to https://github.com/rust-lang/rust/issues/99923. Also teaches Miri to implicitly strip provenance in more situations when transmuting pointers to integers, which fixes https://github.com/rust-lang/miri/issues/2456. Pointer-to-int transmutation during CTFE now produces a message like this: ``` = help: this code performed an operation that depends on the underlying bytes representing a pointer = help: the absolute address of a pointer is not known at compile-time, so such operations are not supported ``` r? ``@oli-obk``
This commit is contained in:
commit
81f3841cfb
46 changed files with 631 additions and 336 deletions
|
@ -10,6 +10,7 @@ use rustc_span::{Span, Symbol};
|
|||
use super::InterpCx;
|
||||
use crate::interpret::{
|
||||
struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
|
||||
UnsupportedOpInfo,
|
||||
};
|
||||
|
||||
/// The CTFE machine has some custom error kinds.
|
||||
|
@ -149,6 +150,18 @@ impl<'tcx> ConstEvalErr<'tcx> {
|
|||
if let Some(span_msg) = 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.
|
||||
if self.stacktrace.len() > 1 {
|
||||
// Helper closure to print duplicated lines.
|
||||
|
|
|
@ -2,8 +2,8 @@ use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr};
|
|||
use crate::interpret::eval_nullary_intrinsic;
|
||||
use crate::interpret::{
|
||||
intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
|
||||
Immediate, InternKind, InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking,
|
||||
StackPopCleanup,
|
||||
Immediate, InternKind, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy,
|
||||
RefTracking, StackPopCleanup,
|
||||
};
|
||||
|
||||
use rustc_hir::def::DefKind;
|
||||
|
@ -387,7 +387,9 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
|
|||
ecx.tcx,
|
||||
"it is undefined behavior to use this value",
|
||||
|diag| {
|
||||
diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
|
||||
if matches!(err.error, InterpError::UndefinedBehavior(_)) {
|
||||
diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
|
||||
}
|
||||
diag.note(&format!(
|
||||
"the raw bytes of the constant ({}",
|
||||
display_allocation(
|
||||
|
|
|
@ -134,7 +134,7 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval:
|
|||
alloc.mutability = Mutability::Not;
|
||||
};
|
||||
// link the alloc id to the actual allocation
|
||||
leftover_allocations.extend(alloc.relocations().iter().map(|&(_, alloc_id)| alloc_id));
|
||||
leftover_allocations.extend(alloc.provenance().iter().map(|&(_, alloc_id)| alloc_id));
|
||||
let alloc = tcx.intern_const_alloc(alloc);
|
||||
tcx.set_alloc_id_memory(alloc_id, alloc);
|
||||
None
|
||||
|
@ -191,10 +191,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
|
|||
return Ok(true);
|
||||
};
|
||||
|
||||
// If there are no relocations in this allocation, it does not contain references
|
||||
// If there is no provenance in this allocation, it does not contain references
|
||||
// that point to another allocation, and we can avoid the interning walk.
|
||||
if let Some(alloc) = self.ecx.get_ptr_alloc(mplace.ptr, size, align)? {
|
||||
if !alloc.has_relocations() {
|
||||
if !alloc.has_provenance() {
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
|
@ -233,8 +233,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
|
|||
}
|
||||
|
||||
fn visit_value(&mut self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
|
||||
// Handle Reference types, as these are the only relocations supported by const eval.
|
||||
// Raw pointers (and boxes) are handled by the `leftover_relocations` logic.
|
||||
// Handle Reference types, as these are the only types with provenance supported by const eval.
|
||||
// Raw pointers (and boxes) are handled by the `leftover_allocations` logic.
|
||||
let tcx = self.ecx.tcx;
|
||||
let ty = mplace.layout.ty;
|
||||
if let ty::Ref(_, referenced_ty, ref_mutability) = *ty.kind() {
|
||||
|
@ -410,7 +410,7 @@ pub fn intern_const_alloc_recursive<
|
|||
// references and a `leftover_allocations` set (where we only have a todo-list here).
|
||||
// So we hand-roll the interning logic here again.
|
||||
match intern_kind {
|
||||
// Statics may contain mutable allocations even behind relocations.
|
||||
// Statics may point to mutable allocations.
|
||||
// Even for immutable statics it would be ok to have mutable allocations behind
|
||||
// raw pointers, e.g. for `static FOO: *const AtomicUsize = &AtomicUsize::new(42)`.
|
||||
InternKind::Static(_) => {}
|
||||
|
@ -441,7 +441,7 @@ pub fn intern_const_alloc_recursive<
|
|||
}
|
||||
let alloc = tcx.intern_const_alloc(alloc);
|
||||
tcx.set_alloc_id_memory(alloc_id, alloc);
|
||||
for &(_, alloc_id) in alloc.inner().relocations().iter() {
|
||||
for &(_, alloc_id) in alloc.inner().provenance().iter() {
|
||||
if leftover_allocations.insert(alloc_id) {
|
||||
todo.push(alloc_id);
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
assert!(!layout.is_unsized());
|
||||
|
||||
let lhs = self.read_pointer(lhs)?;
|
||||
let rhs = self.read_pointer(rhs)?;
|
||||
let lhs_bytes = self.read_bytes_ptr(lhs, layout.size)?;
|
||||
let rhs_bytes = self.read_bytes_ptr(rhs, layout.size)?;
|
||||
let get_bytes = |this: &InterpCx<'mir, 'tcx, M>,
|
||||
op: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -315,7 +315,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
|
|||
/// cache the result. (This relies on `AllocMap::get_or` being able to add the
|
||||
/// owned allocation to the map even when the map is shared.)
|
||||
///
|
||||
/// This must only fail if `alloc` contains relocations.
|
||||
/// This must only fail if `alloc` contains provenance.
|
||||
fn adjust_allocation<'b>(
|
||||
ecx: &InterpCx<'mir, 'tcx, Self>,
|
||||
id: AllocId,
|
||||
|
|
|
@ -214,7 +214,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
self.allocate_raw_ptr(alloc, kind).unwrap()
|
||||
}
|
||||
|
||||
/// This can fail only of `alloc` contains relocations.
|
||||
/// This can fail only of `alloc` contains provenance.
|
||||
pub fn allocate_raw_ptr(
|
||||
&mut self,
|
||||
alloc: Allocation,
|
||||
|
@ -794,10 +794,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
todo.extend(static_roots);
|
||||
while let Some(id) = todo.pop() {
|
||||
if reachable.insert(id) {
|
||||
// This is a new allocation, add its relocations to `todo`.
|
||||
// This is a new allocation, add the allocation it points to to `todo`.
|
||||
if let Some((_, alloc)) = self.memory.alloc_map.get(id) {
|
||||
todo.extend(
|
||||
alloc.relocations().values().filter_map(|prov| prov.get_alloc_id()),
|
||||
alloc.provenance().values().filter_map(|prov| prov.get_alloc_id()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -833,7 +833,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> std::fmt::Debug for DumpAllocs<'a,
|
|||
allocs_to_print: &mut VecDeque<AllocId>,
|
||||
alloc: &Allocation<Prov, Extra>,
|
||||
) -> std::fmt::Result {
|
||||
for alloc_id in alloc.relocations().values().filter_map(|prov| prov.get_alloc_id()) {
|
||||
for alloc_id in alloc.provenance().values().filter_map(|prov| prov.get_alloc_id()) {
|
||||
allocs_to_print.push_back(alloc_id);
|
||||
}
|
||||
write!(fmt, "{}", display_allocation(tcx, alloc))
|
||||
|
@ -953,24 +953,25 @@ impl<'tcx, 'a, Prov: Provenance, Extra> AllocRef<'a, 'tcx, Prov, Extra> {
|
|||
}
|
||||
|
||||
/// `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
|
||||
.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))?)
|
||||
}
|
||||
|
||||
/// Returns whether the allocation has relocations for the entire range of the `AllocRef`.
|
||||
pub(crate) fn has_relocations(&self) -> bool {
|
||||
self.alloc.has_relocations(&self.tcx, self.range)
|
||||
/// Returns whether the allocation has provenance anywhere in the range of the `AllocRef`.
|
||||
pub(crate) fn has_provenance(&self) -> bool {
|
||||
self.alloc.range_has_provenance(&self.tcx, self.range)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
pub fn read_bytes_ptr(
|
||||
pub fn read_bytes_ptr_strip_provenance(
|
||||
&self,
|
||||
ptr: Pointer<Option<M::Provenance>>,
|
||||
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.)
|
||||
Ok(alloc_ref
|
||||
.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))?)
|
||||
}
|
||||
|
||||
|
@ -1078,17 +1079,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
return Ok(());
|
||||
};
|
||||
|
||||
// This checks relocation edges on the src, which needs to happen before
|
||||
// `prepare_relocation_copy`.
|
||||
let src_bytes = src_alloc
|
||||
.get_bytes_with_uninit_and_ptr(&tcx, src_range)
|
||||
.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
|
||||
// first copy the relocations to a temporary buffer, because
|
||||
// `get_bytes_mut` will clear the relocations, which is correct,
|
||||
// since we don't want to keep any relocations at the target.
|
||||
let relocations =
|
||||
src_alloc.prepare_relocation_copy(self, src_range, dest_offset, num_copies);
|
||||
// Checks provenance edges on the src, which needs to happen before
|
||||
// `prepare_provenance_copy`.
|
||||
if src_alloc.range_has_provenance(&tcx, alloc_range(src_range.start, Size::ZERO)) {
|
||||
throw_unsup!(PartialPointerCopy(Pointer::new(src_alloc_id, src_range.start)));
|
||||
}
|
||||
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
|
||||
// `get_bytes_mut` will clear the provenance, which is correct,
|
||||
// since we don't want to keep any provenance at the target.
|
||||
let provenance =
|
||||
src_alloc.prepare_provenance_copy(self, src_range, dest_offset, num_copies);
|
||||
// Prepare a copy of the initialization mask.
|
||||
let compressed = src_alloc.compress_uninit_range(src_range);
|
||||
|
||||
|
@ -1117,7 +1121,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
dest_alloc
|
||||
.write_uninit(&tcx, dest_range)
|
||||
.map_err(|e| e.to_interp_error(dest_alloc_id))?;
|
||||
// We can forget about the relocations, this is all not initialized anyway.
|
||||
// We can forget about the provenance, this is all not initialized anyway.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -1161,8 +1165,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
alloc_range(dest_offset, size), // just a single copy (i.e., not full `dest_range`)
|
||||
num_copies,
|
||||
);
|
||||
// copy the relocations to the destination
|
||||
dest_alloc.mark_relocation_range(relocations);
|
||||
// copy the provenance to the destination
|
||||
dest_alloc.mark_provenance_range(provenance);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -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!)
|
||||
pub fn read_str(&self, mplace: &MPlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx, &str> {
|
||||
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)))?;
|
||||
Ok(str)
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
//! into a place.
|
||||
//! All high-level functions to write to memory work on places as destinations.
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_middle::mir;
|
||||
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
|
||||
impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
|
||||
where
|
||||
Prov: Provenance + Eq + Hash + 'static,
|
||||
Prov: Provenance + 'static,
|
||||
M: Machine<'mir, 'tcx, Provenance = Prov>,
|
||||
{
|
||||
/// 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
|
||||
//! implement the logic on OpTy, and MPlaceTy calls that.
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
use rustc_middle::mir;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
|
@ -22,7 +20,7 @@ use super::{
|
|||
// FIXME: Working around https://github.com/rust-lang/rust/issues/54385
|
||||
impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
|
||||
where
|
||||
Prov: Provenance + Eq + Hash + 'static,
|
||||
Prov: Provenance + 'static,
|
||||
M: Machine<'mir, 'tcx, Provenance = Prov>,
|
||||
{
|
||||
//# Field access
|
||||
|
|
|
@ -20,9 +20,11 @@ use rustc_target::abi::{Abi, Scalar as ScalarAbi, Size, VariantIdx, Variants, Wr
|
|||
|
||||
use std::hash::Hash;
|
||||
|
||||
// for the validation errors
|
||||
use super::UndefinedBehaviorInfo::*;
|
||||
use super::{
|
||||
alloc_range, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy,
|
||||
Machine, MemPlaceMeta, OpTy, Scalar, ValueVisitor,
|
||||
CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine,
|
||||
MemPlaceMeta, OpTy, Scalar, ValueVisitor,
|
||||
};
|
||||
|
||||
macro_rules! throw_validation_failure {
|
||||
|
@ -60,6 +62,7 @@ macro_rules! throw_validation_failure {
|
|||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The patterns must be of type `UndefinedBehaviorInfo`.
|
||||
/// An additional expected parameter can also be added to the failure message:
|
||||
///
|
||||
/// ```
|
||||
|
@ -87,7 +90,7 @@ macro_rules! try_validation {
|
|||
// allocation here as this can only slow down builds that fail anyway.
|
||||
Err(e) => match e.kind() {
|
||||
$(
|
||||
$($p)|+ =>
|
||||
InterpError::UndefinedBehavior($($p)|+) =>
|
||||
throw_validation_failure!(
|
||||
$where,
|
||||
{ $( $what_fmt ),+ } $( expected { $( $expected_fmt ),+ } )?
|
||||
|
@ -313,8 +316,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
Ok(try_validation!(
|
||||
self.ecx.read_immediate(op),
|
||||
self.path,
|
||||
err_unsup!(ReadPointerAsBytes) => { "(potentially part of) a pointer" } expected { "{expected}" },
|
||||
err_ub!(InvalidUninitBytes(None)) => { "uninitialized memory" } expected { "{expected}" }
|
||||
InvalidUninitBytes(None) => { "uninitialized memory" } expected { "{expected}" }
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -339,18 +341,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
let (_ty, _trait) = try_validation!(
|
||||
self.ecx.get_ptr_vtable(vtable),
|
||||
self.path,
|
||||
err_ub!(DanglingIntPointer(..)) |
|
||||
err_ub!(InvalidVTablePointer(..)) =>
|
||||
DanglingIntPointer(..) |
|
||||
InvalidVTablePointer(..) =>
|
||||
{ "{vtable}" } expected { "a vtable pointer" },
|
||||
);
|
||||
// FIXME: check if the type/trait match what ty::Dynamic says?
|
||||
}
|
||||
ty::Slice(..) | ty::Str => {
|
||||
let _len = try_validation!(
|
||||
meta.unwrap_meta().to_machine_usize(self.ecx),
|
||||
self.path,
|
||||
err_unsup!(ReadPointerAsBytes) => { "non-integer slice length in wide pointer" },
|
||||
);
|
||||
let _len = meta.unwrap_meta().to_machine_usize(self.ecx)?;
|
||||
// We do not check that `len * elem_size <= isize::MAX`:
|
||||
// that is only required for references, and there it falls out of the
|
||||
// "dereferenceable" check performed by Stacked Borrows.
|
||||
|
@ -380,7 +378,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
let size_and_align = try_validation!(
|
||||
self.ecx.size_and_align_of_mplace(&place),
|
||||
self.path,
|
||||
err_ub!(InvalidMeta(msg)) => { "invalid {} metadata: {}", kind, msg },
|
||||
InvalidMeta(msg) => { "invalid {} metadata: {}", kind, msg },
|
||||
);
|
||||
let (size, align) = size_and_align
|
||||
// for the purpose of validity, consider foreign types to have
|
||||
|
@ -396,21 +394,21 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
|
||||
),
|
||||
self.path,
|
||||
err_ub!(AlignmentCheckFailed { required, has }) =>
|
||||
AlignmentCheckFailed { required, has } =>
|
||||
{
|
||||
"an unaligned {kind} (required {} byte alignment but found {})",
|
||||
required.bytes(),
|
||||
has.bytes()
|
||||
},
|
||||
err_ub!(DanglingIntPointer(0, _)) =>
|
||||
DanglingIntPointer(0, _) =>
|
||||
{ "a null {kind}" },
|
||||
err_ub!(DanglingIntPointer(i, _)) =>
|
||||
DanglingIntPointer(i, _) =>
|
||||
{ "a dangling {kind} (address {i:#x} is unallocated)" },
|
||||
err_ub!(PointerOutOfBounds { .. }) =>
|
||||
PointerOutOfBounds { .. } =>
|
||||
{ "a dangling {kind} (going beyond the bounds of its allocation)" },
|
||||
// This cannot happen during const-eval (because interning already detects
|
||||
// dangling pointers), but it can happen in Miri.
|
||||
err_ub!(PointerUseAfterFree(..)) =>
|
||||
PointerUseAfterFree(..) =>
|
||||
{ "a dangling {kind} (use-after-free)" },
|
||||
);
|
||||
// Do not allow pointers to uninhabited types.
|
||||
|
@ -498,7 +496,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
try_validation!(
|
||||
value.to_bool(),
|
||||
self.path,
|
||||
err_ub!(InvalidBool(..)) =>
|
||||
InvalidBool(..) =>
|
||||
{ "{:x}", value } expected { "a boolean" },
|
||||
);
|
||||
Ok(true)
|
||||
|
@ -508,7 +506,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
try_validation!(
|
||||
value.to_char(),
|
||||
self.path,
|
||||
err_ub!(InvalidChar(..)) =>
|
||||
InvalidChar(..) =>
|
||||
{ "{:x}", value } expected { "a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)" },
|
||||
);
|
||||
Ok(true)
|
||||
|
@ -567,8 +565,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
let _fn = try_validation!(
|
||||
self.ecx.get_ptr_fn(ptr),
|
||||
self.path,
|
||||
err_ub!(DanglingIntPointer(..)) |
|
||||
err_ub!(InvalidFunctionPointer(..)) =>
|
||||
DanglingIntPointer(..) |
|
||||
InvalidFunctionPointer(..) =>
|
||||
{ "{ptr}" } expected { "a function pointer" },
|
||||
);
|
||||
// FIXME: Check if the signature matches
|
||||
|
@ -683,12 +681,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
Ok(try_validation!(
|
||||
this.ecx.read_discriminant(op),
|
||||
this.path,
|
||||
err_ub!(InvalidTag(val)) =>
|
||||
InvalidTag(val) =>
|
||||
{ "{:x}", val } expected { "a valid enum tag" },
|
||||
err_ub!(InvalidUninitBytes(None)) =>
|
||||
InvalidUninitBytes(None) =>
|
||||
{ "uninitialized bytes" } expected { "a valid enum tag" },
|
||||
err_unsup!(ReadPointerAsBytes) =>
|
||||
{ "a pointer" } expected { "a valid enum tag" },
|
||||
)
|
||||
.1)
|
||||
})
|
||||
|
@ -828,10 +824,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 len = mplace.len(self.ecx)?;
|
||||
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,
|
||||
err_ub!(InvalidUninitBytes(..)) => { "uninitialized data in `str`" },
|
||||
err_unsup!(ReadPointerAsBytes) => { "a pointer in `str`" },
|
||||
InvalidUninitBytes(..) => { "uninitialized data in `str`" },
|
||||
);
|
||||
}
|
||||
ty::Array(tys, ..) | ty::Slice(tys)
|
||||
|
@ -879,9 +874,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
// 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");
|
||||
|
||||
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.
|
||||
Ok(()) => {}
|
||||
Ok(_) => {}
|
||||
// Some error happened, try to provide a more detailed description.
|
||||
Err(err) => {
|
||||
// For some errors we might be able to provide extra information.
|
||||
|
@ -899,9 +894,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
|
||||
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).
|
||||
_ => return Err(err),
|
||||
|
@ -942,14 +934,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
Ok(()) => Ok(()),
|
||||
// Pass through validation failures.
|
||||
Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err),
|
||||
// Also pass through InvalidProgram, those just indicate that we could not
|
||||
// validate and each caller will know best what to do with them.
|
||||
Err(err) if matches!(err.kind(), InterpError::InvalidProgram(_)) => Err(err),
|
||||
// Avoid other errors as those do not show *where* in the value the issue lies.
|
||||
Err(err) => {
|
||||
// Complain about any other kind of UB error -- those are bad because we'd like to
|
||||
// report them in a way that shows *where* in the value the issue lies.
|
||||
Err(err) if matches!(err.kind(), InterpError::UndefinedBehavior(_)) => {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue