Rollup merge of #95785 - RalfJung:interpret-size-mismatch, r=oli-obk
interpret: err instead of ICE on size mismatches in to_bits_or_ptr_internal We did this a while ago already for `to_i32()` and friends, but missed this one. That became quite annoying when I was debugging an ICE caused by `read_pointer` in a Miri shim where the code was passing an argument at the wrong type. Having `scalar_to_ptr` be fallible is consistent with all the other `Scalar::to_*` methods being fallible. I added `unwrap` only in code outside the interpreter, which is no worse off than before now in terms of panics. r? ````@oli-obk````
This commit is contained in:
commit
dfb4194e3b
16 changed files with 107 additions and 67 deletions
|
@ -15,8 +15,8 @@ use rustc_target::abi::{Align, HasDataLayout, Size};
|
|||
|
||||
use super::{
|
||||
read_target_uint, write_target_uint, AllocId, InterpError, InterpResult, Pointer, Provenance,
|
||||
ResourceExhaustionInfo, Scalar, ScalarMaybeUninit, UndefinedBehaviorInfo, UninitBytesAccess,
|
||||
UnsupportedOpInfo,
|
||||
ResourceExhaustionInfo, Scalar, ScalarMaybeUninit, ScalarSizeMismatch, UndefinedBehaviorInfo,
|
||||
UninitBytesAccess, UnsupportedOpInfo,
|
||||
};
|
||||
use crate::ty;
|
||||
|
||||
|
@ -81,6 +81,8 @@ impl<'tcx, Tag, Extra> ConstAllocation<'tcx, Tag, Extra> {
|
|||
/// is added when converting to `InterpError`.
|
||||
#[derive(Debug)]
|
||||
pub enum AllocError {
|
||||
/// A scalar had the wrong size.
|
||||
ScalarSizeMismatch(ScalarSizeMismatch),
|
||||
/// Encountered a pointer where we needed raw bytes.
|
||||
ReadPointerAsBytes,
|
||||
/// Partially overwriting a pointer.
|
||||
|
@ -90,10 +92,19 @@ pub enum AllocError {
|
|||
}
|
||||
pub type AllocResult<T = ()> = Result<T, AllocError>;
|
||||
|
||||
impl From<ScalarSizeMismatch> for AllocError {
|
||||
fn from(s: ScalarSizeMismatch) -> Self {
|
||||
AllocError::ScalarSizeMismatch(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl AllocError {
|
||||
pub fn to_interp_error<'tcx>(self, alloc_id: AllocId) -> InterpError<'tcx> {
|
||||
use AllocError::*;
|
||||
match self {
|
||||
ScalarSizeMismatch(s) => {
|
||||
InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ScalarSizeMismatch(s))
|
||||
}
|
||||
ReadPointerAsBytes => InterpError::Unsupported(UnsupportedOpInfo::ReadPointerAsBytes),
|
||||
PartialPointerOverwrite(offset) => InterpError::Unsupported(
|
||||
UnsupportedOpInfo::PartialPointerOverwrite(Pointer::new(alloc_id, offset)),
|
||||
|
@ -425,7 +436,7 @@ impl<Tag: Provenance, Extra> Allocation<Tag, Extra> {
|
|||
|
||||
// `to_bits_or_ptr_internal` is the right method because we just want to store this data
|
||||
// as-is into memory.
|
||||
let (bytes, provenance) = match val.to_bits_or_ptr_internal(range.size) {
|
||||
let (bytes, provenance) = match val.to_bits_or_ptr_internal(range.size)? {
|
||||
Err(val) => {
|
||||
let (provenance, offset) = val.into_parts();
|
||||
(u128::from(offset.bytes()), Some(provenance))
|
||||
|
|
|
@ -221,6 +221,13 @@ pub struct UninitBytesAccess {
|
|||
pub uninit_size: Size,
|
||||
}
|
||||
|
||||
/// Information about a size mismatch.
|
||||
#[derive(Debug)]
|
||||
pub struct ScalarSizeMismatch {
|
||||
pub target_size: u64,
|
||||
pub data_size: u64,
|
||||
}
|
||||
|
||||
/// Error information for when the program caused Undefined Behavior.
|
||||
pub enum UndefinedBehaviorInfo<'tcx> {
|
||||
/// Free-form case. Only for errors that are never caught!
|
||||
|
@ -298,10 +305,7 @@ pub enum UndefinedBehaviorInfo<'tcx> {
|
|||
/// Working with a local that is not currently live.
|
||||
DeadLocal,
|
||||
/// Data size is not equal to target size.
|
||||
ScalarSizeMismatch {
|
||||
target_size: u64,
|
||||
data_size: u64,
|
||||
},
|
||||
ScalarSizeMismatch(ScalarSizeMismatch),
|
||||
/// A discriminant of an uninhabited enum variant is written.
|
||||
UninhabitedEnumVariantWritten,
|
||||
}
|
||||
|
@ -408,7 +412,7 @@ impl fmt::Display for UndefinedBehaviorInfo<'_> {
|
|||
"using uninitialized data, but this operation requires initialized memory"
|
||||
),
|
||||
DeadLocal => write!(f, "accessing a dead local variable"),
|
||||
ScalarSizeMismatch { target_size, data_size } => write!(
|
||||
ScalarSizeMismatch(self::ScalarSizeMismatch { target_size, data_size }) => write!(
|
||||
f,
|
||||
"scalar size mismatch: expected {} bytes but got {} bytes instead",
|
||||
target_size, data_size
|
||||
|
|
|
@ -120,7 +120,8 @@ use crate::ty::{self, Instance, Ty, TyCtxt};
|
|||
pub use self::error::{
|
||||
struct_error, CheckInAllocMsg, ErrorHandled, EvalToAllocationRawResult, EvalToConstValueResult,
|
||||
InterpError, InterpErrorInfo, InterpResult, InvalidProgramInfo, MachineStopType,
|
||||
ResourceExhaustionInfo, UndefinedBehaviorInfo, UninitBytesAccess, UnsupportedOpInfo,
|
||||
ResourceExhaustionInfo, ScalarSizeMismatch, UndefinedBehaviorInfo, UninitBytesAccess,
|
||||
UnsupportedOpInfo,
|
||||
};
|
||||
|
||||
pub use self::value::{get_slice_bytes, ConstAlloc, ConstValue, Scalar, ScalarMaybeUninit};
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::ty::{Lift, ParamEnv, ScalarInt, Ty, TyCtxt};
|
|||
|
||||
use super::{
|
||||
AllocId, AllocRange, ConstAllocation, InterpResult, Pointer, PointerArithmetic, Provenance,
|
||||
ScalarSizeMismatch,
|
||||
};
|
||||
|
||||
/// Represents the result of const evaluation via the `eval_to_allocation` query.
|
||||
|
@ -300,16 +301,29 @@ impl<Tag> Scalar<Tag> {
|
|||
///
|
||||
/// This method only exists for the benefit of low-level operations that truly need to treat the
|
||||
/// scalar in whatever form it is.
|
||||
///
|
||||
/// This throws UB (instead of ICEing) on a size mismatch since size mismatches can arise in
|
||||
/// Miri when someone declares a function that we shim (such as `malloc`) with a wrong type.
|
||||
#[inline]
|
||||
pub fn to_bits_or_ptr_internal(self, target_size: Size) -> Result<u128, Pointer<Tag>> {
|
||||
pub fn to_bits_or_ptr_internal(
|
||||
self,
|
||||
target_size: Size,
|
||||
) -> Result<Result<u128, Pointer<Tag>>, ScalarSizeMismatch> {
|
||||
assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST");
|
||||
match self {
|
||||
Scalar::Int(int) => Ok(int.assert_bits(target_size)),
|
||||
Ok(match self {
|
||||
Scalar::Int(int) => Ok(int.to_bits(target_size).map_err(|size| {
|
||||
ScalarSizeMismatch { target_size: target_size.bytes(), data_size: size.bytes() }
|
||||
})?),
|
||||
Scalar::Ptr(ptr, sz) => {
|
||||
assert_eq!(target_size.bytes(), u64::from(sz));
|
||||
if target_size.bytes() != sz.into() {
|
||||
return Err(ScalarSizeMismatch {
|
||||
target_size: target_size.bytes(),
|
||||
data_size: sz.into(),
|
||||
});
|
||||
}
|
||||
Err(ptr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,10 +362,10 @@ impl<'tcx, Tag: Provenance> Scalar<Tag> {
|
|||
assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST");
|
||||
self.try_to_int().map_err(|_| err_unsup!(ReadPointerAsBytes))?.to_bits(target_size).map_err(
|
||||
|size| {
|
||||
err_ub!(ScalarSizeMismatch {
|
||||
err_ub!(ScalarSizeMismatch(ScalarSizeMismatch {
|
||||
target_size: target_size.bytes(),
|
||||
data_size: size.bytes(),
|
||||
})
|
||||
}))
|
||||
.into()
|
||||
},
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue