use super::{AllocId, AllocRange, ConstAlloc, Pointer, Scalar};
use crate::mir::interpret::ConstValue;
use crate::query::TyCtxtAt;
use crate::ty::{layout, tls, Ty, ValTree};
use rustc_data_structures::sync::Lock;
use rustc_errors::{pluralize, struct_span_err, DiagnosticBuilder, ErrorGuaranteed};
use rustc_macros::HashStable;
use rustc_session::CtfeBacktrace;
use rustc_span::def_id::DefId;
use rustc_target::abi::{call, Align, Size};
use std::{any::Any, backtrace::Backtrace, fmt};
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
pub enum ErrorHandled {
Reported(ReportedErrorInfo),
TooGeneric,
}
impl From<ErrorGuaranteed> for ErrorHandled {
#[inline]
fn from(error: ErrorGuaranteed) -> ErrorHandled {
ErrorHandled::Reported(error.into())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
pub struct ReportedErrorInfo {
error: ErrorGuaranteed,
is_tainted_by_errors: bool,
}
impl ReportedErrorInfo {
#[inline]
pub fn tainted_by_errors(error: ErrorGuaranteed) -> ReportedErrorInfo {
ReportedErrorInfo { is_tainted_by_errors: true, error }
}
#[inline]
pub fn is_tainted_by_errors(self) -> bool {
self.is_tainted_by_errors
}
}
impl From<ErrorGuaranteed> for ReportedErrorInfo {
#[inline]
fn from(error: ErrorGuaranteed) -> ReportedErrorInfo {
ReportedErrorInfo { is_tainted_by_errors: false, error }
}
}
impl Into<ErrorGuaranteed> for ReportedErrorInfo {
#[inline]
fn into(self) -> ErrorGuaranteed {
self.error
}
}
TrivialTypeTraversalAndLiftImpls! {
ErrorHandled,
}
pub type EvalToAllocationRawResult<'tcx> = Result<ConstAlloc<'tcx>, ErrorHandled>;
pub type EvalToConstValueResult<'tcx> = Result<ConstValue<'tcx>, ErrorHandled>;
pub type EvalToValTreeResult<'tcx> = Result<Option<ValTree<'tcx>>, ErrorHandled>;
pub fn struct_error<'tcx>(
tcx: TyCtxtAt<'tcx>,
msg: &str,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
struct_span_err!(tcx.sess, tcx.span, E0080, "{}", msg)
}
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
static_assert_size!(InterpErrorInfo<'_>, 8);
#[derive(Debug)]
pub struct InterpErrorInfo<'tcx>(Box<InterpErrorInfoInner<'tcx>>);
#[derive(Debug)]
struct InterpErrorInfoInner<'tcx> {
kind: InterpError<'tcx>,
backtrace: Option<Box<Backtrace>>,
}
impl fmt::Display for InterpErrorInfo<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.kind)
}
}
impl<'tcx> InterpErrorInfo<'tcx> {
pub fn print_backtrace(&self) {
if let Some(backtrace) = self.0.backtrace.as_ref() {
print_backtrace(backtrace);
}
}
pub fn into_kind(self) -> InterpError<'tcx> {
let InterpErrorInfo(box InterpErrorInfoInner { kind, .. }) = self;
kind
}
#[inline]
pub fn kind(&self) -> &InterpError<'tcx> {
&self.0.kind
}
}
fn print_backtrace(backtrace: &Backtrace) {
eprintln!("\n\nAn error occurred in miri:\n{}", backtrace);
}
impl From<ErrorGuaranteed> for InterpErrorInfo<'_> {
fn from(err: ErrorGuaranteed) -> Self {
InterpError::InvalidProgram(InvalidProgramInfo::AlreadyReported(err.into())).into()
}
}
impl<'tcx> From<InterpError<'tcx>> for InterpErrorInfo<'tcx> {
fn from(kind: InterpError<'tcx>) -> Self {
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 => {
let backtrace = Backtrace::force_capture();
print_backtrace(&backtrace);
None
}
};
InterpErrorInfo(Box::new(InterpErrorInfoInner { kind, backtrace }))
}
}
pub enum InvalidProgramInfo<'tcx> {
TooGeneric,
AlreadyReported(ReportedErrorInfo),
Layout(layout::LayoutError<'tcx>),
FnAbiAdjustForForeignAbi(call::AdjustForForeignAbiError),
SizeOfUnsizedType(Ty<'tcx>),
UninitUnsizedLocal,
}
impl fmt::Display for InvalidProgramInfo<'_> {
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(ref err) => write!(f, "{err}"),
FnAbiAdjustForForeignAbi(ref err) => write!(f, "{err}"),
SizeOfUnsizedType(ty) => write!(f, "size_of called on unsized type `{ty}`"),
UninitUnsizedLocal => write!(f, "unsized local is used while uninitialized"),
}
}
}
#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)]
pub enum CheckInAllocMsg {
DerefTest,
MemoryAccessTest,
PointerArithmeticTest,
OffsetFromTest,
InboundsTest,
}
impl fmt::Display for CheckInAllocMsg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
CheckInAllocMsg::DerefTest => "dereferencing pointer failed: ",
CheckInAllocMsg::MemoryAccessTest => "memory access failed: ",
CheckInAllocMsg::PointerArithmeticTest => "out-of-bounds pointer arithmetic: ",
CheckInAllocMsg::OffsetFromTest => "out-of-bounds offset_from: ",
CheckInAllocMsg::InboundsTest => "out-of-bounds pointer use: ",
}
)
}
}
#[derive(Debug)]
pub struct UninitBytesAccess {
pub access: AllocRange,
pub uninit: AllocRange,
}
#[derive(Debug)]
pub struct ScalarSizeMismatch {
pub target_size: u64,
pub data_size: u64,
}
pub enum UndefinedBehaviorInfo {
Ub(String),
Unreachable,
BoundsCheckFailed {
len: u64,
index: u64,
},
DivisionByZero,
RemainderByZero,
DivisionOverflow,
RemainderOverflow,
PointerArithOverflow,
InvalidMeta(&'static str),
UnterminatedCString(Pointer),
PointerUseAfterFree(AllocId),
PointerOutOfBounds {
alloc_id: AllocId,
alloc_size: Size,
ptr_offset: i64,
ptr_size: Size,
msg: CheckInAllocMsg,
},
DanglingIntPointer(u64, CheckInAllocMsg),
AlignmentCheckFailed {
required: Align,
has: Align,
},
WriteToReadOnly(AllocId),
DerefFunctionPointer(AllocId),
DerefVTablePointer(AllocId),
ValidationFailure {
path: Option<String>,
msg: String,
},
InvalidBool(u8),
InvalidChar(u32),
InvalidTag(Scalar),
InvalidFunctionPointer(Pointer),
InvalidVTablePointer(Pointer),
InvalidStr(std::str::Utf8Error),
InvalidUninitBytes(Option<(AllocId, UninitBytesAccess)>),
DeadLocal,
ScalarSizeMismatch(ScalarSizeMismatch),
UninhabitedEnumVariantWritten,
}
impl fmt::Display for UndefinedBehaviorInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use UndefinedBehaviorInfo::*;
match self {
Ub(msg) => write!(f, "{msg}"),
Unreachable => write!(f, "entering unreachable code"),
BoundsCheckFailed { ref len, ref 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 in-bounds pointer arithmetic"),
InvalidMeta(msg) => write!(f, "invalid metadata in wide pointer: {msg}"),
UnterminatedCString(p) => write!(
f,
"reading a null-terminated string starting at {p:?} with no null found before end of allocation",
),
PointerUseAfterFree(a) => {
write!(f, "pointer to {a:?} was dereferenced after this allocation got freed")
}
PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size: Size::ZERO, msg } => {
write!(
f,
"{msg}{alloc_id:?} has size {alloc_size}, so pointer at offset {ptr_offset} is out-of-bounds",
alloc_size = alloc_size.bytes(),
)
}
PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => write!(
f,
"{msg}{alloc_id:?} has size {alloc_size}, so pointer to {ptr_size} byte{ptr_size_p} starting at offset {ptr_offset} is out-of-bounds",
alloc_size = alloc_size.bytes(),
ptr_size = ptr_size.bytes(),
ptr_size_p = pluralize!(ptr_size.bytes()),
),
DanglingIntPointer(i, msg) => {
write!(
f,
"{msg}{pointer} is a dangling pointer (it has no provenance)",
pointer = Pointer::<Option<AllocId>>::from_addr_invalid(*i),
)
}
AlignmentCheckFailed { required, has } => write!(
f,
"accessing memory with alignment {has}, but alignment {required} is required",
has = has.bytes(),
required = required.bytes()
),
WriteToReadOnly(a) => write!(f, "writing to {a:?} which is read-only"),
DerefFunctionPointer(a) => write!(f, "accessing {a:?} which contains a function"),
DerefVTablePointer(a) => write!(f, "accessing {a:?} which contains a vtable"),
ValidationFailure { path: None, msg } => {
write!(f, "constructing invalid value: {msg}")
}
ValidationFailure { path: Some(path), msg } => {
write!(f, "constructing invalid value at {path}: {msg}")
}
InvalidBool(b) => {
write!(f, "interpreting an invalid 8-bit value as a bool: 0x{b:02x}")
}
InvalidChar(c) => {
write!(f, "interpreting an invalid 32-bit value as a char: 0x{c:08x}")
}
InvalidTag(val) => write!(f, "enum value has invalid tag: {val:x}"),
InvalidFunctionPointer(p) => {
write!(f, "using {p:?} as function pointer but it does not point to a function")
}
InvalidVTablePointer(p) => {
write!(f, "using {p:?} as vtable pointer but it does not point to a vtable")
}
InvalidStr(err) => write!(f, "this string is not valid UTF-8: {err}"),
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.uninit,
),
InvalidUninitBytes(None) => write!(
f,
"using uninitialized data, but this operation requires initialized memory"
),
DeadLocal => write!(f, "accessing a dead local variable"),
ScalarSizeMismatch(self::ScalarSizeMismatch { target_size, data_size }) => write!(
f,
"scalar size mismatch: expected {target_size} bytes but got {data_size} bytes instead",
),
UninhabitedEnumVariantWritten => {
write!(f, "writing discriminant of an uninhabited enum")
}
}
}
}
pub enum UnsupportedOpInfo {
Unsupported(String),
PartialPointerOverwrite(Pointer<AllocId>),
PartialPointerCopy(Pointer<AllocId>),
ReadPointerAsBytes,
ThreadLocalStatic(DefId),
ReadExternStatic(DefId),
}
impl fmt::Display for UnsupportedOpInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use UnsupportedOpInfo::*;
match self {
Unsupported(ref msg) => write!(f, "{msg}"),
PartialPointerOverwrite(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:?})"),
ReadExternStatic(did) => write!(f, "cannot read from extern static ({did:?})"),
}
}
}
pub enum ResourceExhaustionInfo {
StackFrameLimitReached,
StepLimitReached,
MemoryExhausted,
AddressSpaceFull,
}
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")
}
StepLimitReached => {
write!(f, "exceeded interpreter step limit (see `#[const_eval_limit]`)")
}
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")
}
}
}
}
pub trait AsAny: Any {
fn as_any(&self) -> &dyn Any;
}
impl<T: Any> AsAny for T {
#[inline(always)]
fn as_any(&self) -> &dyn Any {
self
}
}
pub trait MachineStopType: AsAny + fmt::Display + Send {}
impl dyn MachineStopType {
#[inline(always)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
self.as_any().downcast_ref()
}
}
pub enum InterpError<'tcx> {
UndefinedBehavior(UndefinedBehaviorInfo),
Unsupported(UnsupportedOpInfo),
InvalidProgram(InvalidProgramInfo<'tcx>),
ResourceExhaustion(ResourceExhaustionInfo),
MachineStop(Box<dyn MachineStopType>),
}
pub type InterpResult<'tcx, T = ()> = Result<T, InterpErrorInfo<'tcx>>;
impl fmt::Display for InterpError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use InterpError::*;
match *self {
Unsupported(ref msg) => write!(f, "{msg}"),
InvalidProgram(ref msg) => write!(f, "{msg}"),
UndefinedBehavior(ref msg) => write!(f, "{msg}"),
ResourceExhaustion(ref msg) => write!(f, "{msg}"),
MachineStop(ref msg) => write!(f, "{msg}"),
}
}
}
impl fmt::Debug for InterpError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl InterpError<'_> {
pub fn formatted_string(&self) -> bool {
matches!(
self,
InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_))
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationFailure { .. })
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_))
)
}
}