about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2020-03-08 18:52:30 +0100
committerRalf Jung <post@ralfj.de>2020-03-11 19:42:07 +0100
commitf5efb68a24b66a21b7eca055b9e8867eeaf61366 (patch)
tree6079a2a3e64ddaa1910232c441dcfc729a70c6d6
parentc20d7eecbc0928b57da8fe30b2ef8528e2bdd5be (diff)
downloadrust-f5efb68a24b66a21b7eca055b9e8867eeaf61366.tar.gz
rust-f5efb68a24b66a21b7eca055b9e8867eeaf61366.zip
miri: categorize errors into "unsupported" and "UB"
Also slightly refactor pointer bounds checks to avoid creating unnecessary temporary Errors
-rw-r--r--src/librustc/mir/interpret/allocation.rs4
-rw-r--r--src/librustc/mir/interpret/error.rs295
-rw-r--r--src/librustc/mir/interpret/mod.rs14
-rw-r--r--src/librustc/mir/interpret/pointer.rs16
-rw-r--r--src/librustc/mir/interpret/value.rs13
-rw-r--r--src/librustc_mir/const_eval/machine.rs3
-rw-r--r--src/librustc_mir/interpret/eval_context.rs6
-rw-r--r--src/librustc_mir/interpret/intern.rs4
-rw-r--r--src/librustc_mir/interpret/intrinsics.rs2
-rw-r--r--src/librustc_mir/interpret/machine.rs2
-rw-r--r--src/librustc_mir/interpret/memory.rs67
-rw-r--r--src/librustc_mir/interpret/operand.rs4
-rw-r--r--src/librustc_mir/interpret/place.rs2
-rw-r--r--src/librustc_mir/interpret/terminator.rs209
-rw-r--r--src/librustc_mir/interpret/validity.rs16
15 files changed, 293 insertions, 364 deletions
diff --git a/src/librustc/mir/interpret/allocation.rs b/src/librustc/mir/interpret/allocation.rs
index 546ba586d30..48b26cfd83c 100644
--- a/src/librustc/mir/interpret/allocation.rs
+++ b/src/librustc/mir/interpret/allocation.rs
@@ -314,7 +314,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
                 &self.get_bytes(cx, ptr, size_with_null)?[..size]
             }
             // This includes the case where `offset` is out-of-bounds to begin with.
-            None => throw_unsup!(UnterminatedCString(ptr.erase_tag())),
+            None => throw_ub!(UnterminatedCString(ptr.erase_tag())),
         })
     }
 
@@ -573,7 +573,7 @@ impl<'tcx, Tag, Extra> Allocation<Tag, Extra> {
     fn check_defined(&self, ptr: Pointer<Tag>, size: Size) -> InterpResult<'tcx> {
         self.undef_mask
             .is_range_defined(ptr.offset, ptr.offset + size)
-            .or_else(|idx| throw_unsup!(ReadUndefBytes(idx)))
+            .or_else(|idx| throw_ub!(InvalidUndefBytes(Some(Pointer::new(ptr.alloc_id, idx)))))
     }
 
     pub fn mark_definedness(&mut self, ptr: Pointer<Tag>, size: Size, new_state: bool) {
diff --git a/src/librustc/mir/interpret/error.rs b/src/librustc/mir/interpret/error.rs
index 0b33408edf0..ff0e9f2771f 100644
--- a/src/librustc/mir/interpret/error.rs
+++ b/src/librustc/mir/interpret/error.rs
@@ -1,7 +1,6 @@
-use super::{CheckInAllocMsg, Pointer, RawConst, ScalarMaybeUndef};
+use super::{AllocId, CheckInAllocMsg, Pointer, RawConst, ScalarMaybeUndef};
 
 use crate::hir::map::definitions::DefPathData;
-use crate::mir;
 use crate::mir::interpret::ConstValue;
 use crate::ty::layout::{Align, LayoutError, Size};
 use crate::ty::query::TyCtxtAt;
@@ -14,9 +13,8 @@ use rustc_errors::{struct_span_err, DiagnosticBuilder};
 use rustc_hir as hir;
 use rustc_macros::HashStable;
 use rustc_session::CtfeBacktrace;
-use rustc_span::{Pos, Span};
-use rustc_target::spec::abi::Abi;
-use std::{any::Any, fmt};
+use rustc_span::{Pos, Span, def_id::DefId};
+use std::{any::Any, env, fmt};
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, RustcEncodable, RustcDecodable)]
 pub enum ErrorHandled {
@@ -296,6 +294,8 @@ pub enum InvalidProgramInfo<'tcx> {
     TypeckError,
     /// An error occurred during layout computation.
     Layout(layout::LayoutError<'tcx>),
+    /// An invalid transmute happened.
+    TransmuteSizeDiff(Ty<'tcx>, Ty<'tcx>),
 }
 
 impl fmt::Debug for InvalidProgramInfo<'_> {
@@ -306,6 +306,11 @@ impl fmt::Debug for InvalidProgramInfo<'_> {
             ReferencedConstant => write!(f, "referenced constant has errors"),
             TypeckError => write!(f, "encountered constants with type errors, stopping evaluation"),
             Layout(ref err) => write!(f, "{}", err),
+            TransmuteSizeDiff(from_ty, to_ty) => write!(
+                f,
+                "tried to transmute from {:?} to {:?}, but their sizes differed",
+                from_ty, to_ty
+            ),
         }
     }
 }
@@ -330,6 +335,43 @@ pub enum UndefinedBehaviorInfo {
     PointerArithOverflow,
     /// Invalid metadata in a wide pointer (using `str` to avoid allocations).
     InvalidMeta(&'static str),
+    /// Reading a C string that does not end within its allocation.
+    UnterminatedCString(Pointer),
+    /// Dereferencing a dangling pointer after it got freed.
+    PointerUseAfterFree(AllocId),
+    /// Using a NULL pointer in the wrong way.
+    InvalidNullPointerUsage,
+    /// Used a pointer outside the bounds it is valid for.
+    PointerOutOfBounds {
+        ptr: Pointer,
+        msg: CheckInAllocMsg,
+        allocation_size: Size,
+    },
+    /// Used a pointer with bad alignment.
+    AlignmentCheckFailed {
+        required: Align,
+        has: Align,
+    },
+    /// Writing to read-only memory.
+    WriteToReadOnly(AllocId),
+    /// Using a pointer-not-to-a-function as function pointer.
+    InvalidFunctionPointer(Pointer),
+    // Trying to access the data behind a function pointer.
+    DerefFunctionPointer(AllocId),
+    /// The value validity check found a problem.
+    /// Should only be thrown by `validity.rs` and always point out which part of the value
+    /// is the problem.
+    ValidationFailure(String),
+    /// Using a non-boolean `u8` as bool.
+    InvalidBool(u8),
+    /// Using a non-character `u32` as character.
+    InvalidChar(u32),
+    /// Using uninitialized data where it is not allowed.
+    InvalidUndefBytes(Option<Pointer>),
+    /// Working with a local that is not currently live.
+    DeadLocal,
+    /// Trying to read from the return place of a function.
+    ReadFromReturnPlace,
 }
 
 impl fmt::Debug for UndefinedBehaviorInfo {
@@ -348,6 +390,44 @@ impl fmt::Debug for UndefinedBehaviorInfo {
             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),
+            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 allocation {:?} was dereferenced after allocation got freed",
+                a
+            ),
+            InvalidNullPointerUsage => write!(f, "invalid use of NULL pointer"),
+            PointerOutOfBounds { ptr, msg, allocation_size } => write!(
+                f,
+                "{} failed: pointer must be in-bounds at offset {}, \
+                           but is outside bounds of allocation {} which has size {}",
+                msg,
+                ptr.offset.bytes(),
+                ptr.alloc_id,
+                allocation_size.bytes()
+            ),
+            AlignmentCheckFailed { required, has } => write!(
+                f,
+                "accessing memory with alignment {}, but alignment {} is required",
+                has.bytes(),
+                required.bytes()
+            ),
+            WriteToReadOnly(a) => write!(f, "writing to read-only allocation {:?}", a),
+            InvalidFunctionPointer(p) => {
+                write!(f, "using {:?} as function pointer but it does not point to a function", p)
+            }
+            DerefFunctionPointer(a) => write!(f, "accessing data behind function pointer allocation {:?}", a),
+            ValidationFailure(ref err) => write!(f, "type validation failed: {}", err),
+            InvalidBool(b) => write!(f, "interpreting an invalid 8-bit value as a bool: {}", b),
+            InvalidChar(c) => write!(f, "interpreting an invalid 32-bit value as a char: {}", c),
+            InvalidUndefBytes(Some(p)) => write!(f, "reading uninitialized memory at {:?}, but this operation requires initialized memory", p),
+            InvalidUndefBytes(None) => write!(f, "using uninitialized data, but this operation requires initialized memory"),
+            DeadLocal => write!(f, "accessing a dead local variable"),
+            ReadFromReturnPlace => write!(f, "tried to read from the return place"),
         }
     }
 }
@@ -359,7 +439,7 @@ impl fmt::Debug for UndefinedBehaviorInfo {
 ///
 /// Currently, we also use this as fall-back error kind for errors that have not been
 /// categorized yet.
-pub enum UnsupportedOpInfo<'tcx> {
+pub enum UnsupportedOpInfo {
     /// Free-form case. Only for errors that are never caught!
     Unsupported(String),
 
@@ -367,194 +447,45 @@ pub enum UnsupportedOpInfo<'tcx> {
     /// This must not allocate for performance reasons (hence `str`, not `String`).
     ConstPropUnsupported(&'static str),
 
-    // -- Everything below is not categorized yet --
-    FunctionAbiMismatch(Abi, Abi),
-    FunctionArgMismatch(Ty<'tcx>, Ty<'tcx>),
-    FunctionRetMismatch(Ty<'tcx>, Ty<'tcx>),
-    FunctionArgCountMismatch,
-    UnterminatedCString(Pointer),
-    DanglingPointerDeref,
-    DoubleFree,
-    InvalidMemoryAccess,
-    InvalidFunctionPointer,
-    InvalidBool,
-    PointerOutOfBounds {
-        ptr: Pointer,
-        msg: CheckInAllocMsg,
-        allocation_size: Size,
-    },
-    InvalidNullPointerUsage,
+    /// Accessing an unsupported foreign static.
+    ReadForeignStatic(DefId),
+
+    /// Could not find MIR for a function.
+    NoMirFor(DefId),
+
+    /// Modified a static during const-eval.
+    /// FIXME: move this to `ConstEvalErrKind` through a machine hook.
+    ModifiedStatic,
+
+    /// Encountered a pointer where we needed raw bytes.
     ReadPointerAsBytes,
+
+    /// Encountered raw bytes where we needed a pointer.
     ReadBytesAsPointer,
-    ReadForeignStatic,
-    InvalidPointerMath,
-    ReadUndefBytes(Size),
-    DeadLocal,
-    InvalidBoolOp(mir::BinOp),
-    UnimplementedTraitSelection,
-    CalledClosureAsFunction,
-    NoMirFor(String),
-    DerefFunctionPointer,
-    ExecuteMemory,
-    InvalidChar(u128),
-    OutOfTls,
-    TlsOutOfBounds,
-    AlignmentCheckFailed {
-        required: Align,
-        has: Align,
-    },
-    ValidationFailure(String),
-    VtableForArgumentlessMethod,
-    ModifiedConstantMemory,
-    ModifiedStatic,
-    TypeNotPrimitive(Ty<'tcx>),
-    ReallocatedWrongMemoryKind(String, String),
-    DeallocatedWrongMemoryKind(String, String),
-    ReallocateNonBasePtr,
-    DeallocateNonBasePtr,
-    IncorrectAllocationInformation(Size, Size, Align, Align),
-    HeapAllocZeroBytes,
-    HeapAllocNonPowerOfTwoAlignment(u64),
-    ReadFromReturnPointer,
-    PathNotFound(Vec<String>),
-    TransmuteSizeDiff(Ty<'tcx>, Ty<'tcx>),
 }
 
-impl fmt::Debug for UnsupportedOpInfo<'tcx> {
+impl fmt::Debug for UnsupportedOpInfo {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         use UnsupportedOpInfo::*;
         match self {
-            PointerOutOfBounds { ptr, msg, allocation_size } => write!(
-                f,
-                "{} failed: pointer must be in-bounds at offset {}, \
-                           but is outside bounds of allocation {} which has size {}",
-                msg,
-                ptr.offset.bytes(),
-                ptr.alloc_id,
-                allocation_size.bytes()
-            ),
-            ValidationFailure(ref err) => write!(f, "type validation failed: {}", err),
-            NoMirFor(ref func) => write!(f, "no MIR for `{}`", func),
-            FunctionAbiMismatch(caller_abi, callee_abi) => write!(
-                f,
-                "tried to call a function with ABI {:?} using caller ABI {:?}",
-                callee_abi, caller_abi
-            ),
-            FunctionArgMismatch(caller_ty, callee_ty) => write!(
-                f,
-                "tried to call a function with argument of type {:?} \
-                           passing data of type {:?}",
-                callee_ty, caller_ty
-            ),
-            TransmuteSizeDiff(from_ty, to_ty) => write!(
-                f,
-                "tried to transmute from {:?} to {:?}, but their sizes differed",
-                from_ty, to_ty
-            ),
-            FunctionRetMismatch(caller_ty, callee_ty) => write!(
-                f,
-                "tried to call a function with return type {:?} \
-                           passing return place of type {:?}",
-                callee_ty, caller_ty
-            ),
-            FunctionArgCountMismatch => {
-                write!(f, "tried to call a function with incorrect number of arguments")
-            }
-            ReallocatedWrongMemoryKind(ref old, ref new) => {
-                write!(f, "tried to reallocate memory from `{}` to `{}`", old, new)
-            }
-            DeallocatedWrongMemoryKind(ref old, ref new) => {
-                write!(f, "tried to deallocate `{}` memory but gave `{}` as the kind", old, new)
-            }
-            InvalidChar(c) => {
-                write!(f, "tried to interpret an invalid 32-bit value as a char: {}", c)
-            }
-            AlignmentCheckFailed { required, has } => write!(
-                f,
-                "tried to access memory with alignment {}, but alignment {} is required",
-                has.bytes(),
-                required.bytes()
-            ),
-            TypeNotPrimitive(ty) => write!(f, "expected primitive type, got {}", ty),
-            PathNotFound(ref path) => write!(f, "cannot find path {:?}", path),
-            IncorrectAllocationInformation(size, size2, align, align2) => write!(
-                f,
-                "incorrect alloc info: expected size {} and align {}, \
-                           got size {} and align {}",
-                size.bytes(),
-                align.bytes(),
-                size2.bytes(),
-                align2.bytes()
-            ),
-            InvalidMemoryAccess => write!(f, "tried to access memory through an invalid pointer"),
-            DanglingPointerDeref => write!(f, "dangling pointer was dereferenced"),
-            DoubleFree => write!(f, "tried to deallocate dangling pointer"),
-            InvalidFunctionPointer => {
-                write!(f, "tried to use a function pointer after offsetting it")
-            }
-            InvalidBool => write!(f, "invalid boolean value read"),
-            InvalidNullPointerUsage => write!(f, "invalid use of NULL pointer"),
-            ReadPointerAsBytes => write!(
-                f,
-                "a raw memory access tried to access part of a pointer value as raw \
-                    bytes"
-            ),
-            ReadBytesAsPointer => {
-                write!(f, "a memory access tried to interpret some bytes as a pointer")
-            }
-            ReadForeignStatic => write!(f, "tried to read from foreign (extern) static"),
-            InvalidPointerMath => write!(
-                f,
-                "attempted to do invalid arithmetic on pointers that would leak base \
-                    addresses, e.g., comparing pointers into different allocations"
-            ),
-            DeadLocal => write!(f, "tried to access a dead local variable"),
-            DerefFunctionPointer => write!(f, "tried to dereference a function pointer"),
-            ExecuteMemory => write!(f, "tried to treat a memory pointer as a function pointer"),
-            OutOfTls => write!(f, "reached the maximum number of representable TLS keys"),
-            TlsOutOfBounds => write!(f, "accessed an invalid (unallocated) TLS key"),
-            CalledClosureAsFunction => {
-                write!(f, "tried to call a closure through a function pointer")
-            }
-            VtableForArgumentlessMethod => {
-                write!(f, "tried to call a vtable function without arguments")
+            Unsupported(ref msg) => write!(f, "{}", msg),
+            ConstPropUnsupported(ref msg) => {
+                write!(f, "Constant propagation encountered an unsupported situation: {}", msg)
             }
-            ModifiedConstantMemory => write!(f, "tried to modify constant memory"),
+            ReadForeignStatic(did) => write!(f, "tried to read from foreign (extern) static {:?}", did),
+            NoMirFor(did) => write!(f, "could not load MIR for {:?}", did),
             ModifiedStatic => write!(
                 f,
                 "tried to modify a static's initial value from another static's \
                     initializer"
             ),
-            ReallocateNonBasePtr => write!(
-                f,
-                "tried to reallocate with a pointer not to the beginning of an \
-                    existing object"
-            ),
-            DeallocateNonBasePtr => write!(
-                f,
-                "tried to deallocate with a pointer not to the beginning of an \
-                    existing object"
-            ),
-            HeapAllocZeroBytes => write!(f, "tried to re-, de- or allocate zero bytes on the heap"),
-            ReadFromReturnPointer => write!(f, "tried to read from the return pointer"),
-            UnimplementedTraitSelection => {
-                write!(f, "there were unresolved type arguments during trait selection")
-            }
-            InvalidBoolOp(_) => write!(f, "invalid boolean operation"),
-            UnterminatedCString(_) => write!(
-                f,
-                "attempted to get length of a null-terminated string, but no null \
-                    found before end of allocation"
-            ),
-            ReadUndefBytes(_) => write!(f, "attempted to read undefined bytes"),
-            HeapAllocNonPowerOfTwoAlignment(_) => write!(
+
+            ReadPointerAsBytes => write!(
                 f,
-                "tried to re-, de-, or allocate heap memory with alignment that is \
-                    not a power of two"
+                "unable to turn this pointer into raw bytes",
             ),
-            Unsupported(ref msg) => write!(f, "{}", msg),
-            ConstPropUnsupported(ref msg) => {
-                write!(f, "Constant propagation encountered an unsupported situation: {}", msg)
+            ReadBytesAsPointer => {
+                write!(f, "unable to turn these bytes into a pointer")
             }
         }
     }
@@ -590,7 +521,7 @@ pub enum InterpError<'tcx> {
     UndefinedBehavior(UndefinedBehaviorInfo),
     /// The program did something the interpreter does not support (some of these *might* be UB
     /// but the interpreter is not sure).
-    Unsupported(UnsupportedOpInfo<'tcx>),
+    Unsupported(UnsupportedOpInfo),
     /// The program was invalid (ill-typed, bad MIR, not sufficiently monomorphized, ...).
     InvalidProgram(InvalidProgramInfo<'tcx>),
     /// The program exhausted the interpreter's resources (stack/heap too big,
@@ -606,7 +537,7 @@ pub type InterpResult<'tcx, T = ()> = Result<T, InterpErrorInfo<'tcx>>;
 impl fmt::Display for InterpError<'_> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         // Forward `Display` to `Debug`.
-        write!(f, "{:?}", self)
+        fmt::Debug::fmt(self, f)
     }
 }
 
@@ -631,7 +562,7 @@ impl InterpError<'_> {
         match self {
             InterpError::MachineStop(_)
             | InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_))
-            | InterpError::Unsupported(UnsupportedOpInfo::ValidationFailure(_))
+            | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationFailure(_))
             | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_))
             | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::UbExperimental(_)) => true,
             _ => false,
diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs
index 64c07b431db..0b5bb7f3c03 100644
--- a/src/librustc/mir/interpret/mod.rs
+++ b/src/librustc/mir/interpret/mod.rs
@@ -161,7 +161,13 @@ pub struct AllocId(pub u64);
 
 impl fmt::Debug for AllocId {
     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(fmt, "alloc{}", self.0)
+        fmt::Display::fmt(self, fmt)
+    }
+}
+
+impl fmt::Display for AllocId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "alloc{}", self.0)
     }
 }
 
@@ -351,12 +357,6 @@ impl<'s> AllocDecodingSession<'s> {
     }
 }
 
-impl fmt::Display for AllocId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}", self.0)
-    }
-}
-
 /// An allocation in the global (tcx-managed) memory can be either a function pointer,
 /// a static, or a "real" allocation with some data in it.
 #[derive(Debug, Clone, Eq, PartialEq, Hash, RustcDecodable, RustcEncodable, HashStable)]
diff --git a/src/librustc/mir/interpret/pointer.rs b/src/librustc/mir/interpret/pointer.rs
index cc3c50b7899..2cbe25f9674 100644
--- a/src/librustc/mir/interpret/pointer.rs
+++ b/src/librustc/mir/interpret/pointer.rs
@@ -213,20 +213,4 @@ impl<'tcx, Tag> Pointer<Tag> {
     pub fn erase_tag(self) -> Pointer {
         Pointer { alloc_id: self.alloc_id, offset: self.offset, tag: () }
     }
-
-    /// Test if the pointer is "inbounds" of an allocation of the given size.
-    /// A pointer is "inbounds" even if its offset is equal to the size; this is
-    /// a "one-past-the-end" pointer.
-    #[inline(always)]
-    pub fn check_inbounds_alloc(
-        self,
-        allocation_size: Size,
-        msg: CheckInAllocMsg,
-    ) -> InterpResult<'tcx, ()> {
-        if self.offset > allocation_size {
-            throw_unsup!(PointerOutOfBounds { ptr: self.erase_tag(), msg, allocation_size })
-        } else {
-            Ok(())
-        }
-    }
 }
diff --git a/src/librustc/mir/interpret/value.rs b/src/librustc/mir/interpret/value.rs
index 9dc0b24cd2f..854f3e0146d 100644
--- a/src/librustc/mir/interpret/value.rs
+++ b/src/librustc/mir/interpret/value.rs
@@ -429,10 +429,11 @@ impl<'tcx, Tag> Scalar<Tag> {
     }
 
     pub fn to_bool(self) -> InterpResult<'tcx, bool> {
-        match self {
-            Scalar::Raw { data: 0, size: 1 } => Ok(false),
-            Scalar::Raw { data: 1, size: 1 } => Ok(true),
-            _ => throw_unsup!(InvalidBool),
+        let val = self.to_u8()?;
+        match val {
+            0 => Ok(false),
+            1 => Ok(true),
+            _ => throw_ub!(InvalidBool(val)),
         }
     }
 
@@ -440,7 +441,7 @@ impl<'tcx, Tag> Scalar<Tag> {
         let val = self.to_u32()?;
         match ::std::char::from_u32(val) {
             Some(c) => Ok(c),
-            None => throw_unsup!(InvalidChar(val as u128)),
+            None => throw_ub!(InvalidChar(val)),
         }
     }
 
@@ -583,7 +584,7 @@ impl<'tcx, Tag> ScalarMaybeUndef<Tag> {
     pub fn not_undef(self) -> InterpResult<'static, Scalar<Tag>> {
         match self {
             ScalarMaybeUndef::Scalar(scalar) => Ok(scalar),
-            ScalarMaybeUndef::Undef => throw_unsup!(ReadUndefBytes(Size::ZERO)),
+            ScalarMaybeUndef::Undef => throw_ub!(InvalidUndefBytes(None)),
         }
     }
 
diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs
index bb661d3d2a3..28889486c38 100644
--- a/src/librustc_mir/const_eval/machine.rs
+++ b/src/librustc_mir/const_eval/machine.rs
@@ -240,7 +240,8 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
         Ok(Some(match ecx.load_mir(instance.def, None) {
             Ok(body) => *body,
             Err(err) => {
-                if let err_unsup!(NoMirFor(ref path)) = err.kind {
+                if let err_unsup!(NoMirFor(did)) = err.kind {
+                    let path = ecx.tcx.def_path_str(did);
                     return Err(ConstEvalErrKind::NeedsRfc(format!(
                         "calling extern function `{}`",
                         path
diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs
index 9b28b7a20c0..b7888d85f38 100644
--- a/src/librustc_mir/interpret/eval_context.rs
+++ b/src/librustc_mir/interpret/eval_context.rs
@@ -138,7 +138,7 @@ pub enum LocalValue<Tag = (), Id = AllocId> {
 impl<'tcx, Tag: Copy + 'static> LocalState<'tcx, Tag> {
     pub fn access(&self) -> InterpResult<'tcx, Operand<Tag>> {
         match self.value {
-            LocalValue::Dead => throw_unsup!(DeadLocal),
+            LocalValue::Dead => throw_ub!(DeadLocal),
             LocalValue::Uninitialized => {
                 bug!("The type checker should prevent reading from a never-written local")
             }
@@ -152,7 +152,7 @@ impl<'tcx, Tag: Copy + 'static> LocalState<'tcx, Tag> {
         &mut self,
     ) -> InterpResult<'tcx, Result<&mut LocalValue<Tag>, MemPlace<Tag>>> {
         match self.value {
-            LocalValue::Dead => throw_unsup!(DeadLocal),
+            LocalValue::Dead => throw_ub!(DeadLocal),
             LocalValue::Live(Operand::Indirect(mplace)) => Ok(Err(mplace)),
             ref mut local @ LocalValue::Live(Operand::Immediate(_))
             | ref mut local @ LocalValue::Uninitialized => Ok(Ok(local)),
@@ -326,7 +326,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 if self.tcx.is_mir_available(did) {
                     Ok(self.tcx.optimized_mir(did).unwrap_read_only())
                 } else {
-                    throw_unsup!(NoMirFor(self.tcx.def_path_str(def_id)))
+                    throw_unsup!(NoMirFor(def_id))
                 }
             }
             _ => Ok(self.tcx.instance_mir(instance)),
diff --git a/src/librustc_mir/interpret/intern.rs b/src/librustc_mir/interpret/intern.rs
index 9b13db2289e..90b8a493299 100644
--- a/src/librustc_mir/interpret/intern.rs
+++ b/src/librustc_mir/interpret/intern.rs
@@ -327,7 +327,7 @@ pub fn intern_const_alloc_recursive<M: CompileTimeMachine<'mir, 'tcx>>(
         if let Err(error) = interned {
             // This can happen when e.g. the tag of an enum is not a valid discriminant. We do have
             // to read enum discriminants in order to find references in enum variant fields.
-            if let err_unsup!(ValidationFailure(_)) = error.kind {
+            if let err_ub!(ValidationFailure(_)) = error.kind {
                 let err = crate::const_eval::error_to_const_error(&ecx, error);
                 match err.struct_error(
                     ecx.tcx,
@@ -390,7 +390,7 @@ pub fn intern_const_alloc_recursive<M: CompileTimeMachine<'mir, 'tcx>>(
             }
         } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
             // dangling pointer
-            throw_unsup!(ValidationFailure("encountered dangling pointer in final constant".into()))
+            throw_ub_format!("encountered dangling pointer in final constant")
         } else if ecx.tcx.alloc_map.lock().get(alloc_id).is_none() {
             // We have hit an `AllocId` that is neither in local or global memory and isn't marked
             // as dangling by local memory.
diff --git a/src/librustc_mir/interpret/intrinsics.rs b/src/librustc_mir/interpret/intrinsics.rs
index 1e5ed76c467..75d936600b6 100644
--- a/src/librustc_mir/interpret/intrinsics.rs
+++ b/src/librustc_mir/interpret/intrinsics.rs
@@ -134,7 +134,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let bits = self.force_bits(val, layout_of.size)?;
                 let kind = match layout_of.abi {
                     ty::layout::Abi::Scalar(ref scalar) => scalar.value,
-                    _ => throw_unsup!(TypeNotPrimitive(ty)),
+                    _ => bug!("{} called on invalid type {:?}", intrinsic_name, ty),
                 };
                 let (nonzero, intrinsic_name) = match intrinsic_name {
                     sym::cttz_nonzero => (true, sym::cttz),
diff --git a/src/librustc_mir/interpret/machine.rs b/src/librustc_mir/interpret/machine.rs
index 087517ff4e3..d86ea026ad0 100644
--- a/src/librustc_mir/interpret/machine.rs
+++ b/src/librustc_mir/interpret/machine.rs
@@ -281,7 +281,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
         int: u64,
     ) -> InterpResult<'tcx, Pointer<Self::PointerTag>> {
         Err((if int == 0 {
-            err_unsup!(InvalidNullPointerUsage)
+            err_ub!(InvalidNullPointerUsage)
         } else {
             err_unsup!(ReadBytesAsPointer)
         })
diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs
index 82a467c7ba9..24147bdd44f 100644
--- a/src/librustc_mir/interpret/memory.rs
+++ b/src/librustc_mir/interpret/memory.rs
@@ -215,7 +215,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
         kind: MemoryKind<M::MemoryKinds>,
     ) -> InterpResult<'tcx, Pointer<M::PointerTag>> {
         if ptr.offset.bytes() != 0 {
-            throw_unsup!(ReallocateNonBasePtr)
+            throw_ub_format!("reallocating {:?} which does not point to the beginning of an object", ptr);
         }
 
         // For simplicities' sake, we implement reallocate as "alloc, copy, dealloc".
@@ -251,7 +251,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
         trace!("deallocating: {}", ptr.alloc_id);
 
         if ptr.offset.bytes() != 0 {
-            throw_unsup!(DeallocateNonBasePtr)
+            throw_ub_format!("deallocating {:?} which does not point to the beginning of an object", ptr);
         }
 
         let (alloc_kind, mut alloc) = match self.alloc_map.remove(&ptr.alloc_id) {
@@ -259,29 +259,24 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
             None => {
                 // Deallocating static memory -- always an error
                 return Err(match self.tcx.alloc_map.lock().get(ptr.alloc_id) {
-                    Some(GlobalAlloc::Function(..)) => err_unsup!(DeallocatedWrongMemoryKind(
-                        "function".to_string(),
-                        format!("{:?}", kind),
-                    )),
-                    Some(GlobalAlloc::Static(..)) | Some(GlobalAlloc::Memory(..)) => err_unsup!(
-                        DeallocatedWrongMemoryKind("static".to_string(), format!("{:?}", kind))
-                    ),
-                    None => err_unsup!(DoubleFree),
+                    Some(GlobalAlloc::Function(..)) => err_ub_format!("deallocating a function"),
+                    Some(GlobalAlloc::Static(..)) | Some(GlobalAlloc::Memory(..)) =>
+                        err_ub_format!("deallocating static memory"),
+                    None => err_ub!(PointerUseAfterFree(ptr.alloc_id)),
                 }
                 .into());
             }
         };
 
         if alloc_kind != kind {
-            throw_unsup!(DeallocatedWrongMemoryKind(
-                format!("{:?}", alloc_kind),
-                format!("{:?}", kind),
-            ))
+            throw_ub_format!("deallocating `{:?}` memory using `{:?}` deallocation operation", alloc_kind, kind);
         }
         if let Some((size, align)) = old_size_and_align {
             if size != alloc.size || align != alloc.align {
-                let bytes = alloc.size;
-                throw_unsup!(IncorrectAllocationInformation(size, bytes, align, alloc.align))
+                throw_ub_format!(
+                    "incorrect layout on deallocation: allocation has size {} and alignment {}, but gave size {} and alignment {}",
+                    alloc.size.bytes(), alloc.align.bytes(), size.bytes(), align.bytes(),
+                )
             }
         }
 
@@ -338,7 +333,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
             } else {
                 // The biggest power of two through which `offset` is divisible.
                 let offset_pow2 = 1 << offset.trailing_zeros();
-                throw_unsup!(AlignmentCheckFailed {
+                throw_ub!(AlignmentCheckFailed {
                     has: Align::from_bytes(offset_pow2).unwrap(),
                     required: align,
                 })
@@ -360,7 +355,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
                 assert!(size.bytes() == 0);
                 // Must be non-NULL.
                 if bits == 0 {
-                    throw_unsup!(InvalidNullPointerUsage)
+                    throw_ub!(InvalidNullPointerUsage)
                 }
                 // Must be aligned.
                 if let Some(align) = align {
@@ -375,7 +370,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
                 // It is sufficient to check this for the end pointer. The addition
                 // checks for overflow.
                 let end_ptr = ptr.offset(size, self)?;
-                end_ptr.check_inbounds_alloc(allocation_size, msg)?;
+                if end_ptr.offset > allocation_size { // equal is okay!
+                    throw_ub!(PointerOutOfBounds { ptr: end_ptr.erase_tag(), msg, allocation_size })
+                }
                 // Test align. Check this last; if both bounds and alignment are violated
                 // we want the error to be about the bounds.
                 if let Some(align) = align {
@@ -385,7 +382,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
                         // got picked we might be aligned even if this check fails.
                         // We instead have to fall back to converting to an integer and checking
                         // the "real" alignment.
-                        throw_unsup!(AlignmentCheckFailed { has: alloc_align, required: align });
+                        throw_ub!(AlignmentCheckFailed { has: alloc_align, required: align });
                     }
                     check_offset_align(ptr.offset.bytes(), align)?;
                 }
@@ -402,7 +399,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
         let (size, _align) = self
             .get_size_and_align(ptr.alloc_id, AllocCheck::MaybeDead)
             .expect("alloc info with MaybeDead cannot fail");
-        ptr.check_inbounds_alloc(size, CheckInAllocMsg::NullPointerTest).is_err()
+        // An inbounds pointer is never null!  And "inbounds" includes one-past-the-end.
+        let inbounds = ptr.offset <= size;
+        !inbounds
     }
 }
 
@@ -432,13 +431,13 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
         let alloc = tcx.alloc_map.lock().get(id);
         let alloc = match alloc {
             Some(GlobalAlloc::Memory(mem)) => Cow::Borrowed(mem),
-            Some(GlobalAlloc::Function(..)) => throw_unsup!(DerefFunctionPointer),
-            None => throw_unsup!(DanglingPointerDeref),
+            Some(GlobalAlloc::Function(..)) => throw_ub!(DerefFunctionPointer(id)),
+            None => throw_ub!(PointerUseAfterFree(id)),
             Some(GlobalAlloc::Static(def_id)) => {
                 // We got a "lazy" static that has not been computed yet.
                 if tcx.is_foreign_item(def_id) {
                     trace!("get_static_alloc: foreign item {:?}", def_id);
-                    throw_unsup!(ReadForeignStatic)
+                    throw_unsup!(ReadForeignStatic(def_id))
                 }
                 trace!("get_static_alloc: Need to compute {:?}", def_id);
                 let instance = Instance::mono(tcx.tcx, def_id);
@@ -524,7 +523,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
             // to give us a cheap reference.
             let alloc = Self::get_static_alloc(memory_extra, tcx, id)?;
             if alloc.mutability == Mutability::Not {
-                throw_unsup!(ModifiedConstantMemory)
+                throw_ub!(WriteToReadOnly(id))
             }
             match M::STATIC_KIND {
                 Some(kind) => Ok((MemoryKind::Machine(kind), alloc.into_owned())),
@@ -538,7 +537,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
             Ok(a) => {
                 let a = &mut a.1;
                 if a.mutability == Mutability::Not {
-                    throw_unsup!(ModifiedConstantMemory)
+                    throw_ub!(WriteToReadOnly(id))
                 }
                 Ok(a)
             }
@@ -568,7 +567,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
         if self.get_fn_alloc(id).is_some() {
             return if let AllocCheck::Dereferenceable = liveness {
                 // The caller requested no function pointers.
-                throw_unsup!(DerefFunctionPointer)
+                throw_ub!(DerefFunctionPointer(id))
             } else {
                 Ok((Size::ZERO, Align::from_bytes(1).unwrap()))
             };
@@ -596,12 +595,12 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
                 if let AllocCheck::MaybeDead = liveness {
                     // Deallocated pointers are allowed, we should be able to find
                     // them in the map.
-                    Ok(*self.dead_alloc_map.get(&id).expect(
-                        "deallocated pointers should all be recorded in \
-                            `dead_alloc_map`",
-                    ))
+                    Ok(*self
+                        .dead_alloc_map
+                        .get(&id)
+                        .expect("deallocated pointers should all be recorded in `dead_alloc_map`"))
                 } else {
-                    throw_unsup!(DanglingPointerDeref)
+                    throw_ub!(PointerUseAfterFree(id))
                 }
             }
         }
@@ -626,10 +625,10 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
     ) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
         let ptr = self.force_ptr(ptr)?; // We definitely need a pointer value.
         if ptr.offset.bytes() != 0 {
-            throw_unsup!(InvalidFunctionPointer)
+            throw_ub!(InvalidFunctionPointer(ptr.erase_tag()))
         }
         let id = M::canonical_alloc_id(self, ptr.alloc_id);
-        self.get_fn_alloc(id).ok_or_else(|| err_unsup!(ExecuteMemory).into())
+        self.get_fn_alloc(id).ok_or_else(|| err_ub!(InvalidFunctionPointer(ptr.erase_tag())).into())
     }
 
     pub fn mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> {
diff --git a/src/librustc_mir/interpret/operand.rs b/src/librustc_mir/interpret/operand.rs
index 22b1a7b7137..227ba540e6b 100644
--- a/src/librustc_mir/interpret/operand.rs
+++ b/src/librustc_mir/interpret/operand.rs
@@ -344,7 +344,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         let len = mplace.len(self)?;
         let bytes = self.memory.read_bytes(mplace.ptr, Size::from_bytes(len as u64))?;
         let str = ::std::str::from_utf8(bytes)
-            .map_err(|err| err_unsup!(ValidationFailure(err.to_string())))?;
+            .map_err(|err| err_ub_format!("this string is not valid UTF-8: {}", err))?;
         Ok(str)
     }
 
@@ -458,7 +458,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         layout: Option<TyLayout<'tcx>>,
     ) -> InterpResult<'tcx, OpTy<'tcx, M::PointerTag>> {
         let base_op = match place.local {
-            mir::RETURN_PLACE => throw_unsup!(ReadFromReturnPointer),
+            mir::RETURN_PLACE => throw_ub!(ReadFromReturnPlace),
             local => {
                 // Do not use the layout passed in as argument if the base we are looking at
                 // here is not the entire place.
diff --git a/src/librustc_mir/interpret/place.rs b/src/librustc_mir/interpret/place.rs
index a4815b9696e..107cfee5ace 100644
--- a/src/librustc_mir/interpret/place.rs
+++ b/src/librustc_mir/interpret/place.rs
@@ -926,7 +926,7 @@ where
             // most likey we *are* running `typeck` right now. Investigate whether we can bail out
             // on `typeck_tables().has_errors` at all const eval entry points.
             debug!("Size mismatch when transmuting!\nsrc: {:#?}\ndest: {:#?}", src, dest);
-            throw_unsup!(TransmuteSizeDiff(src.layout.ty, dest.layout.ty));
+            throw_inval!(TransmuteSizeDiff(src.layout.ty, dest.layout.ty));
         }
         // Unsized copies rely on interpreting `src.meta` with `dest.layout`, we want
         // to avoid that here.
diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs
index b5c34daf8a3..51a557851fc 100644
--- a/src/librustc_mir/interpret/terminator.rs
+++ b/src/librustc_mir/interpret/terminator.rs
@@ -170,13 +170,19 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             trace!("Skipping callee ZST");
             return Ok(());
         }
-        let caller_arg = caller_arg.next().ok_or_else(|| err_unsup!(FunctionArgCountMismatch))?;
+        let caller_arg = caller_arg.next().ok_or_else(|| {
+            err_ub_format!("calling a function passing fewer arguments than it requires")
+        })?;
         if rust_abi {
             assert!(!caller_arg.layout.is_zst(), "ZSTs must have been already filtered out");
         }
         // Now, check
         if !Self::check_argument_compat(rust_abi, caller_arg.layout, callee_arg.layout) {
-            throw_unsup!(FunctionArgMismatch(caller_arg.layout.ty, callee_arg.layout.ty))
+            throw_ub_format!(
+                "calling a function with argument of type {:?} passing data of type {:?}",
+                callee_arg.layout.ty,
+                caller_arg.layout.ty
+            )
         }
         // We allow some transmutes here
         self.copy_op_transmute(caller_arg, callee_arg)
@@ -221,7 +227,11 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 abi => abi,
             };
             if normalize_abi(caller_abi) != normalize_abi(callee_abi) {
-                throw_unsup!(FunctionAbiMismatch(caller_abi, callee_abi))
+                throw_ub_format!(
+                    "calling a function with ABI {:?} using caller ABI {:?}",
+                    callee_abi,
+                    caller_abi
+                )
             }
         }
 
@@ -254,107 +264,110 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 // We want to pop this frame again in case there was an error, to put
                 // the blame in the right location.  Until the 2018 edition is used in
                 // the compiler, we have to do this with an immediately invoked function.
-                let res =
-                    (|| {
-                        trace!(
-                            "caller ABI: {:?}, args: {:#?}",
-                            caller_abi,
-                            args.iter()
-                                .map(|arg| (arg.layout.ty, format!("{:?}", **arg)))
-                                .collect::<Vec<_>>()
-                        );
-                        trace!(
-                            "spread_arg: {:?}, locals: {:#?}",
-                            body.spread_arg,
-                            body.args_iter()
-                                .map(|local| (
-                                    local,
-                                    self.layout_of_local(self.frame(), local, None).unwrap().ty
-                                ))
-                                .collect::<Vec<_>>()
-                        );
-
-                        // Figure out how to pass which arguments.
-                        // The Rust ABI is special: ZST get skipped.
-                        let rust_abi = match caller_abi {
-                            Abi::Rust | Abi::RustCall => true,
-                            _ => false,
+                let res = (|| {
+                    trace!(
+                        "caller ABI: {:?}, args: {:#?}",
+                        caller_abi,
+                        args.iter()
+                            .map(|arg| (arg.layout.ty, format!("{:?}", **arg)))
+                            .collect::<Vec<_>>()
+                    );
+                    trace!(
+                        "spread_arg: {:?}, locals: {:#?}",
+                        body.spread_arg,
+                        body.args_iter()
+                            .map(|local| (
+                                local,
+                                self.layout_of_local(self.frame(), local, None).unwrap().ty
+                            ))
+                            .collect::<Vec<_>>()
+                    );
+
+                    // Figure out how to pass which arguments.
+                    // The Rust ABI is special: ZST get skipped.
+                    let rust_abi = match caller_abi {
+                        Abi::Rust | Abi::RustCall => true,
+                        _ => false,
+                    };
+                    // We have two iterators: Where the arguments come from,
+                    // and where they go to.
+
+                    // For where they come from: If the ABI is RustCall, we untuple the
+                    // last incoming argument.  These two iterators do not have the same type,
+                    // so to keep the code paths uniform we accept an allocation
+                    // (for RustCall ABI only).
+                    let caller_args: Cow<'_, [OpTy<'tcx, M::PointerTag>]> =
+                        if caller_abi == Abi::RustCall && !args.is_empty() {
+                            // Untuple
+                            let (&untuple_arg, args) = args.split_last().unwrap();
+                            trace!("eval_fn_call: Will pass last argument by untupling");
+                            Cow::from(
+                                args.iter()
+                                    .map(|&a| Ok(a))
+                                    .chain(
+                                        (0..untuple_arg.layout.fields.count())
+                                            .map(|i| self.operand_field(untuple_arg, i as u64)),
+                                    )
+                                    .collect::<InterpResult<'_, Vec<OpTy<'tcx, M::PointerTag>>>>(
+                                    )?,
+                            )
+                        } else {
+                            // Plain arg passing
+                            Cow::from(args)
                         };
-                        // We have two iterators: Where the arguments come from,
-                        // and where they go to.
-
-                        // For where they come from: If the ABI is RustCall, we untuple the
-                        // last incoming argument.  These two iterators do not have the same type,
-                        // so to keep the code paths uniform we accept an allocation
-                        // (for RustCall ABI only).
-                        let caller_args: Cow<'_, [OpTy<'tcx, M::PointerTag>]> =
-                            if caller_abi == Abi::RustCall && !args.is_empty() {
-                                // Untuple
-                                let (&untuple_arg, args) = args.split_last().unwrap();
-                                trace!("eval_fn_call: Will pass last argument by untupling");
-                                Cow::from(args.iter().map(|&a| Ok(a))
-                                .chain((0..untuple_arg.layout.fields.count())
-                                    .map(|i| self.operand_field(untuple_arg, i as u64))
-                                )
-                                .collect::<InterpResult<'_, Vec<OpTy<'tcx, M::PointerTag>>>>()?)
-                            } else {
-                                // Plain arg passing
-                                Cow::from(args)
-                            };
-                        // Skip ZSTs
-                        let mut caller_iter = caller_args
-                            .iter()
-                            .filter(|op| !rust_abi || !op.layout.is_zst())
-                            .copied();
-
-                        // Now we have to spread them out across the callee's locals,
-                        // taking into account the `spread_arg`.  If we could write
-                        // this is a single iterator (that handles `spread_arg`), then
-                        // `pass_argument` would be the loop body. It takes care to
-                        // not advance `caller_iter` for ZSTs
-                        for local in body.args_iter() {
-                            let dest = self.eval_place(&mir::Place::from(local))?;
-                            if Some(local) == body.spread_arg {
-                                // Must be a tuple
-                                for i in 0..dest.layout.fields.count() {
-                                    let dest = self.place_field(dest, i as u64)?;
-                                    self.pass_argument(rust_abi, &mut caller_iter, dest)?;
-                                }
-                            } else {
-                                // Normal argument
+                    // Skip ZSTs
+                    let mut caller_iter =
+                        caller_args.iter().filter(|op| !rust_abi || !op.layout.is_zst()).copied();
+
+                    // Now we have to spread them out across the callee's locals,
+                    // taking into account the `spread_arg`.  If we could write
+                    // this is a single iterator (that handles `spread_arg`), then
+                    // `pass_argument` would be the loop body. It takes care to
+                    // not advance `caller_iter` for ZSTs.
+                    let mut locals_iter = body.args_iter();
+                    while let Some(local) = locals_iter.next() {
+                        let dest = self.eval_place(&mir::Place::from(local))?;
+                        if Some(local) == body.spread_arg {
+                            // Must be a tuple
+                            for i in 0..dest.layout.fields.count() {
+                                let dest = self.place_field(dest, i as u64)?;
                                 self.pass_argument(rust_abi, &mut caller_iter, dest)?;
                             }
+                        } else {
+                            // Normal argument
+                            self.pass_argument(rust_abi, &mut caller_iter, dest)?;
                         }
-                        // Now we should have no more caller args
-                        if caller_iter.next().is_some() {
-                            trace!("Caller has passed too many args");
-                            throw_unsup!(FunctionArgCountMismatch)
+                    }
+                    // Now we should have no more caller args
+                    if caller_iter.next().is_some() {
+                        throw_ub_format!(
+                            "calling a function passing more arguments than it expected"
+                        )
+                    }
+                    // Don't forget to check the return type!
+                    if let Some((caller_ret, _)) = ret {
+                        let callee_ret = self.eval_place(&mir::Place::return_place())?;
+                        if !Self::check_argument_compat(
+                            rust_abi,
+                            caller_ret.layout,
+                            callee_ret.layout,
+                        ) {
+                            throw_ub_format!(
+                                "calling a function with return type {:?} passing \
+                                     return place of type {:?}",
+                                callee_ret.layout.ty,
+                                caller_ret.layout.ty
+                            )
                         }
-                        // Don't forget to check the return type!
-                        if let Some((caller_ret, _)) = ret {
-                            let callee_ret = self.eval_place(&mir::Place::return_place())?;
-                            if !Self::check_argument_compat(
-                                rust_abi,
-                                caller_ret.layout,
-                                callee_ret.layout,
-                            ) {
-                                throw_unsup!(FunctionRetMismatch(
-                                    caller_ret.layout.ty,
-                                    callee_ret.layout.ty
-                                ))
-                            }
-                        } else {
-                            let local = mir::RETURN_PLACE;
-                            let callee_layout = self.layout_of_local(self.frame(), local, None)?;
-                            if !callee_layout.abi.is_uninhabited() {
-                                throw_unsup!(FunctionRetMismatch(
-                                    self.tcx.types.never,
-                                    callee_layout.ty
-                                ))
-                            }
+                    } else {
+                        let local = mir::RETURN_PLACE;
+                        let callee_layout = self.layout_of_local(self.frame(), local, None)?;
+                        if !callee_layout.abi.is_uninhabited() {
+                            throw_ub_format!("calling a returning function without a return place")
                         }
-                        Ok(())
-                    })();
+                    }
+                    Ok(())
+                })();
                 match res {
                     Err(err) => {
                         self.stack.pop();
diff --git a/src/librustc_mir/interpret/validity.rs b/src/librustc_mir/interpret/validity.rs
index 05bb010959b..b3a09cb81ba 100644
--- a/src/librustc_mir/interpret/validity.rs
+++ b/src/librustc_mir/interpret/validity.rs
@@ -29,7 +29,7 @@ macro_rules! throw_validation_failure {
             write_path(&mut msg, where_);
         }
         write!(&mut msg, ", but expected {}", $details).unwrap();
-        throw_unsup!(ValidationFailure(msg))
+        throw_ub!(ValidationFailure(msg))
     }};
     ($what:expr, $where:expr) => {{
         let mut msg = format!("encountered {}", $what);
@@ -38,7 +38,7 @@ macro_rules! throw_validation_failure {
             msg.push_str(" at ");
             write_path(&mut msg, where_);
         }
-        throw_unsup!(ValidationFailure(msg))
+        throw_ub!(ValidationFailure(msg))
     }};
 }
 
@@ -353,10 +353,10 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, 'tcx, M
                     place.ptr, size, align
                 );
                 match err.kind {
-                    err_unsup!(InvalidNullPointerUsage) => {
+                    err_ub!(InvalidNullPointerUsage) => {
                         throw_validation_failure!(format_args!("a NULL {}", kind), self.path)
                     }
-                    err_unsup!(AlignmentCheckFailed { required, has }) => {
+                    err_ub!(AlignmentCheckFailed { required, has }) => {
                         throw_validation_failure!(
                             format_args!(
                                 "an unaligned {} \
@@ -372,7 +372,7 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, 'tcx, M
                         format_args!("a dangling {} (created from integer)", kind),
                         self.path
                     ),
-                    err_unsup!(PointerOutOfBounds { .. }) | err_unsup!(DanglingPointerDeref) => {
+                    err_ub!(PointerOutOfBounds { .. }) | err_ub!(PointerUseAfterFree(_)) => {
                         throw_validation_failure!(
                             format_args!("a dangling {} (not entirely in bounds)", kind),
                             self.path
@@ -765,11 +765,11 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
                     Err(err) => {
                         // For some errors we might be able to provide extra information
                         match err.kind {
-                            err_unsup!(ReadUndefBytes(offset)) => {
+                            err_ub!(InvalidUndefBytes(Some(ptr))) => {
                                 // Some byte was undefined, determine which
                                 // element that byte belongs to so we can
                                 // provide an index.
-                                let i = (offset.bytes() / layout.size.bytes()) as usize;
+                                let i = (ptr.offset.bytes() / layout.size.bytes()) as usize;
                                 self.path.push(PathElem::ArrayElem(i));
 
                                 throw_validation_failure!("undefined bytes", self.path)
@@ -817,7 +817,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         // Run it.
         match visitor.visit_value(op) {
             Ok(()) => Ok(()),
-            Err(err) if matches!(err.kind, err_unsup!(ValidationFailure { .. })) => Err(err),
+            Err(err) if matches!(err.kind, err_ub!(ValidationFailure { .. })) => Err(err),
             Err(err) if cfg!(debug_assertions) => {
                 bug!("Unexpected error during validation: {}", err)
             }