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::{HashStable, 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, HashStable, 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, HashStable, 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, HashStable, 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>),
    /// 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(box 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
    }
}

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.
    Dereferenceable,
}

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 => write!(f, "pointer 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<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 if inbounds_size == 0 => {
                    write!(f, "pointer must point to some allocation")
                }
                CheckInAllocMsg::Dereferenceable => {
                    write!(f, "pointer 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 {}

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_info(
        self,
        f: impl FnOnce(InterpErrorInfo<'tcx>) -> InterpErrorInfo<'tcx>,
    ) -> InterpResult<'tcx, T> {
        InterpResult::new(self.disarm().map_err(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_kind(self, f: impl FnOnce(&InterpErrorKind<'tcx>)) -> InterpResult<'tcx, T> {
        InterpResult::new(self.disarm().inspect_err(|e| f(&e.0.kind)))
    }

    #[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))
}
