1
Fork 0

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:
Dylan DPC 2022-04-09 12:52:05 +02:00 committed by GitHub
commit dfb4194e3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 107 additions and 67 deletions

View file

@ -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))

View file

@ -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

View file

@ -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};

View file

@ -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()
},
)