miri: categorize errors into "unsupported" and "UB"
Also slightly refactor pointer bounds checks to avoid creating unnecessary temporary Errors
This commit is contained in:
parent
c20d7eecbc
commit
f5efb68a24
15 changed files with 293 additions and 364 deletions
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
ReadPointerAsBytes,
|
||||
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,
|
||||
/// 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,
|
||||
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>),
|
||||
|
||||
/// Encountered a pointer where we needed raw bytes.
|
||||
ReadPointerAsBytes,
|
||||
|
||||
/// Encountered raw bytes where we needed a pointer.
|
||||
ReadBytesAsPointer,
|
||||
}
|
||||
|
||||
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")
|
||||
Unsupported(ref msg) => write!(f, "{}", msg),
|
||||
ConstPropUnsupported(ref msg) => {
|
||||
write!(f, "Constant propagation encountered an unsupported situation: {}", msg)
|
||||
}
|
||||
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")
|
||||
}
|
||||
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!(
|
||||
|
||||
ReadPointerAsBytes => write!(
|
||||
f,
|
||||
"tried to reallocate with a pointer not to the beginning of an \
|
||||
existing object"
|
||||
"unable to turn this pointer into raw bytes",
|
||||
),
|
||||
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!(
|
||||
f,
|
||||
"tried to re-, de-, or allocate heap memory with alignment that is \
|
||||
not a power of two"
|
||||
),
|
||||
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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<_>>()
|
||||
);
|
||||
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,
|
||||
// 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.
|
||||
// Skip ZSTs
|
||||
let mut caller_iter =
|
||||
caller_args.iter().filter(|op| !rust_abi || !op.layout.is_zst()).copied();
|
||||
|
||||
// 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
|
||||
// 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)?;
|
||||
}
|
||||
}
|
||||
// Now we should have no more caller args
|
||||
if caller_iter.next().is_some() {
|
||||
trace!("Caller has passed too many args");
|
||||
throw_unsup!(FunctionArgCountMismatch)
|
||||
}
|
||||
// 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
|
||||
))
|
||||
}
|
||||
// Normal argument
|
||||
self.pass_argument(rust_abi, &mut caller_iter, dest)?;
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
}
|
||||
// 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
|
||||
)
|
||||
}
|
||||
} 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(())
|
||||
})();
|
||||
match res {
|
||||
Err(err) => {
|
||||
self.stack.pop();
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue