use super::{AllocId, ConstAlloc, Pointer, Scalar};
use crate::mir::interpret::ConstValue;
use crate::ty::{layout, query::TyCtxtAt, tls, FnSig, Ty};
use rustc_data_structures::sync::Lock;
use rustc_errors::{pluralize, struct_span_err, DiagnosticBuilder, ErrorReported};
use rustc_macros::HashStable;
use rustc_session::CtfeBacktrace;
use rustc_span::def_id::DefId;
use rustc_target::abi::{Align, Size};
use std::{any::Any, backtrace::Backtrace, fmt, mem};
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
pub enum ErrorHandled {
Reported(ErrorReported),
Linted,
TooGeneric,
}
impl From<ErrorReported> for ErrorHandled {
fn from(err: ErrorReported) -> ErrorHandled {
ErrorHandled::Reported(err)
}
}
TrivialTypeFoldableAndLiftImpls! {
ErrorHandled,
}
pub type EvalToAllocationRawResult<'tcx> = Result<ConstAlloc<'tcx>, ErrorHandled>;
pub type EvalToConstValueResult<'tcx> = Result<ConstValue<'tcx>, ErrorHandled>;
pub fn struct_error<'tcx>(tcx: TyCtxtAt<'tcx>, msg: &str) -> DiagnosticBuilder<'tcx> {
struct_span_err!(tcx.sess, tcx.span, E0080, "{}", msg)
}
#[derive(Debug)]
pub struct InterpErrorInfo<'tcx> {
pub kind: InterpError<'tcx>,
backtrace: Option<Box<Backtrace>>,
}
impl fmt::Display for InterpErrorInfo<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)
}
}
impl InterpErrorInfo<'_> {
pub fn print_backtrace(&self) {
if let Some(backtrace) = self.backtrace.as_ref() {
print_backtrace(backtrace);
}
}
}
fn print_backtrace(backtrace: &Backtrace) {
eprintln!("\n\nAn error occurred in miri:\n{}", backtrace);
}
impl From<ErrorHandled> for InterpErrorInfo<'_> {
fn from(err: ErrorHandled) -> Self {
match err {
ErrorHandled::Reported(ErrorReported) | ErrorHandled::Linted => {
err_inval!(ReferencedConstant)
}
ErrorHandled::TooGeneric => err_inval!(TooGeneric),
}
.into()
}
}
impl From<ErrorReported> for InterpErrorInfo<'_> {
fn from(err: ErrorReported) -> Self {
InterpError::InvalidProgram(InvalidProgramInfo::AlreadyReported(err)).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 { kind, backtrace }
}
}
pub enum InvalidProgramInfo<'tcx> {
TooGeneric,
ReferencedConstant,
AlreadyReported(ErrorReported),
Layout(layout::LayoutError<'tcx>),
TransmuteSizeDiff(Ty<'tcx>, Ty<'tcx>),
}
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"),
ReferencedConstant => write!(f, "referenced constant has errors"),
AlreadyReported(ErrorReported) => {
write!(f, "encountered constants with type errors, stopping evaluation")
}
Layout(ref err) => write!(f, "{}", err),
TransmuteSizeDiff(from_ty, to_ty) => write!(
f,
"transmuting `{}` to `{}` is not possible, because these types do not have the same size",
from_ty, to_ty
),
}
}
}
#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)]
pub enum CheckInAllocMsg {
MemoryAccessTest,
NullPointerTest,
PointerArithmeticTest,
InboundsTest,
}
impl fmt::Display for CheckInAllocMsg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
CheckInAllocMsg::MemoryAccessTest => "memory access",
CheckInAllocMsg::NullPointerTest => "NULL pointer test",
CheckInAllocMsg::PointerArithmeticTest => "pointer arithmetic",
CheckInAllocMsg::InboundsTest => "inbounds test",
}
)
}
}
#[derive(Debug)]
pub struct UninitBytesAccess {
pub access_ptr: Pointer,
pub access_size: Size,
pub uninit_ptr: Pointer,
pub uninit_size: Size,
}
pub enum UndefinedBehaviorInfo<'tcx> {
Ub(String),
Unreachable,
BoundsCheckFailed {
len: u64,
index: u64,
},
DivisionByZero,
RemainderByZero,
PointerArithOverflow,
InvalidMeta(&'static str),
InvalidDropFn(FnSig<'tcx>),
UnterminatedCString(Pointer),
PointerUseAfterFree(AllocId),
PointerOutOfBounds {
ptr: Pointer,
msg: CheckInAllocMsg,
allocation_size: Size,
},
DanglingIntPointer(u64, CheckInAllocMsg),
AlignmentCheckFailed {
required: Align,
has: Align,
},
WriteToReadOnly(AllocId),
DerefFunctionPointer(AllocId),
ValidationFailure(String),
InvalidBool(u8),
InvalidChar(u32),
InvalidTag(Scalar),
InvalidFunctionPointer(Pointer),
InvalidStr(std::str::Utf8Error),
InvalidUninitBytes(Option<Box<UninitBytesAccess>>),
DeadLocal,
ScalarSizeMismatch {
target_size: u64,
data_size: u64,
},
}
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 {} but the index is {}", len, index)
}
DivisionByZero => write!(f, "dividing by zero"),
RemainderByZero => write!(f, "calculating the remainder with a divisor of zero"),
PointerArithOverflow => write!(f, "overflowing in-bounds pointer arithmetic"),
InvalidMeta(msg) => write!(f, "invalid metadata in wide pointer: {}", msg),
InvalidDropFn(sig) => write!(
f,
"invalid drop function signature: got {}, expected exactly one argument which must be a pointer type",
sig
),
UnterminatedCString(p) => write!(
f,
"reading a null-terminated string starting at {} with no null found before end of allocation",
p,
),
PointerUseAfterFree(a) => {
write!(f, "pointer to {} was dereferenced after this allocation got freed", a)
}
PointerOutOfBounds { ptr, msg, allocation_size } => write!(
f,
"{} failed: pointer must be in-bounds at offset {}, \
but is outside bounds of {} which has size {}",
msg,
ptr.offset.bytes(),
ptr.alloc_id,
allocation_size.bytes()
),
DanglingIntPointer(_, CheckInAllocMsg::NullPointerTest) => {
write!(f, "NULL pointer is not allowed for this operation")
}
DanglingIntPointer(i, msg) => {
write!(f, "{} failed: 0x{:x} is not a valid pointer", msg, i)
}
AlignmentCheckFailed { required, has } => write!(
f,
"accessing memory with alignment {}, but alignment {} is required",
has.bytes(),
required.bytes()
),
WriteToReadOnly(a) => write!(f, "writing to {} which is read-only", a),
DerefFunctionPointer(a) => write!(f, "accessing {} which contains a function", a),
ValidationFailure(ref err) => write!(f, "type validation failed: {}", err),
InvalidBool(b) => {
write!(f, "interpreting an invalid 8-bit value as a bool: 0x{:02x}", b)
}
InvalidChar(c) => {
write!(f, "interpreting an invalid 32-bit value as a char: 0x{:08x}", c)
}
InvalidTag(val) => write!(f, "enum value has invalid tag: {}", val),
InvalidFunctionPointer(p) => {
write!(f, "using {} as function pointer but it does not point to a function", p)
}
InvalidStr(err) => write!(f, "this string is not valid UTF-8: {}", err),
InvalidUninitBytes(Some(access)) => write!(
f,
"reading {} byte{} of memory starting at {}, \
but {} byte{} {} uninitialized starting at {}, \
and this operation requires initialized memory",
access.access_size.bytes(),
pluralize!(access.access_size.bytes()),
access.access_ptr,
access.uninit_size.bytes(),
pluralize!(access.uninit_size.bytes()),
if access.uninit_size.bytes() != 1 { "are" } else { "is" },
access.uninit_ptr,
),
InvalidUninitBytes(None) => write!(
f,
"using uninitialized data, but this operation requires initialized memory"
),
DeadLocal => write!(f, "accessing a dead local variable"),
ScalarSizeMismatch { target_size, data_size } => write!(
f,
"scalar size mismatch: expected {} bytes but got {} bytes instead",
target_size, data_size
),
}
}
}
pub enum UnsupportedOpInfo {
Unsupported(String),
NoMirFor(DefId),
ReadPointerAsBytes,
ReadBytesAsPointer,
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),
ReadExternStatic(did) => write!(f, "cannot read from extern static ({:?})", did),
NoMirFor(did) => write!(f, "no MIR body is available for {:?}", did),
ReadPointerAsBytes => write!(f, "unable to turn pointer into raw bytes",),
ReadBytesAsPointer => write!(f, "unable to turn bytes into a pointer"),
ThreadLocalStatic(did) => write!(f, "cannot access thread local static ({:?})", did),
}
}
}
pub enum ResourceExhaustionInfo {
StackFrameLimitReached,
StepLimitReached,
}
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]`)")
}
}
}
}
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 MachineStopType for String {}
impl dyn MachineStopType {
#[inline(always)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
self.as_any().downcast_ref()
}
}
#[cfg(target_arch = "x86_64")]
static_assert_size!(InterpError<'_>, 40);
pub enum InterpError<'tcx> {
UndefinedBehavior(UndefinedBehaviorInfo<'tcx>),
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 allocates(&self) -> bool {
match self {
InterpError::MachineStop(b) => mem::size_of_val::<dyn MachineStopType>(&**b) > 0,
InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_))
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationFailure(_))
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_))
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some(_))) => {
true
}
_ => false,
}
}
}