| use std::any::Any; | |
| use std::backtrace::Backtrace; | |
| use std::borrow::Cow; | |
| use std::{convert, fmt, mem, ops}; | |
| use either::Either; | |
| use rustc_abi::{Align, Size, VariantIdx}; | |
| use rustc_data_structures::sync::Lock; | |
| use rustc_errors::{DiagArgValue, ErrorGuaranteed, IntoDiagArg}; | |
| use rustc_macros::{StableHash, TyDecodable, TyEncodable}; | |
| use rustc_session::CtfeBacktrace; | |
| use rustc_span::def_id::DefId; | |
| use rustc_span::{DUMMY_SP, Span, Symbol}; | |
| use super::{AllocId, AllocRange, ConstAllocation, Pointer, Scalar}; | |
| use crate::error; | |
| use crate::mir::interpret::CtfeProvenance; | |
| use crate::mir::{ConstAlloc, ConstValue}; | |
| use crate::ty::{self, Ty, TyCtxt, ValTree, layout, tls}; | |
| #[derive(Debug, Copy, Clone, PartialEq, Eq, StableHash, TyEncodable, TyDecodable)] | |
| pub enum ErrorHandled { | |
| /// Already reported an error for this evaluation, and the compilation is | |
| /// *guaranteed* to fail. Warnings/lints *must not* produce `Reported`. | |
| Reported(ReportedErrorInfo, Span), | |
| /// Don't emit an error, the evaluation failed because the MIR was generic | |
| /// and the args didn't fully monomorphize it. | |
| TooGeneric(Span), | |
| } | |
| impl From<ReportedErrorInfo> for ErrorHandled { | |
| #[inline] | |
| fn from(error: ReportedErrorInfo) -> ErrorHandled { | |
| ErrorHandled::Reported(error, DUMMY_SP) | |
| } | |
| } | |
| impl ErrorHandled { | |
| pub(crate) fn with_span(self, span: Span) -> Self { | |
| match self { | |
| ErrorHandled::Reported(err, _span) => ErrorHandled::Reported(err, span), | |
| ErrorHandled::TooGeneric(_span) => ErrorHandled::TooGeneric(span), | |
| } | |
| } | |
| pub fn emit_note(&self, tcx: TyCtxt<'_>) { | |
| match self { | |
| &ErrorHandled::Reported(err, span) => { | |
| if !err.allowed_in_infallible && !span.is_dummy() { | |
| tcx.dcx().emit_note(error::ErroneousConstant { span }); | |
| } | |
| } | |
| &ErrorHandled::TooGeneric(_) => {} | |
| } | |
| } | |
| } | |
| #[derive(Debug, Copy, Clone, PartialEq, Eq, StableHash, TyEncodable, TyDecodable)] | |
| pub struct ReportedErrorInfo { | |
| error: ErrorGuaranteed, | |
| /// Whether this error is allowed to show up even in otherwise "infallible" promoteds. | |
| /// This is for things like overflows during size computation or resource exhaustion. | |
| allowed_in_infallible: bool, | |
| } | |
| impl ReportedErrorInfo { | |
| #[inline] | |
| pub fn const_eval_error(error: ErrorGuaranteed) -> ReportedErrorInfo { | |
| ReportedErrorInfo { allowed_in_infallible: false, error } | |
| } | |
| /// Use this when the error that led to this is *not* a const-eval error | |
| /// (e.g., a layout or type checking error). | |
| #[inline] | |
| pub fn non_const_eval_error(error: ErrorGuaranteed) -> ReportedErrorInfo { | |
| ReportedErrorInfo { allowed_in_infallible: true, error } | |
| } | |
| /// Use this when the error that led to this *is* a const-eval error, but | |
| /// we do allow it to occur in infallible constants (e.g., resource exhaustion). | |
| #[inline] | |
| pub fn allowed_in_infallible(error: ErrorGuaranteed) -> ReportedErrorInfo { | |
| ReportedErrorInfo { allowed_in_infallible: true, error } | |
| } | |
| pub fn is_allowed_in_infallible(&self) -> bool { | |
| self.allowed_in_infallible | |
| } | |
| } | |
| impl From<ReportedErrorInfo> for ErrorGuaranteed { | |
| #[inline] | |
| fn from(val: ReportedErrorInfo) -> Self { | |
| val.error | |
| } | |
| } | |
| /// An error type for the `const_to_valtree` query. Some error should be reported with a "use-site span", | |
| /// which means the query cannot emit the error, so those errors are represented as dedicated variants here. | |
| #[derive(Debug, Copy, Clone, PartialEq, Eq, StableHash, TyEncodable, TyDecodable)] | |
| pub enum ValTreeCreationError<'tcx> { | |
| /// The constant is too big to be valtree'd. | |
| NodesOverflow, | |
| /// The constant references mutable or external memory, so it cannot be valtree'd. | |
| InvalidConst, | |
| /// Values of this type, or this particular value, are not supported as valtrees. | |
| NonSupportedType(Ty<'tcx>), | |
| /// Trying to valtree this constant would cause the valtree to have cycles. | |
| CyclicConst, | |
| /// The error has already been handled by const evaluation. | |
| ErrorHandled(ErrorHandled), | |
| } | |
| impl<'tcx> From<ErrorHandled> for ValTreeCreationError<'tcx> { | |
| fn from(err: ErrorHandled) -> Self { | |
| ValTreeCreationError::ErrorHandled(err) | |
| } | |
| } | |
| impl<'tcx> From<InterpErrorInfo<'tcx>> for ValTreeCreationError<'tcx> { | |
| fn from(err: InterpErrorInfo<'tcx>) -> Self { | |
| // An error occurred outside the const-eval query, as part of constructing the valtree. We | |
| // don't currently preserve the details of this error, since `InterpErrorInfo` cannot be put | |
| // into a query result and it can only be access of some mutable or external memory. | |
| let (_kind, backtrace) = err.into_parts(); | |
| backtrace.print_backtrace(); | |
| ValTreeCreationError::InvalidConst | |
| } | |
| } | |
| impl<'tcx> ValTreeCreationError<'tcx> { | |
| pub(crate) fn with_span(self, span: Span) -> Self { | |
| use ValTreeCreationError::*; | |
| match self { | |
| ErrorHandled(handled) => ErrorHandled(handled.with_span(span)), | |
| other => other, | |
| } | |
| } | |
| } | |
| pub type EvalToAllocationRawResult<'tcx> = Result<ConstAlloc<'tcx>, ErrorHandled>; | |
| pub type EvalStaticInitializerRawResult<'tcx> = Result<ConstAllocation<'tcx>, ErrorHandled>; | |
| pub type EvalToConstValueResult<'tcx> = Result<ConstValue, ErrorHandled>; | |
| pub type EvalToValTreeResult<'tcx> = Result<ValTree<'tcx>, ValTreeCreationError<'tcx>>; | |
| #[cfg(target_pointer_width = "64")] | |
| rustc_data_structures::static_assert_size!(InterpErrorInfo<'_>, 8); | |
| /// Packages the kind of error we got from the const code interpreter | |
| /// up with a Rust-level backtrace of where the error occurred. | |
| /// These should always be constructed by calling `.into()` on | |
| /// an `InterpError`. In `rustc_mir::interpret`, we have `throw_err_*` | |
| /// macros for this. | |
| /// | |
| /// Interpreter errors must *not* be silently discarded (that will lead to a panic). Instead, | |
| /// explicitly call `discard_err` if this is really the right thing to do. Note that if | |
| /// this happens during const-eval or in Miri, it could lead to a UB error being lost! | |
| #[derive(Debug)] | |
| pub struct InterpErrorInfo<'tcx>(Box<InterpErrorInfoInner<'tcx>>); | |
| #[derive(Debug)] | |
| struct InterpErrorInfoInner<'tcx> { | |
| kind: InterpErrorKind<'tcx>, | |
| backtrace: InterpErrorBacktrace, | |
| } | |
| #[derive(Debug)] | |
| pub struct InterpErrorBacktrace { | |
| backtrace: Option<Box<Backtrace>>, | |
| } | |
| impl InterpErrorBacktrace { | |
| pub fn new() -> InterpErrorBacktrace { | |
| let capture_backtrace = tls::with_opt(|tcx| { | |
| if let Some(tcx) = tcx { | |
| *Lock::borrow(&tcx.sess.ctfe_backtrace) | |
| } else { | |
| CtfeBacktrace::Disabled | |
| } | |
| }); | |
| let backtrace = match capture_backtrace { | |
| CtfeBacktrace::Disabled => None, | |
| CtfeBacktrace::Capture => Some(Box::new(Backtrace::force_capture())), | |
| CtfeBacktrace::Immediate => { | |
| // Print it now. | |
| let backtrace = Backtrace::force_capture(); | |
| print_backtrace(&backtrace); | |
| None | |
| } | |
| }; | |
| InterpErrorBacktrace { backtrace } | |
| } | |
| pub fn print_backtrace(&self) { | |
| if let Some(backtrace) = self.backtrace.as_ref() { | |
| print_backtrace(backtrace); | |
| } | |
| } | |
| } | |
| impl<'tcx> InterpErrorInfo<'tcx> { | |
| pub fn into_parts(self) -> (InterpErrorKind<'tcx>, InterpErrorBacktrace) { | |
| let InterpErrorInfo(InterpErrorInfoInner { kind, backtrace }) = self; | |
| (kind, backtrace) | |
| } | |
| pub fn into_kind(self) -> InterpErrorKind<'tcx> { | |
| self.0.kind | |
| } | |
| pub fn from_parts(kind: InterpErrorKind<'tcx>, backtrace: InterpErrorBacktrace) -> Self { | |
| Self(Box::new(InterpErrorInfoInner { kind, backtrace })) | |
| } | |
| #[inline] | |
| pub fn kind(&self) -> &InterpErrorKind<'tcx> { | |
| &self.0.kind | |
| } | |
| /// Turn the given error into a human-readable string. Expects the string to be printed, so if | |
| /// `RUSTC_CTFE_BACKTRACE` is set this will show a backtrace of the rustc internals that | |
| /// triggered the error. | |
| /// | |
| /// This is NOT the preferred way to render an error; use `report` from `const_eval` instead. | |
| /// However, this is useful when error messages appear in ICEs. | |
| pub fn to_string(&self) -> String { | |
| self.0.backtrace.print_backtrace(); | |
| self.0.kind.to_string() | |
| } | |
| } | |
| fn print_backtrace(backtrace: &Backtrace) { | |
| eprintln!("\n\nAn error occurred in the MIR interpreter:\n{backtrace}"); | |
| } | |
| impl From<ErrorHandled> for InterpErrorInfo<'_> { | |
| fn from(err: ErrorHandled) -> Self { | |
| InterpErrorKind::InvalidProgram(match err { | |
| ErrorHandled::Reported(r, _span) => InvalidProgramInfo::AlreadyReported(r), | |
| ErrorHandled::TooGeneric(_span) => InvalidProgramInfo::TooGeneric, | |
| }) | |
| .into() | |
| } | |
| } | |
| impl<'tcx> From<InterpErrorKind<'tcx>> for InterpErrorInfo<'tcx> { | |
| fn from(kind: InterpErrorKind<'tcx>) -> Self { | |
| InterpErrorInfo(Box::new(InterpErrorInfoInner { | |
| kind, | |
| backtrace: InterpErrorBacktrace::new(), | |
| })) | |
| } | |
| } | |
| /// Details of why a pointer had to be in-bounds. | |
| #[derive(Debug, Copy, Clone)] | |
| pub enum CheckInAllocMsg { | |
| /// We are accessing memory. | |
| MemoryAccess, | |
| /// We are doing pointer arithmetic. | |
| InboundsPointerArithmetic, | |
| /// None of the above -- generic/unspecific inbounds test. | |
| /// The string is the subject of the test, e.g. "pointer". | |
| Dereferenceable(&'static str), | |
| } | |
| impl fmt::Display for CheckInAllocMsg { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| use CheckInAllocMsg::*; | |
| match self { | |
| MemoryAccess => write!(f, "memory access failed"), | |
| InboundsPointerArithmetic => write!(f, "in-bounds pointer arithmetic failed"), | |
| Dereferenceable(what) => write!(f, "{what} not dereferenceable"), | |
| } | |
| } | |
| } | |
| /// Details of which pointer is not aligned. | |
| #[derive(Debug, Copy, Clone)] | |
| pub enum CheckAlignMsg { | |
| /// The accessed pointer did not have proper alignment. | |
| AccessedPtr, | |
| /// The access occurred with a place that was based on a misaligned pointer. | |
| BasedOn, | |
| } | |
| #[derive(Debug, Copy, Clone)] | |
| pub enum InvalidMetaKind { | |
| /// Size of a `[T]` is too big | |
| SliceTooBig, | |
| /// Size of a DST is too big | |
| TooBig, | |
| } | |
| impl IntoDiagArg for InvalidMetaKind { | |
| fn into_diag_arg(self, _: &mut Option<std::path::PathBuf>) -> DiagArgValue { | |
| DiagArgValue::Str(Cow::Borrowed(match self { | |
| InvalidMetaKind::SliceTooBig => "slice_too_big", | |
| InvalidMetaKind::TooBig => "too_big", | |
| })) | |
| } | |
| } | |
| /// Details of an access to uninitialized bytes / bad pointer bytes where it is not allowed. | |
| #[derive(Debug, Clone, Copy)] | |
| pub struct BadBytesAccess { | |
| /// Range of the original memory access. | |
| pub access: AllocRange, | |
| /// Range of the bad memory that was encountered. (Might not be maximal.) | |
| pub bad: AllocRange, | |
| } | |
| /// Information about a size mismatch. | |
| #[derive(Debug)] | |
| pub struct ScalarSizeMismatch { | |
| pub target_size: u64, | |
| pub data_size: u64, | |
| } | |
| /// Information about a misaligned pointer. | |
| #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] | |
| pub struct Misalignment { | |
| pub has: Align, | |
| pub required: Align, | |
| } | |
| /// Error information for when the program caused Undefined Behavior. | |
| #[derive(Debug)] | |
| pub enum UndefinedBehaviorInfo<'tcx> { | |
| /// Free-form case. Only for errors that are never caught! Used by miri | |
| Ub(String), | |
| /// Validation error. | |
| ValidationError { | |
| orig_ty: Ty<'tcx>, | |
| path: Option<String>, | |
| msg: String, | |
| ptr_bytes_warning: bool, | |
| }, | |
| /// Unreachable code was executed. | |
| Unreachable, | |
| /// A slice/array index projection went out-of-bounds. | |
| BoundsCheckFailed { len: u64, index: u64 }, | |
| /// Something was divided by 0 (x / 0). | |
| DivisionByZero, | |
| /// Something was "remainded" by 0 (x % 0). | |
| RemainderByZero, | |
| /// Signed division overflowed (INT_MIN / -1). | |
| DivisionOverflow, | |
| /// Signed remainder overflowed (INT_MIN % -1). | |
| RemainderOverflow, | |
| /// Overflowing inbounds pointer arithmetic. | |
| PointerArithOverflow, | |
| /// Overflow in arithmetic that may not overflow. | |
| ArithOverflow { intrinsic: Symbol }, | |
| /// Shift by too much. | |
| ShiftOverflow { intrinsic: Symbol, shift_amount: Either<u128, i128> }, | |
| /// Invalid metadata in a wide pointer | |
| InvalidMeta(InvalidMetaKind), | |
| /// Reading a C string that does not end within its allocation. | |
| UnterminatedCString(Pointer<AllocId>), | |
| /// Using a pointer after it got freed. | |
| PointerUseAfterFree(AllocId, CheckInAllocMsg), | |
| /// Used a pointer outside the bounds it is valid for. | |
| PointerOutOfBounds { | |
| alloc_id: AllocId, | |
| alloc_size: Size, | |
| ptr_offset: i64, | |
| /// The size of the memory range that was expected to be in-bounds. | |
| inbounds_size: i64, | |
| msg: CheckInAllocMsg, | |
| }, | |
| /// Using an integer as a pointer in the wrong way. | |
| DanglingIntPointer { | |
| addr: u64, | |
| /// The size of the memory range that was expected to be in-bounds (or 0 if we need an | |
| /// allocation but not any actual memory there, e.g. for function pointers). | |
| inbounds_size: i64, | |
| msg: CheckInAllocMsg, | |
| }, | |
| /// Used a pointer with bad alignment. | |
| AlignmentCheckFailed(Misalignment, CheckAlignMsg), | |
| /// Writing to read-only memory. | |
| WriteToReadOnly(AllocId), | |
| /// Trying to access the data behind a function pointer. | |
| DerefFunctionPointer(AllocId), | |
| /// Trying to access the data behind a vtable pointer. | |
| DerefVTablePointer(AllocId), | |
| /// Trying to access the data behind a va_list pointer. | |
| DerefVaListPointer(AllocId), | |
| /// Trying to access the actual type id. | |
| DerefTypeIdPointer(AllocId), | |
| /// Using a non-boolean `u8` as bool. | |
| InvalidBool(u8), | |
| /// Using a non-character `u32` as character. | |
| InvalidChar(u32), | |
| /// The tag of an enum does not encode an actual discriminant. | |
| InvalidTag(Scalar<AllocId>), | |
| /// Using a pointer-not-to-a-function as function pointer. | |
| InvalidFunctionPointer(Pointer<AllocId>), | |
| /// Using a pointer-not-to-a-va-list as variable argument list pointer. | |
| InvalidVaListPointer(Pointer<AllocId>), | |
| /// Using a pointer-not-to-a-vtable as vtable pointer. | |
| InvalidVTablePointer(Pointer<Option<AllocId>>), | |
| /// Using a vtable for the wrong trait. | |
| InvalidVTableTrait { | |
| /// The vtable that was actually referenced by the wide pointer metadata. | |
| vtable_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, | |
| /// The vtable that was expected at the point in MIR that it was accessed. | |
| expected_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, | |
| }, | |
| /// Using a string that is not valid UTF-8, | |
| InvalidStr(std::str::Utf8Error), | |
| /// Using uninitialized data where it is not allowed. | |
| InvalidUninitBytes(Option<(AllocId, BadBytesAccess)>), | |
| /// Working with a local that is not currently live. | |
| DeadLocal, | |
| /// Data size is not equal to target size. | |
| ScalarSizeMismatch(ScalarSizeMismatch), | |
| /// A discriminant of an uninhabited enum variant is written. | |
| UninhabitedEnumVariantWritten(VariantIdx), | |
| /// An uninhabited enum variant is projected. | |
| UninhabitedEnumVariantRead(Option<VariantIdx>), | |
| /// Trying to set discriminant to the niched variant, but the value does not match. | |
| InvalidNichedEnumVariantWritten { enum_ty: Ty<'tcx> }, | |
| /// ABI-incompatible argument types. | |
| AbiMismatchArgument { | |
| /// The index of the argument whose type is wrong. | |
| arg_idx: usize, | |
| caller_ty: Ty<'tcx>, | |
| callee_ty: Ty<'tcx>, | |
| }, | |
| /// ABI-incompatible return types. | |
| AbiMismatchReturn { caller_ty: Ty<'tcx>, callee_ty: Ty<'tcx> }, | |
| /// `va_arg` was called on an exhausted `VaList`. | |
| VaArgOutOfBounds, | |
| /// The caller and callee disagree on whether they are c-variadic or not. | |
| CVariadicMismatch { caller_is_c_variadic: bool, callee_is_c_variadic: bool }, | |
| /// The caller and callee disagree on the number of fixed (i.e. non-c-variadic) arguments. | |
| CVariadicFixedCountMismatch { caller: u32, callee: u32 }, | |
| } | |
| impl<'tcx> fmt::Display for UndefinedBehaviorInfo<'tcx> { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| use UndefinedBehaviorInfo::*; | |
| fn fmt_in_alloc_attempt( | |
| f: &mut fmt::Formatter<'_>, | |
| msg: CheckInAllocMsg, | |
| inbounds_size: i64, | |
| ) -> fmt::Result { | |
| let inbounds_size_fmt = if inbounds_size == 1 { | |
| format_args!("1 byte") | |
| } else { | |
| format_args!("{inbounds_size} bytes") | |
| }; | |
| write!(f, "{msg}: ")?; | |
| match msg { | |
| CheckInAllocMsg::MemoryAccess => { | |
| write!(f, "attempting to access {inbounds_size_fmt}") | |
| } | |
| CheckInAllocMsg::InboundsPointerArithmetic => { | |
| write!(f, "attempting to offset pointer by {inbounds_size_fmt}") | |
| } | |
| CheckInAllocMsg::Dereferenceable(what) if inbounds_size == 0 => { | |
| write!(f, "{what} must point to some allocation") | |
| } | |
| CheckInAllocMsg::Dereferenceable(what) => { | |
| write!(f, "{what} must be dereferenceable for {inbounds_size_fmt}") | |
| } | |
| } | |
| } | |
| match self { | |
| Ub(msg) => write!(f, "{msg}"), | |
| ValidationError { orig_ty, path: None, msg, .. } => { | |
| write!(f, "constructing invalid value of type {orig_ty}: {msg}") | |
| } | |
| ValidationError { orig_ty, path: Some(path), msg, .. } => { | |
| write!(f, "constructing invalid value of type {orig_ty}: at {path}, {msg}") | |
| } | |
| Unreachable => write!(f, "entering unreachable code"), | |
| BoundsCheckFailed { len, index } => { | |
| write!(f, "indexing out of bounds: the len is {len} but the index is {index}") | |
| } | |
| DivisionByZero => write!(f, "dividing by zero"), | |
| RemainderByZero => write!(f, "calculating the remainder with a divisor of zero"), | |
| DivisionOverflow => write!(f, "overflow in signed division (dividing MIN by -1)"), | |
| RemainderOverflow => write!(f, "overflow in signed remainder (dividing MIN by -1)"), | |
| PointerArithOverflow => write!( | |
| f, | |
| "overflowing pointer arithmetic: the total offset in bytes does not fit in an `isize`" | |
| ), | |
| ArithOverflow { intrinsic } => write!(f, "arithmetic overflow in `{intrinsic}`"), | |
| ShiftOverflow { shift_amount, intrinsic } => { | |
| write!(f, "overflowing shift by {shift_amount} in `{intrinsic}`") | |
| } | |
| InvalidMeta(InvalidMetaKind::SliceTooBig) => write!( | |
| f, | |
| "invalid metadata in wide pointer: slice is bigger than largest supported object" | |
| ), | |
| InvalidMeta(InvalidMetaKind::TooBig) => write!( | |
| f, | |
| "invalid metadata in wide pointer: total size is bigger than largest supported object" | |
| ), | |
| UnterminatedCString(ptr) => write!( | |
| f, | |
| "reading a null-terminated string starting at {ptr} with no null found before end of allocation" | |
| ), | |
| PointerUseAfterFree(alloc_id, msg) => { | |
| write!(f, "{msg}: {alloc_id} has been freed, so this pointer is dangling") | |
| } | |
| &PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, inbounds_size, msg } => { | |
| fmt_in_alloc_attempt(f, msg, inbounds_size)?; | |
| write!(f, ", but got ")?; | |
| // Write pointer. Offset might be negative so we cannot use the normal `impl Display | |
| // for Pointer`. | |
| write!(f, "{}", alloc_id)?; | |
| if ptr_offset > 0 { | |
| write!(f, "+{:#x}", ptr_offset)?; | |
| } else if ptr_offset < 0 { | |
| write!(f, "-{:#x}", ptr_offset.unsigned_abs())?; | |
| } | |
| // Write why it is invalid. | |
| write!(f, " which ")?; | |
| if ptr_offset < 0 { | |
| write!(f, "points to before the beginning of the allocation") | |
| } else if inbounds_size < 0 { | |
| // We expected the ptr to have memory to its left, but it does not. | |
| if ptr_offset == 0 { | |
| write!(f, "is at the beginning of the allocation") | |
| } else { | |
| write!(f, "is only {ptr_offset} bytes from the beginning of the allocation") | |
| } | |
| } else { | |
| let ptr_offset = ptr_offset as u64; | |
| let alloc_size = alloc_size.bytes(); | |
| if ptr_offset >= alloc_size { | |
| let size = if alloc_size == 1 { | |
| format_args!("1 byte") | |
| } else { | |
| format_args!("{alloc_size} bytes") | |
| }; | |
| write!(f, "is at or beyond the end of the allocation of size {size}",) | |
| } else { | |
| let dist_to_end = alloc_size - ptr_offset; | |
| let dist = if dist_to_end == 1 { | |
| format_args!("1 byte") | |
| } else { | |
| format_args!("{dist_to_end} bytes") | |
| }; | |
| write!(f, "is only {dist} from the end of the allocation",) | |
| } | |
| } | |
| } | |
| &DanglingIntPointer { addr: 0, inbounds_size, msg } => { | |
| fmt_in_alloc_attempt(f, msg, inbounds_size)?; | |
| write!(f, ", but got null pointer") | |
| } | |
| &DanglingIntPointer { addr, inbounds_size, msg } => { | |
| fmt_in_alloc_attempt(f, msg, inbounds_size)?; | |
| write!( | |
| f, | |
| ", but got {ptr} which is a dangling pointer (it has no provenance)", | |
| ptr = Pointer::<Option<CtfeProvenance>>::without_provenance(addr), | |
| ) | |
| } | |
| AlignmentCheckFailed(misalign, msg) => { | |
| write!( | |
| f, | |
| "{acc} with alignment {has}, but alignment {required} is required", | |
| acc = match msg { | |
| CheckAlignMsg::AccessedPtr => "accessing memory", | |
| CheckAlignMsg::BasedOn => "accessing memory based on pointer", | |
| }, | |
| has = misalign.has.bytes(), | |
| required = misalign.required.bytes(), | |
| ) | |
| } | |
| WriteToReadOnly(alloc) => write!(f, "writing to {alloc} which is read-only"), | |
| DerefFunctionPointer(alloc) => { | |
| write!(f, "accessing {alloc} which contains a function") | |
| } | |
| DerefVTablePointer(alloc) => write!(f, "accessing {alloc} which contains a vtable"), | |
| DerefVaListPointer(alloc) => { | |
| write!(f, "accessing {alloc} which contains a variable argument list") | |
| } | |
| DerefTypeIdPointer(alloc) => write!(f, "accessing {alloc} which contains a `TypeId`"), | |
| InvalidBool(value) => { | |
| write!(f, "interpreting an invalid 8-bit value as a bool: 0x{value:02x}") | |
| } | |
| InvalidChar(value) => { | |
| write!(f, "interpreting an invalid 32-bit value as a char: 0x{value:08x}") | |
| } | |
| InvalidTag(tag) => write!(f, "enum value has invalid tag: {tag:x}"), | |
| InvalidFunctionPointer(ptr) => { | |
| write!(f, "using {ptr} as function pointer but it does not point to a function") | |
| } | |
| InvalidVaListPointer(ptr) => write!( | |
| f, | |
| "using {ptr} as variable argument list pointer but it does not point to a variable argument list" | |
| ), | |
| InvalidVTablePointer(ptr) => { | |
| write!(f, "using {ptr} as vtable pointer but it does not point to a vtable") | |
| } | |
| InvalidVTableTrait { vtable_dyn_type, expected_dyn_type } => write!( | |
| f, | |
| "using vtable for `{vtable_dyn_type}` but `{expected_dyn_type}` was expected" | |
| ), | |
| InvalidStr(err) => write!(f, "this string is not valid UTF-8: {err}"), | |
| InvalidUninitBytes(None) => { | |
| write!( | |
| f, | |
| "using uninitialized data, but this operation requires initialized memory" | |
| ) | |
| } | |
| InvalidUninitBytes(Some((alloc, info))) => write!( | |
| f, | |
| "reading memory at {alloc}{access}, but memory is uninitialized at {uninit}, and this operation requires initialized memory", | |
| access = info.access, | |
| uninit = info.bad, | |
| ), | |
| DeadLocal => write!(f, "accessing a dead local variable"), | |
| ScalarSizeMismatch(mismatch) => write!( | |
| f, | |
| "scalar size mismatch: expected {target_size} bytes but got {data_size} bytes instead", | |
| target_size = mismatch.target_size, | |
| data_size = mismatch.data_size, | |
| ), | |
| UninhabitedEnumVariantWritten(_) => { | |
| write!(f, "writing discriminant of an uninhabited enum variant") | |
| } | |
| UninhabitedEnumVariantRead(_) => { | |
| write!(f, "read discriminant of an uninhabited enum variant") | |
| } | |
| InvalidNichedEnumVariantWritten { enum_ty } => { | |
| write!( | |
| f, | |
| "trying to set discriminant of a {enum_ty} to the niched variant, but the value does not match" | |
| ) | |
| } | |
| AbiMismatchArgument { arg_idx, caller_ty, callee_ty } => write!( | |
| f, | |
| "calling a function whose parameter #{arg_idx} has type {callee_ty} passing argument of type {caller_ty}", | |
| arg_idx = arg_idx + 1, // adjust for 1-indexed lists in output | |
| ), | |
| AbiMismatchReturn { caller_ty, callee_ty } => write!( | |
| f, | |
| "calling a function with return type {callee_ty} passing return place of type {caller_ty}" | |
| ), | |
| VaArgOutOfBounds => write!(f, "more C-variadic arguments read than were passed"), | |
| CVariadicMismatch { .. } => write!( | |
| f, | |
| "calling a function where the caller and callee disagree on whether the function is C-variadic" | |
| ), | |
| CVariadicFixedCountMismatch { caller, callee } => write!( | |
| f, | |
| "calling a C-variadic function with {caller} fixed arguments, but the function expects {callee}" | |
| ), | |
| } | |
| } | |
| } | |
| /// Error information for when the program we executed turned out not to actually be a valid | |
| /// program. This cannot happen in stand-alone Miri (except for layout errors that are only detect | |
| /// during monomorphization), but it can happen during CTFE/ConstProp where we work on generic code | |
| /// or execution does not have all information available. | |
| #[derive(Debug)] | |
| pub enum InvalidProgramInfo<'tcx> { | |
| /// Resolution can fail if we are in a too generic context. | |
| TooGeneric, | |
| /// Abort in case errors are already reported. | |
| AlreadyReported(ReportedErrorInfo), | |
| /// An error occurred during layout computation. | |
| Layout(layout::LayoutError<'tcx>), | |
| } | |
| impl<'tcx> fmt::Display for InvalidProgramInfo<'tcx> { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| use InvalidProgramInfo::*; | |
| match self { | |
| TooGeneric => write!(f, "encountered overly generic constant"), | |
| AlreadyReported(_) => { | |
| write!( | |
| f, | |
| "an error has already been reported elsewhere (this should not usually be printed)" | |
| ) | |
| } | |
| Layout(e) => write!(f, "{e}"), | |
| } | |
| } | |
| } | |
| /// Error information for when the program did something that might (or might not) be correct | |
| /// to do according to the Rust spec, but due to limitations in the interpreter, the | |
| /// operation could not be carried out. These limitations can differ between CTFE and the | |
| /// Miri engine, e.g., CTFE does not support dereferencing pointers at integral addresses. | |
| #[derive(Debug)] | |
| pub enum UnsupportedOpInfo { | |
| /// Free-form case. Only for errors that are never caught! Used by Miri. | |
| // FIXME still use translatable diagnostics | |
| Unsupported(String), | |
| /// Unsized local variables. | |
| UnsizedLocal, | |
| /// Extern type field with an indeterminate offset. | |
| ExternTypeField, | |
| // | |
| // The variants below are only reachable from CTFE/const prop, miri will never emit them. | |
| // | |
| /// Attempting to read or copy parts of a pointer to somewhere else; without knowing absolute | |
| /// addresses, the resulting state cannot be represented by the CTFE interpreter. | |
| ReadPartialPointer(Pointer<AllocId>), | |
| /// Encountered a pointer where we needed an integer. | |
| ReadPointerAsInt(Option<(AllocId, BadBytesAccess)>), | |
| /// Accessing thread local statics | |
| ThreadLocalStatic(DefId), | |
| /// Accessing an unsupported extern static. | |
| ExternStatic(DefId), | |
| } | |
| impl fmt::Display for UnsupportedOpInfo { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| use UnsupportedOpInfo::*; | |
| match self { | |
| Unsupported(s) => write!(f, "{s}"), | |
| ExternTypeField => { | |
| write!(f, "`extern type` field does not have a known offset") | |
| } | |
| UnsizedLocal => write!(f, "unsized locals are not supported"), | |
| ReadPartialPointer(ptr) => { | |
| write!(f, "unable to read parts of a pointer from memory at {ptr}") | |
| } | |
| ReadPointerAsInt(_) => write!(f, "unable to turn pointer into integer"), | |
| &ThreadLocalStatic(did) => { | |
| write!( | |
| f, | |
| "cannot access thread local static `{did}`", | |
| did = ty::tls::with(|tcx| tcx.def_path_str(did)) | |
| ) | |
| } | |
| &ExternStatic(did) => { | |
| write!( | |
| f, | |
| "cannot access extern static `{did}`", | |
| did = ty::tls::with(|tcx| tcx.def_path_str(did)) | |
| ) | |
| } | |
| } | |
| } | |
| } | |
| /// Error information for when the program exhausted the resources granted to it | |
| /// by the interpreter. | |
| #[derive(Debug)] | |
| pub enum ResourceExhaustionInfo { | |
| /// The stack grew too big. | |
| StackFrameLimitReached, | |
| /// There is not enough memory (on the host) to perform an allocation. | |
| MemoryExhausted, | |
| /// The address space (of the target) is full. | |
| AddressSpaceFull, | |
| /// The compiler got an interrupt signal (a user ran out of patience). | |
| Interrupted, | |
| } | |
| impl fmt::Display for ResourceExhaustionInfo { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| use ResourceExhaustionInfo::*; | |
| match self { | |
| StackFrameLimitReached => { | |
| write!(f, "reached the configured maximum number of stack frames") | |
| } | |
| MemoryExhausted => { | |
| write!(f, "tried to allocate more memory than available to compiler") | |
| } | |
| AddressSpaceFull => { | |
| write!(f, "there are no more free addresses in the address space") | |
| } | |
| Interrupted => write!(f, "compilation was interrupted"), | |
| } | |
| } | |
| } | |
| /// A trait for machine-specific errors (or other "machine stop" conditions). | |
| pub trait MachineStopType: Any + fmt::Display + fmt::Debug + Send { | |
| /// This error occurred during validation, inside a value at the given path. | |
| fn with_validation_path(&mut self, _path: String) {} | |
| } | |
| impl dyn MachineStopType { | |
| #[inline(always)] | |
| pub fn downcast_ref<T: Any>(&self) -> Option<&T> { | |
| let x: &dyn Any = self; | |
| x.downcast_ref() | |
| } | |
| } | |
| #[derive(Debug)] | |
| pub enum InterpErrorKind<'tcx> { | |
| /// The program caused undefined behavior. | |
| UndefinedBehavior(UndefinedBehaviorInfo<'tcx>), | |
| /// The program was invalid (ill-typed, bad MIR, not sufficiently monomorphized, ...). | |
| InvalidProgram(InvalidProgramInfo<'tcx>), | |
| /// The program did something the interpreter does not support (some of these *might* be UB | |
| /// but the interpreter is not sure). | |
| Unsupported(UnsupportedOpInfo), | |
| /// The program exhausted the interpreter's resources (stack/heap too big, | |
| /// execution takes too long, ...). | |
| ResourceExhaustion(ResourceExhaustionInfo), | |
| /// Stop execution for a machine-controlled reason. This is never raised by | |
| /// the core engine itself. | |
| MachineStop(Box<dyn MachineStopType>), | |
| } | |
| impl<'tcx> fmt::Display for InterpErrorKind<'tcx> { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| use InterpErrorKind::*; | |
| match self { | |
| Unsupported(msg) => write!(f, "{msg}"), | |
| InvalidProgram(msg) => write!(f, "{msg}"), | |
| UndefinedBehavior(msg) => write!(f, "{msg}"), | |
| ResourceExhaustion(msg) => write!(f, "{msg}"), | |
| MachineStop(msg) => write!(f, "{msg}"), | |
| } | |
| } | |
| } | |
| impl InterpErrorKind<'_> { | |
| /// Some errors do string formatting even if the error is never printed. | |
| /// To avoid performance issues, there are places where we want to be sure to never raise these formatting errors, | |
| /// so this method lets us detect them and `bug!` on unexpected errors. | |
| pub fn formatted_string(&self) -> bool { | |
| matches!( | |
| self, | |
| InterpErrorKind::Unsupported(UnsupportedOpInfo::Unsupported(_)) | |
| | InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::ValidationError { .. }) | |
| | InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_)) | |
| ) | |
| } | |
| } | |
| // Macros for constructing / throwing `InterpErrorKind` | |
| #[macro_export] | |
| macro_rules! err_unsup { | |
| ($($tt:tt)*) => { | |
| $crate::mir::interpret::InterpErrorKind::Unsupported( | |
| $crate::mir::interpret::UnsupportedOpInfo::$($tt)* | |
| ) | |
| }; | |
| } | |
| #[macro_export] | |
| macro_rules! err_unsup_format { | |
| ($($tt:tt)*) => { $crate::err_unsup!(Unsupported(format!($($tt)*))) }; | |
| } | |
| #[macro_export] | |
| macro_rules! err_inval { | |
| ($($tt:tt)*) => { | |
| $crate::mir::interpret::InterpErrorKind::InvalidProgram( | |
| $crate::mir::interpret::InvalidProgramInfo::$($tt)* | |
| ) | |
| }; | |
| } | |
| #[macro_export] | |
| macro_rules! err_ub { | |
| ($($tt:tt)*) => { | |
| $crate::mir::interpret::InterpErrorKind::UndefinedBehavior( | |
| $crate::mir::interpret::UndefinedBehaviorInfo::$($tt)* | |
| ) | |
| }; | |
| } | |
| #[macro_export] | |
| macro_rules! err_ub_format { | |
| ($($tt:tt)*) => { $crate::err_ub!(Ub(format!($($tt)*))) }; | |
| } | |
| #[macro_export] | |
| macro_rules! err_exhaust { | |
| ($($tt:tt)*) => { | |
| $crate::mir::interpret::InterpErrorKind::ResourceExhaustion( | |
| $crate::mir::interpret::ResourceExhaustionInfo::$($tt)* | |
| ) | |
| }; | |
| } | |
| #[macro_export] | |
| macro_rules! err_machine_stop { | |
| ($($tt:tt)*) => { | |
| $crate::mir::interpret::InterpErrorKind::MachineStop(Box::new($($tt)*)) | |
| }; | |
| } | |
| // In the `throw_*` macros, avoid `return` to make them work with `try {}`. | |
| #[macro_export] | |
| macro_rules! throw_unsup { | |
| ($($tt:tt)*) => { do yeet $crate::err_unsup!($($tt)*) }; | |
| } | |
| #[macro_export] | |
| macro_rules! throw_unsup_format { | |
| ($($tt:tt)*) => { do yeet $crate::err_unsup_format!($($tt)*) }; | |
| } | |
| #[macro_export] | |
| macro_rules! throw_inval { | |
| ($($tt:tt)*) => { do yeet $crate::err_inval!($($tt)*) }; | |
| } | |
| #[macro_export] | |
| macro_rules! throw_ub { | |
| ($($tt:tt)*) => { do yeet $crate::err_ub!($($tt)*) }; | |
| } | |
| #[macro_export] | |
| macro_rules! throw_ub_format { | |
| ($($tt:tt)*) => { do yeet $crate::err_ub_format!($($tt)*) }; | |
| } | |
| #[macro_export] | |
| macro_rules! throw_exhaust { | |
| ($($tt:tt)*) => { do yeet $crate::err_exhaust!($($tt)*) }; | |
| } | |
| #[macro_export] | |
| macro_rules! throw_machine_stop { | |
| ($($tt:tt)*) => { do yeet $crate::err_machine_stop!($($tt)*) }; | |
| } | |
| /// Guard type that panics on drop. | |
| #[derive(Debug)] | |
| struct Guard; | |
| impl Drop for Guard { | |
| fn drop(&mut self) { | |
| // We silence the guard if we are already panicking, to avoid double-panics. | |
| if !std::thread::panicking() { | |
| panic!( | |
| "an interpreter error got improperly discarded; use `discard_err()` if this is intentional" | |
| ); | |
| } | |
| } | |
| } | |
| /// The result type used by the interpreter. This is a newtype around `Result` | |
| /// to block access to operations like `ok()` that discard UB errors. | |
| /// | |
| /// We also make things panic if this type is ever implicitly dropped. | |
| #[derive(Debug)] | |
| #[must_use] | |
| pub struct InterpResult<'tcx, T = ()> { | |
| res: Result<T, InterpErrorInfo<'tcx>>, | |
| guard: Guard, | |
| } | |
| impl<'tcx, T> ops::Try for InterpResult<'tcx, T> { | |
| type Output = T; | |
| type Residual = InterpResult<'tcx, convert::Infallible>; | |
| #[inline] | |
| fn from_output(output: Self::Output) -> Self { | |
| InterpResult::new(Ok(output)) | |
| } | |
| #[inline] | |
| fn branch(self) -> ops::ControlFlow<Self::Residual, Self::Output> { | |
| match self.disarm() { | |
| Ok(v) => ops::ControlFlow::Continue(v), | |
| Err(e) => ops::ControlFlow::Break(InterpResult::new(Err(e))), | |
| } | |
| } | |
| } | |
| impl<'tcx, T> ops::Residual<T> for InterpResult<'tcx, convert::Infallible> { | |
| type TryType = InterpResult<'tcx, T>; | |
| } | |
| impl<'tcx, T> ops::FromResidual for InterpResult<'tcx, T> { | |
| #[inline] | |
| #[track_caller] | |
| fn from_residual(residual: InterpResult<'tcx, convert::Infallible>) -> Self { | |
| match residual.disarm() { | |
| Err(e) => Self::new(Err(e)), | |
| } | |
| } | |
| } | |
| // Allow `yeet`ing `InterpError` in functions returning `InterpResult_`. | |
| impl<'tcx, T> ops::FromResidual<ops::Yeet<InterpErrorKind<'tcx>>> for InterpResult<'tcx, T> { | |
| #[inline] | |
| fn from_residual(ops::Yeet(e): ops::Yeet<InterpErrorKind<'tcx>>) -> Self { | |
| Self::new(Err(e.into())) | |
| } | |
| } | |
| // Allow `?` on `Result<_, InterpError>` in functions returning `InterpResult_`. | |
| // This is useful e.g. for `option.ok_or_else(|| err_ub!(...))`. | |
| impl<'tcx, T, E: Into<InterpErrorInfo<'tcx>>> ops::FromResidual<Result<convert::Infallible, E>> | |
| for InterpResult<'tcx, T> | |
| { | |
| #[inline] | |
| fn from_residual(residual: Result<convert::Infallible, E>) -> Self { | |
| match residual { | |
| Err(e) => Self::new(Err(e.into())), | |
| } | |
| } | |
| } | |
| impl<'tcx, T, E: Into<InterpErrorInfo<'tcx>>> From<Result<T, E>> for InterpResult<'tcx, T> { | |
| #[inline] | |
| fn from(value: Result<T, E>) -> Self { | |
| Self::new(value.map_err(|e| e.into())) | |
| } | |
| } | |
| impl<'tcx, T, V: FromIterator<T>> FromIterator<InterpResult<'tcx, T>> for InterpResult<'tcx, V> { | |
| fn from_iter<I: IntoIterator<Item = InterpResult<'tcx, T>>>(iter: I) -> Self { | |
| Self::new(iter.into_iter().map(|x| x.disarm()).collect()) | |
| } | |
| } | |
| impl<'tcx, T> InterpResult<'tcx, T> { | |
| #[inline(always)] | |
| fn new(res: Result<T, InterpErrorInfo<'tcx>>) -> Self { | |
| Self { res, guard: Guard } | |
| } | |
| #[inline(always)] | |
| fn disarm(self) -> Result<T, InterpErrorInfo<'tcx>> { | |
| mem::forget(self.guard); | |
| self.res | |
| } | |
| /// Discard the error information in this result. Only use this if ignoring Undefined Behavior is okay! | |
| #[inline] | |
| pub fn discard_err(self) -> Option<T> { | |
| self.disarm().ok() | |
| } | |
| /// Look at the `Result` wrapped inside of this. | |
| /// Must only be used to report the error! | |
| #[inline] | |
| pub fn report_err(self) -> Result<T, InterpErrorInfo<'tcx>> { | |
| self.disarm() | |
| } | |
| #[inline] | |
| pub fn map<U>(self, f: impl FnOnce(T) -> U) -> InterpResult<'tcx, U> { | |
| InterpResult::new(self.disarm().map(f)) | |
| } | |
| #[inline] | |
| pub fn map_err_kind( | |
| self, | |
| f: impl FnOnce(InterpErrorKind<'tcx>) -> InterpErrorKind<'tcx>, | |
| ) -> InterpResult<'tcx, T> { | |
| InterpResult::new(self.disarm().map_err(|mut e| { | |
| e.0.kind = f(e.0.kind); | |
| e | |
| })) | |
| } | |
| #[inline] | |
| pub fn inspect_err_info(self, f: impl FnOnce(&InterpErrorInfo<'tcx>)) -> InterpResult<'tcx, T> { | |
| InterpResult::new(self.disarm().inspect_err(f)) | |
| } | |
| #[inline] | |
| #[track_caller] | |
| pub fn unwrap(self) -> T { | |
| self.disarm().unwrap() | |
| } | |
| #[inline] | |
| #[track_caller] | |
| pub fn unwrap_or_else(self, f: impl FnOnce(InterpErrorInfo<'tcx>) -> T) -> T { | |
| self.disarm().unwrap_or_else(f) | |
| } | |
| #[inline] | |
| #[track_caller] | |
| pub fn expect(self, msg: &str) -> T { | |
| self.disarm().expect(msg) | |
| } | |
| #[inline] | |
| pub fn and_then<U>(self, f: impl FnOnce(T) -> InterpResult<'tcx, U>) -> InterpResult<'tcx, U> { | |
| InterpResult::new(self.disarm().and_then(|t| f(t).disarm())) | |
| } | |
| /// Returns success if both `self` and `other` succeed, while ensuring we don't | |
| /// accidentally drop an error. | |
| /// | |
| /// If both are an error, `self` will be reported. | |
| #[inline] | |
| pub fn and<U>(self, other: InterpResult<'tcx, U>) -> InterpResult<'tcx, (T, U)> { | |
| match self.disarm() { | |
| Ok(t) => interp_ok((t, other?)), | |
| Err(e) => { | |
| // Discard the other error. | |
| drop(other.disarm()); | |
| // Return `self`. | |
| InterpResult::new(Err(e)) | |
| } | |
| } | |
| } | |
| } | |
| #[inline(always)] | |
| pub fn interp_ok<'tcx, T>(x: T) -> InterpResult<'tcx, T> { | |
| InterpResult::new(Ok(x)) | |
| } |